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::string
或 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)
在我从事的项目中,这是一个 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::string
或 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)