在Visual Studio 2015中,snprintf的输出是否保证以空字符结尾?

3

我正在处理一个C代码库,我的前任使用了以下代码:

#ifdef _MSC_VER
  // Map to equivalent function
  #define snprintf sprintf_s
#endif

代码库需要在Linux(gcc/clang),OSX(gcc/clang)和Windows(VS)上编译。我第一次尝试在Visual Studio 2015中编译代码。以前使用的是Visual Studio 2010。我遇到了描述这里的错误,并能够使用已接受答案的线索进行编译:
#ifdef _MSC_VER
  #if _MSC_VER<1900
    #define snprintf sprintf_s
  #endif
#endif

该项目现在可在Visual Studio 2015和OSX上使用clang编译。但是,我对sprintf_s文档中的一条陈述感到担忧:

与snprintf不同,sprintf_s保证缓冲区将以null结尾(除非缓冲区大小为零)。

如果Visual Studio包含符合C99标准的snprintf版本,那么缓冲区不应该被保证是以null结尾吗?

我写了一个简单的程序来评估这种行为。

#include <stdio.h>
#include <string.h>

int main(int argc, char** argv) {
    char buf[5];
    snprintf(buf, sizeof(buf), "abcdef");
    printf("buffer: %s\n", buf);
    printf("size of buffer: %lu\n", strlen(buf));
    printf("last character a null terminator?: %s\n",
        (strcmp(&buf[4], "\0") == 0) ? "yes" : "no");
    return 0;
}

我在OSX和Windows上使用Visual Studio 2015构建和运行了该程序。

在OSX上,输出结果是:

~$c99 main.c
~$./a.out 
buffer: abcd
size of buffer: 4
last character a null terminator?: yes

在 Windows 7 上使用 Visual Studio 2015 的情况下,输出结果是

> printf_evaluation.exe
buffer: abcd
size of buffer: 4
last character a null terminator?: yes

这是否表明输出实际上是以null结尾的,并暗示MSDN文档是错误的?我的示例是否太简单了?在使用Visual Studio 2015时,snprintf的输出是否存在未以NULL结尾的情况?


我认为他们的意思是与 _snprintf 不同,_snprintf 本身并不以 null 终止。除了作为 sprintf_s 的宏之外,VS 根本不知道 snprintf。 - Ctx
我怀疑这是文档错误,而不是snprintf()在Visual Studio中实现的任何异常或错误。 - P.P
1
微软在20世纪80年代实现的snprintf与C99标准的实现不同,这就是所有这些问题的根源。我不知道Visual Studio运行时是否会更新到使用C99版本。 - M.M
微软从未努力遵守C99标准 - 你怎么能认为“Visual Studio正在包含符合C99标准的snprintf版本”? - M.M
建议编写一个小程序,将“过长”的缓冲区传递给snprintf()函数,然后使用调试器查看实际放置在结果缓冲区中的内容,以验证实际发生的情况。 - user3629249
哇,你的前任宏完全不是“等价”的。不仅需要使用 _snprintf_s() 而不是 sprintf_s() 来避免(人为)崩溃,而且返回值也会是错误的(直到 VS2015,只有 _scprintf() 无条件地返回“完整”长度)。 - Medinoc
1个回答

1

snprintf()只要第二个参数大于零,就会始终在缓冲区末尾添加空字符。所以是的,MSDN文档是错误的。

来自C11标准,snprintf():

snprintf函数类似于fprintf,但输出写入一个数组(由参数s指定),而不是流。 如果n为零,则不写任何内容,并且s可以是空指针。否则,超出第n-1个字符的输出将被丢弃,而实际写入数组中的字符末尾会写入空字符。如果在重叠对象之间进行复制,则行为是未定义的。

(强调是我的)。


3
MSDN文档将描述MS工具的行为,这可能与ISO C标准不同。据我所知,微软从未声称支持C99(更不用说C11了)。 - M.M
请参阅此博客:现在已经实现了snprintf和vsnprintf函数:C99的snprintf和vsnprintf函数已经被实现。 - RjOllos
2
我使用MSVC 9.0和15.0运行了这个示例。较早的版本没有snprintf,只有_snprintf。使用后者,两个编译器都会生成将5个字符写入字符串并且不终止的代码。然而,MSVC 15.0确实有snprintf,它的行为与OP所说的一样。MSVC关于_snprintf的文档说:“此函数不能保证NULL终止”,而该文档集合中没有提到snprintf。因此,是的,存在文档错误,并且两个版本的snprintf_snprintf的行为不同。 - Weather Vane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接