snprintf 的交叉编译版本在字符串缓冲区中分配适量的内存

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

在我从事的项目中,这是一个 Windows 和 Ubuntu 应用程序,我定义了一个与日志库交互的宏。本质上,这个宏接受类似 printf 的输入,转换为字符串,并将其发送到日志记录库。我附加了一个使用 std::cout 而不是日志库的宏示例:

#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;
}

custom_sprintf 部分是为了避免以下 windows 警告:

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.

除非您需要超过 500 个字符来分配正在记录的消息,否则此宏可以完美运行并完成其工作。如果内存不足,Windows 会产生段错误,而 Ubuntu 会打印所需的输出,但在运行时会出现编译警告和错误消息:

*** stack smashing detected ***: terminated

为了能够分配适当大小的缓冲区,我尝试了三种方法都没有成功。

在第一个中,我尝试将大小设置为 const size_t,这样我就可以分配一个大小合适的缓冲区:

#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;
}

此解决方案在 Ubuntu 上完美运行。但是,它不会在 windows 中编译说

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'

在第二个中,我尝试定义了两个不同的宏,这显然不利于维护:

#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;
}

我必须定义两个宏,尽管只有一行不同,因为在宏中我无法合并 #ifdef _WIN32 部分。在这种情况下,虽然 Linux 完美运行,但 windows 也会产生分段错误。

第三种方法使用具有可变长度输入参数的函数而不是宏,并且基于此 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;
}

这两种情况下的代码都可以编译,但是,在 Windows 中,它会产生分段错误,而在 Ubuntu 中,它不会崩溃,但不会输出所需的消息:

-1592530864 + -296641163

您能帮我解决这两种方法中的任何一种问题,以便我可以使用上述功能吗?从我的角度来看,最好的方法是类似函数的方法,尤其是从维护的角度来看,但是,只要它有效,实际上并不重要。

按照解释 here 更改为 boost::format 不被视为一个选项,因为此宏在库中使用了数千次,因此需要大量工作。

使用的编译器是Ubuntu中的g++(Ubuntu7.5.0-3ubuntu1~18.04)7.5.0和Windows中的MSVC 19.25.28614.0。

char buffer[neded]; 是 VLA 扩展,因此 C++ 无效。

你确实必须使用分配(你可以用 std::stringstd::vector<char> 包装)。

然后您可以使用 snprintf 来了解大小和格式: 请注意,snprintf 返回的值不包括最终的 nul 字符,因此您必须添加 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