简体   繁体   中英

Cross compilation version of snprintf allocating the right amount of memory in the string buffer

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.

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)

Demo

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM