首页 > 解决方案 > snprintf 的交叉编译版本在字符串缓冲区中分配适量的内存

问题描述

在我从事的项目(一个 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为了避免以下窗口警告:

C4996:'sprintf':此函数或变量可能不安全。考虑改用 sprintf_s。要禁用弃用,请使用 _CRT_SECURE_NO_WARNINGS。详细信息请参见在线帮助。

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

***检测到堆栈粉碎***:终止

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

在第一个中,我尝试获取大小,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 中编译说

错误 C2131 表达式未计算为常量

错误 C2664 'int sprintf_s(char *const ,const size_t,const char *const,...)':无法将参数 2 从 'const char [13]' 转换为 '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 也会产生分段错误。

第三种方法使用具有可变长度输入参数的函数而不是宏,并且基于此答案

/* 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

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

不考虑更改为此处boost::format解释的选项,因为此宏在库中使用了数千次,因此需要大量工作。

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

标签: c++stringubuntuwindow

解决方案


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

你确实必须使用分配(你可以用std::stringor包装std::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)

演示


推荐阅读