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

时间:2010-10-20 09:15:43

标签: c cross-platform compilation portability

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

所以我做了类似的事,

#ifdef WIN32 
#define snprintf sprintf_s
#endif

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

5 个答案:

答案 0 :(得分:16)

我发现this使用_snprintf()作为替代,并且如果缓冲区溢出保护实际触发,则涉及陷阱。从我能够快速浏览的内容来看,类似的警告适用于sprintf_s

  

你能看到问题吗?在Linux版本中,输出始终以空值终止。在MSVC中,它不是。

     

更加微妙的是Linux中的size参数与MSVC中的count参数之间的差异。前者是输出缓冲区的大小,包括终止空值,后者是要存储的最大字符数,排除了终止空值。

哦,不要忘记向Microsoft发送邮件,要求他们支持当前的语言标准。 (我知道他们已经宣布他们没有计划支持C99,但无论如何都要打扰他们。他们应该得到它。)

最重要的是,如果您想要真正安全地播放它,您必须提供自己的snprintf()_snprintf()sprintf_s()的包装,以捕捉其非标准行为)对于MSVC。

答案 1 :(得分:13)

如果你小心,你的建议可以奏效。问题是这两个函数的行为略有不同,如果这对你来说不是问题,你就好了,否则考虑一个包装函数:

MSVC _snprintf与官方C99(gcc,clang)snprintf之间的差异:

返回值:

  • MSVC:如果缓冲区大小不足以写入所有内容(不包括终止null!),则返回-1。
  • GCC:如果缓冲区足够大,则返回已写入的字符数

写入字节:

  • MSVC:尽可能多写,如果没有剩余空间,不要在结尾写入
  • GCC:尽可能多地写,总是写终止NULL(异常:buffer_size = 0)

有趣%n微妙: 如果您在代码中使用%n,MSVC会将其统一化!如果由于缓冲区大小很小而停止解析,GCC将始终写入如果缓冲区足够大就会写入的字节数。

所以我的提议是使用mysnprintf / vsnprintf编写自己的包装函数_vsnprintf,它提供相同的返回值,并在两个平台上写入相同的字节(注意:{{ 1}}更难修复。

答案 2 :(得分:1)

您可以打开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。

答案 3 :(得分:0)

最完整的答案(如果您愿意,可以改进),将其放入贴纸

#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

答案 4 :(得分:-8)

不。您的方法注定要失败。

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


您可能应该编写自己的snprintf,或者从Internet(google is your friend)下载实现,并在Linux和Windows中使用它们。