3
\$\begingroup\$
#include <stdint.h>
#include <stdarg.h>
#include <stddef.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
HANDLE inHandle = INVALID_HANDLE_VALUE;
HANDLE outHandle = INVALID_HANDLE_VALUE;
#else
#include <unistd.h>
#endif
int stdoutFunction(const char* buffer, size_t bufferSize) {
#ifdef _WIN32
 int bytesWritten = 0;
 WriteConsoleA(outHandle, buffer, bufferSize, &bytesWritten, NULL);
 return bytesWritten;
#else
 return write(1, buffer, bufferSize);
#endif
}
size_t copyAscii(char* buffer, const char* src, size_t charSize, size_t bufferIdx, const size_t maxBufferIdx) {
 for (; *src != '0円'; src += charSize) {
 buffer[bufferIdx] = *src;
 bufferIdx += (bufferIdx < maxBufferIdx);
 }
 return bufferIdx;
}
size_t intToHexStr(char* buffer, const size_t n, size_t bufferIdx, const size_t maxBufferIdx) {
 const char hexCharacters[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
 for (int32_t shiftRightBy = (sizeof(size_t) * 8) - 4; shiftRightBy >= 0; shiftRightBy -= 4) {
 buffer[bufferIdx] = hexCharacters[(n >> shiftRightBy) % 16];
 bufferIdx += (bufferIdx < maxBufferIdx);
 }
 return bufferIdx;
}
size_t intToStr(char* buffer, size_t n, size_t bufferIdx, const size_t maxBufferIdx) {
 size_t frontIdx = bufferIdx;
 do {
 buffer[bufferIdx] = '0' + (n % 10);
 bufferIdx += (bufferIdx < maxBufferIdx);
 n /= 10;
 } while (n > 0);
 size_t backIdx = bufferIdx - 1;
 while (frontIdx < backIdx) {
 char tmp = buffer[frontIdx];
 buffer[frontIdx] = buffer[backIdx];
 buffer[backIdx] = tmp;
 frontIdx += 1;
 backIdx -= 1;
 }
 return bufferIdx;
}
// working format specifiers: s, ls, d, u, zu, x, %
int dumberPrintf(const char* fmt, ...) {
 if (fmt[0] == '0円') {
 return 0;
 }
 char buffer[512];
 size_t bufferIdx = 0;
 size_t maxBufferIdx = sizeof(buffer) - 1;
 va_list args;
 va_start(args, fmt);
 for (size_t fmtIdx = 0; fmt[fmtIdx] != '0円'; fmtIdx++) {
 if (fmt[fmtIdx] == '%') {
 if (
 fmt[fmtIdx + 1] == 's'
 || (fmt[fmtIdx + 1] == 'l' && fmt[fmtIdx + 2] == 's')
 ) {
 bufferIdx = copyAscii(
 buffer,
 fmt[fmtIdx + 1] == 's' ? va_arg(args, char*) : (char*)va_arg(args, wchar_t*),
 fmt[fmtIdx + 1] == 's' ? sizeof(char) : sizeof(wchar_t),
 bufferIdx,
 maxBufferIdx
 );
 fmtIdx += (fmt[fmtIdx + 1] == 'l');
 }
 else if (
 fmt[fmtIdx + 1] == 'd'
 || fmt[fmtIdx + 1] == 'u'
 || (fmt[fmtIdx + 1] == 'z' && fmt[fmtIdx + 2] == 'u')
 ) {
 bufferIdx = intToStr(buffer, va_arg(args, size_t), bufferIdx, maxBufferIdx);
 fmtIdx += (fmt[fmtIdx + 1] == 'z');
 }
 else if (fmt[fmtIdx + 1] == 'x') {
 bufferIdx = intToHexStr(buffer, va_arg(args, size_t), bufferIdx, maxBufferIdx);
 }
 else {
 buffer[bufferIdx] = '%';
 bufferIdx += (bufferIdx < maxBufferIdx);
 }
 fmtIdx += 1;
 }
 else {
 buffer[bufferIdx] = fmt[fmtIdx];
 bufferIdx += (bufferIdx < maxBufferIdx);
 }
 }
 va_end(args);
 return stdoutFunction(buffer, bufferIdx);
}
int main() {
#ifdef _WIN32
 inHandle = GetStdHandle(STD_INPUT_HANDLE);
 outHandle = GetStdHandle(STD_OUTPUT_HANDLE);
 if (inHandle == INVALID_HANDLE_VALUE || outHandle == INVALID_HANDLE_VALUE) {
 return GetLastError();
 }
#endif
 dumberPrintf("%%s test: %s\n%%ls test: %ls\n%%d test: %d\n%%u test: %u\n%%zu test: %zu\n%%x test: %x\n",
 "char string",
 L"wchar_t string",
 12345,
 54321,
 56789,
 0xabcdef
 );
 return 0;
}
/*
int __stdcall mainCRTStartup() {
 int result = main();
 return result;
}
*/

I'm making something which I'm going to update to not use any C runtime functions, including printf. I'm going to compile with /NODEFAULTLIB. I made this function to imitate printf. It works with the format specifiers s, ls, d, u, zu, x, and %. It's not expected to print any negative numbers. It's also not expected to print anything longer than 512 bytes, so it cuts off anything beyond that length.

Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
asked Dec 13, 2023 at 20:59
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Why, though? If you need a printf then just... Use that and don't pass NODEFAULTLIB. \$\endgroup\$ Commented Dec 13, 2023 at 22:48
  • \$\begingroup\$ I can't use those functions because I'm doing setup on Linux in the __attribute__((constructor)) function, and I was told you shouldn't use C runtime functions there. I'm also not using many C functions anyways. \$\endgroup\$ Commented Dec 13, 2023 at 23:07
  • \$\begingroup\$ You can use printf in __attribute__((constructor)), you cannot use std::cout as it is not initialized yet. But then that not c but c++. \$\endgroup\$ Commented Dec 14, 2023 at 9:35

2 Answers 2

2
\$\begingroup\$

Use the standard library

There is no reason to use /NODEFAULTLIB

  1. Use printf instead of rolling your solution. But since you are probably removing the standard library. Here you go.
  2. write does not promise that all bytes will be written. If the kernel buffers are smaller than the number of bytes to be written it will only write a few bytes. You have to address that in your stdoutFunction.
  3. You might want to create two different definitions for Windows and Linux and then choose which one to use based on a macro, instead of using macros in the same function. It makes the code easier to read. But that is just my style. People may differ.
  4. When writing parsing code, don't loop over the parse string character by character. Instead, think in terms of tokens. You can break your problem into tokenizing the format string and doing the correct stuff in a switch statement based on the token, like below. It is much more clear than mixing wchar_t and char strings. It also leads to fewer bugs because you are focusing on printing a single in each case statement.
typedef enum {
 ConversionType_StringLiteral = 0,
 ConversionType_String,
 ConversionType_WideString,
 //...
 ConversionType_MAX
} ConversionType;
typedef struct ConversionSpecification {
 ConversionType type;
 size_t length;
 size_t precision;
}ConversionSpecification;
ConversionSpecification getFormatSpecification(const char* fmt){
}
int dumberPrintf(const char* fmt, ...) {
 va_list args;
 va_start(args, fmt);
 size_t bytesWritten = 0;
 while(*fmt){
 ConversionSpecification spec = getFormatSpecification(fmt);
 if(spec.type >= ConversionType_MAX){
 return -1;
 }
 switch(spec.type){
 case ConversionType_StringLiteral:
 {
 // Do string literal stuff
 }
 case ConversionType_String:
 {
 // Do string stuff
 }
 case ConversionType_WideString:
 {
 // Do wide string stuff stuff
 }
 default:
 }
 fmt += spec.length;`
 }
 va_end(args);
 return bytesWritten;
}
Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
answered Dec 14, 2023 at 10:37
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Thank you. Also, I read your comment, but I thought you weren't supposed to use standard library functions in __attribute__((constructor)) because the standard library might need things to be initialized too. \$\endgroup\$ Commented Dec 14, 2023 at 16:33
  • 1
    \$\begingroup\$ The C runtime is what calls __attribute__((constructor)) and then main. The C runtime also contains all external linkages to the C standard library. It is true that neither POSIX nor the C standard guarantees that the C runtime will be initialized before __attribute__((constructor)). But that is because none of them recognize the existence of __attribute__((constructor)). It is a compiler-specific attribute. What is your use case though? There might be better ways to implement it instead of doing this \$\endgroup\$ Commented Dec 14, 2023 at 18:40
  • \$\begingroup\$ I'm writing to a game's executable segment to mod it for a speedrunning tool. On Linux, I'm doing this using LD_PRELOAD. I also can't just change the game's executable because the moderators said they wouldn't allow that. \$\endgroup\$ Commented Dec 14, 2023 at 18:48
  • \$\begingroup\$ this is how you get perma bans. You know that anti-cheat will easily catch you if you try to inject a dll right? They also scan and hash your segments and send them to a server for verification. Also, how is attribute constructor going to help you with changing another program's executable segment? \$\endgroup\$ Commented Dec 14, 2023 at 19:29
  • \$\begingroup\$ There is no anti-cheat system, it's a speedrunning-related mod for a single player game. I use LD_PRELOAD to load a .so file which uses __attribute__((constructor)) to set things up and write to the game's executable segment when it starts up. \$\endgroup\$ Commented Dec 14, 2023 at 19:52
2
\$\begingroup\$

A few points to add:

  1. I have no idea why you will or won't mark any parameter you could const. At best, you are awfully inconsistent in that area.
    You don't get the benefits of making what you can constant, small as they are when the scope is small. Nor do you get the benefit of not cluttering the prototype with inconsequential noise.

  2. I suspect at least some of the functions should be purely for internal use. Consider marking them static to enforce that, and keep them out of any exports.

  3. Even though it might be made static by the compiler under the as-if rule due to how it is used, your const local has auto storage duration, which might result in extra overhead.

  4. var += 1 and var -= 1 are more verbose and much less common than var++ and var--.

answered Dec 15, 2023 at 20:48
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.