在跨平台应用程序中使用snprintf
我正在编写一个C程序,预计将与所有主要编译器一起编译。 目前我在Linux机器上开发GCC,并在提交代码之前在MSVC上编译。 为了使交叉编译变得容易,我正在使用-ansi
和-pedantic
标志进行编译。 这很有效,直到我开始使用c89标准中没有的snprintf
。 GCC可以在没有-ansi
开关的情况下编译它,但MSVC总是会失败,因为它没有C99支持。
所以我做了类似的事情,
#ifdef WIN32 #define snprintf sprintf_s #endif
这很有效,因为snprintf
和sprintf_s
具有相同的签名。 我想知道这是正确的做法吗?
不,你的方法注定要失败。
sqrt
和cos
具有相同的原型。 您是否认为可以在程序中交换它们并在更改之前/之后获得相同的行为?
你可能应该编写自己的snprintf
,或从互联网上下载一个实现( 谷歌是你的朋友 ),并在Linux和Windows中使用它。
我在使用_snprintf()
作为替代方法时发现了这一点 ,如果缓冲区溢出保护实际上触发了,则会涉及到问题。 从我能够快速浏览的内容来看,类似的警告适用于sprintf_s
。
你能看到问题吗? 在Linux版本中,输出始终以null结尾。 在MSVC中,事实并非如此。
更精细的是Linux中的
size
参数和MSVC中的count
参数之间的差异。 前者是输出缓冲区的大小,包括终止空值,后者是要存储的最大字符数,这排除了终止空值。
哦,不要忘记向Microsoft发送邮件,要求他们支持当前的语言标准。 (我知道他们已经宣布他们没有计划支持C99,但无论如何都要打扰他们。他们应得的。)
最重要的是,如果你想要真正安全地玩它,你必须为MSVC提供你自己的snprintf()
(围绕_snprintf()
或sprintf_s()
捕捉它们的非标准行为的包装器)。
如果你小心,你的建议可以奏效。 问题是这两个函数的行为略有不同,如果这对你来说不是问题,你就好了,否则考虑一个包装函数:
MSVC _snprintf
与官方C99(gcc,clang) snprintf
之间的差异:
返回值:
- MSVC:如果缓冲区大小不足以写入所有内容,则返回-1(不包括终止null!)
- GCC:如果缓冲区足够大,则返回已写入的字符数
写字节:
- MSVC:尽可能写入,如果没有剩余空间,不要在结尾写入NULL
- GCC:尽可能写,总是写终止NULL(exception:buffer_size = 0)
有趣的%n
微妙之处:如果你在代码中使用%n
,MSVC会将它保持单元化! 如果由于缓冲区大小很小而停止解析,GCC将始终写入如果缓冲区足够大就会写入的字节数。
所以我的建议是使用vsnprintf
/ _vsnprintf
编写自己的包装函数mysnprintf
,它给出相同的返回值并在两个平台上写入相同的字节(注意: %n
更难修复)。
您可以打开MSVC的NUL特殊文件并写入。 它总是会告诉你需要多少字节,并且不会写入任何内容。 像这样:
int main (int argc, char* argv[]) { FILE* outfile = fopen("nul", "wb"); int written; if(outfile == NULL) { fputs ("could not open 'nul'", stderr); } else { written = fprintf(outfile, "redirect to /dev/null"); fclose(outfile); fprintf(stdout, "didn't write %d characters", written); } return 0; }
然后,您应该知道要分配多少字节才能成功使用sprintf。
最完整的答案(如果你愿意,你可以改进),把它放到贴纸上
#if __PLATFORM_WIN_ZERO_STANDARD__ static inline int LIBSYS_SNPRINTF(char * str, size_t size, const char * format, ...) { int retval; va_list ap; va_start(ap, format); retval = _vsnprintf(str, size, format, ap); va_end(ap); return retval; } static inline int LIBSYS_VASPRINTF(char **ret, char * format, va_list ap) { int wanted = vsnprintf(*ret = NULL, 0, format, ap); if((wanted > 0) && ((*ret = LIBSYS_MALLOC(1 + wanted)) != NULL)) { return vsprintf(*ret, format, ap); } return wanted; } static inline int LIBSYS_ASPRINTF(char **ret, char * format, ...) { int retval; va_list ap; va_start(ap, format); retval = LIBSYS_VASPRINTF(ret, format, ap); va_end(ap); return retval; } #else #define LIBSYS_SNPRINTF snprintf #define LIBSYS_VASPRINTF vasprintf #define LIBSYS_ASPRINTF asprintf #endif