c++ - 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。
解决方案
char buffer[neded];
是 VLA 扩展,因此 C++ 无效。
你确实必须使用分配(你可以用std::string
or包装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)
推荐阅读
- javascript - chrome.webRequest.onBeforeRequest 导致网页部分功能加载失败
- amazon-web-services - 随机切割森林的超参数调整
- java - 黑白 ChromeDriver 和 WebDriver 有什么区别?
- python - 使用 raw_input 不能输入超过 1025 个字符
- kubernetes - 在定义集合时添加新闻行
- javascript - 基于相同形式的其他参数选择动态更改选择?
- javascript - Firefox 正则表达式的正则表达式
- html - SVG 元素有太多额外空间
- apache-spark - Spark SQL 2.1 是否支持将临时表或配置单元表写入 Mysql/Oracle?
- c - 我可以在返回类型指针的函数中返回变量的地址吗?