Reputation: 1935
In the project I work on, which is a Windows and Ubuntu application, I have defined a macro that interacts with a logging library. Essentially, this macro takes printf-like input, converts to string, and sends it to the logging library. I attach an example of the macro using std::cout
instead of the logging library:
#include <stdio.h>
#include <iostream>
#ifdef _WIN32
#define custom_sprintf sprintf_s
#else
#define custom_sprintf sprintf
#endif
#define LOG_PF(...) \
do \
{ \
char buffer[500]; \
custom_sprintf(buffer, __VA_ARGS__); \
std::cout << buffer << std::endl; \
} while (false)
int main()
{
int a = 3, b = 5;
LOG_PF("%i + %i = %i", a, b, a + b);
return EXIT_SUCCESS;
}
the custom_sprintf
part is to avoid the following windows warning:
C4996: 'sprintf': This function or variable may be unsafe. Consider using sprintf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
This macro works perfectly and does its job unless you need more than 500 char to allocate the message that is being logged. If there is not enough memory, Windows produces a segfault while Ubuntu does print the desired output but there are a compilation warning and an error message in runtime:
*** stack smashing detected ***: terminated
To be able to allocate the proper size of the buffer, I've tried three approaches without success.
In the first one, I try to get the size as a const size_t
so I can allocate a buffer of the right size:
#include <stdio.h>
#include <iostream>
#ifdef _WIN32
#define custom_sprintf sprintf_s
#else
#define custom_sprintf sprintf
#endif
#define LOG_PF(...) \
do \
{ \
const size_t neded = snprintf(nullptr, 0, __VA_ARGS__); \
char buffer[neded]; \
custom_sprintf(buffer, __VA_ARGS__); \
std::cout << buffer << std::endl; \
} while (false)
int main()
{
int a = 3, b = 5;
LOG_PF("%i + %i = %i", a, b, a + b);
return EXIT_SUCCESS;
}
This solution works perfectly on Ubuntu. However, it does not compile in windows saying
Error C2131 expression did not evaluate to a constant
Error C2664 'int sprintf_s(char *const ,const size_t,const char *const,...)': cannot convert argument 2 from 'const char [13]' to 'const size_t'
In the second one, I have tried to define two different macros, which is obviously not optimal for maintenance:
#include <stdio.h>
#include <iostream>
#ifdef _WIN32
#define LOG_PF(...) \
do \
{ \
int n = snprintf(nullptr, 0, __VA_ARGS__); \
char* buffer = new char[n]; \
sprintf_s(buffer,n, __VA_ARGS__); \
std::cout << buffer << std::endl; \
delete[] buffer; \
} while (false)
#else
#define LOG_PF(...) \
do \
{ \
int n = snprintf(nullptr, 0, __VA_ARGS__); \
char* buffer = new char[n]; \
sprintf(buffer, __VA_ARGS__); \
std::cout << buffer << std::endl; \
delete[] buffer; \
} while (false)
#endif
int main()
{
int a = 3, b = 5;
LOG_PF("%i + %i = %i", a, b, a + b);
return EXIT_SUCCESS;
}
I have to define two macros although there is only one different line as within the macro I have not been able to incorporate the #ifdef _WIN32
part. In this case, while the Linux one works perfectly, the windows one also produces a segmentation fault.
The third approach uses a function with variable length input parameters instead of a macro and is based on this answer:
/* sprintf example */
#include <stdarg.h>
#include <stdio.h>
#include <iostream>
#ifdef _WIN32
#define custom_sprintf sprintf_s
#else
#define custom_sprintf sprintf
#endif
void log_pf(const char* format, ...)
{
va_list args;
va_start(args, format);
int result = vsnprintf(NULL, 0, format, args) + 1;
char* buffer = new char[result];
#ifdef _WIN32
custom_sprintf(buffer, result, format, args);
#else
custom_sprintf(buffer, format, args);
#endif
std::cout << buffer << std::endl;
va_end(args);
}
int main()
{
int a = 3, b = 5;
log_pf("%i + %i = %i", a, b, a + b);
return EXIT_SUCCESS;
}
This, code compiles in both cases, however, in Windows it produces a segmentation fault while in Ubuntu it does not crash but it is not outputting the desired message:
-1592530864 + -296641163
Can you help me fix the issues in either of the approaches so I can have the functionality described above? From my point of view, the best would be the function-like approach, especially from maintenance, but, as long as it works it does not actually matter.
Changing to boost::format
as explained here is not considered an option as this macro is used thousands of times in the library so it would require a lot of work.
The compilers being used are g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0 in Ubuntu and MSVC 19.25.28614.0 in Windows.
Upvotes: 0
Views: 128
Reputation: 217810
char buffer[neded];
is VLA extension, and so invalid C++.
You indeed have to use allocation (that you can wrap with std::string
or std::vector<char>
).
Then you might use snprintf
both to know the size and for formatting:
Notice that value returned by snprintf
doesn't include final nul char, so you have to add 1.
#define LOG_PF(...) \
do \
{ \
const int n = 1 + snprintf(nullptr, 0, __VA_ARGS__); \
std::vector<char> buffer(n); \
snprintf(buffer.data(), n, __VA_ARGS__); \
std::cout << buffer.data() << std::endl; \
} while (false)
Upvotes: 1