I am making a log function that has a printf-like function: log(LOG_LEVEL lvl, const char* fmt, ...).
LOG_LEVEL is just an enum (0 is critical, higher are less important).
If lvl is higher than another variable (say, current_log_level), I want to do as little as possible and return fast. However, I’m afraid of breaking the stack by not using the parameters after fmt, and I would like to avoid writing my own "check fmt and call va_arg() as required" if possible to avoid possible discrepancies with the printf format, so I thought about using vsnprintf and just have the length of the output string as small as possible.
According to the docs, for vsnprintf, if the length is set to 0, the output string can be null but I am not sure if that would use all the remaining arguments or if it just stops processing there and returns.
Imagine the following (I’m using C++17 but I think this also applies to good old C):
enum class LOG_LEVEL : unsigned int { ERROR, WARNING };
LOG_LEVEL current_log_level = LOG_LEVEL::WARNING;
void log(LOG_LEVEL lvl, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
if (static_cast<unsigned int>(lvl) <= static_cast<unsigned int>(current_log_level))
{
/* lock a mutex, write to a file and/or whatever */
}
else
{
vsnprintf(nullptr, 0, fmt, args);
}
va_end(args);
}
I feel that vsnprintf (and other v*printf functions) should use all the args even if it truncates, (if not, that’d be a nice security issue in many programs), but it is not mentioned in the man pages and I could not find any information. Maybe my search-fu was not good enough. Or maybe there is a better, faster and/or easier way to do this?
>Solution :
Does vsnprintf() use all va_list arguments even if the output is truncated?
Is an implementation detail. The result has to be truncated. How is it implemented is not specified – potentially, vsnprintf source code can check if the output is longer and then stop processing.
I feel that vsnprintf (and other v*printf functions) should use all the args even if it truncate
I feel that is an unjustified feeling. Arguments exists on stack, they are there. If they are not accessed, they are not accessed, nothing happens.
Do nothing when you want to do nothing. Early return looks like a style to use here.
void log(LOG_LEVEL lvl, const char* fmt, ...) {
if (static_cast<unsigned int>(lvl) > static_cast<unsigned int>(current_log_level)) {
return;
}
va_list args;
va_start(args, fmt);
/* lock a mutex, write to a file and/or whatever */
va_end(args);
}
Consider adding __attribute__((__printf__(2,3))) for gcc compiler to issue formatting warinings.
But then, in C++ use C++ streams or std::format.
I was thinking that as the stack frame is not known by the compiler (unknown number of parameters), if not all were used then when you return the stack would get corrupted. No?
Bottom line, no. It is implemented in such a way that that does not happen. If you are interested in implementation details for x86 standard System V ABI you can read https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf relevant sections about va_list and friends .