在跨平台应用程序中使用snprintf

我正在编写一个C程序,预计将与所有主要编译器一起编译。 目前我在Linux机器上开发GCC,并在提交代码之前在MSVC上编译。 为了使交叉编译变得容易,我正在使用-ansi-pedantic标志进行编译。 这很有效,直到我开始使用c89标准中没有的snprintf 。 GCC可以在没有-ansi开关的情况下编译它,但MSVC总是会失败,因为它没有C99支持。

所以我做了类似的事情,

 #ifdef WIN32 #define snprintf sprintf_s #endif 

这很有效,因为snprintfsprintf_s具有相同的签名。 我想知道这是正确的做法吗?

不,你的方法注定要失败。

sqrtcos具有相同的原型。 您是否认为可以在程序中交换它们并在更改之前/之后获得相同的行为?


你可能应该编写自己的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