snprintf和Visual Studio 2010

109

很不幸,我被迫使用VS 2010来完成一个项目,注意到以下代码仍无法在不符合标准的编译器下构建:

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    char buffer[512];

    snprintf(buffer, sizeof(buffer), "SomeString");

    return 0;
}

(编译失败,显示错误: C3861: 'snprintf':标识符未找到)

我记得这在VS 2005时就是这样的情况了,而且我很惊讶看到它仍然没有被修复。

有人知道Microsoft是否有计划将它们的标准C库移植到2010年吗?


1
或者你可以直接写成"#define snprintf _snprintf"。 - Fernando Gonzalez Sanchez
5
很遗憾_snprintf()不同于snprintf(),因为它不能保证空字符结尾。虽然你可以使用它,但需要注意这一点。 - Andy Krouwel
好的,所以在使用_snprintf()之前,您需要将其memset为零。我也同意你的看法。在MSVC下开发真是太糟糕了,错误信息也让人困惑不已。 - Owl
6个回答

96

简短故事: Microsoft终于在Visual Studio 2015中实现了snprintf。在早期版本中,您可以按照以下方式模拟它。


详细版本:

这里是snprintf的预期行为:

int snprintf( char* buffer, std::size_t buf_size, const char* format, ... );
写入至多 buf_size - 1 个字符到缓冲区中。结果字符串将以空字符结尾,除非 buf_size 等于零。如果 buf_size 等于零,则不会写入任何内容,buffer 可以是空指针。返回值是在假定无限制的 buf_size 的情况下可以被写入的字符数(不包括终止空字符)。
Visual Studio 2015之前的版本没有符合规范的实现。而是有一些非标准扩展,例如 _snprintf()(在溢出时不会写入空终止符)和 _snprintf_s()(可以强制执行空终止,但在溢出时返回-1而不是返回应该被写入的字符数)。建议在使用 VS 2005 或更高版本时使用后备方案。
#if defined(_MSC_VER) && _MSC_VER < 1900

#define snprintf c99_snprintf
#define vsnprintf c99_vsnprintf

__inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
{
    int count = -1;

    if (size != 0)
        count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
    if (count == -1)
        count = _vscprintf(format, ap);

    return count;
}

__inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...)
{
    int count;
    va_list ap;

    va_start(ap, format);
    count = c99_vsnprintf(outBuf, size, format, ap);
    va_end(ap);

    return count;
}

#endif

1
@Lothar: 缓冲区始终以空字符结尾。根据MSDN文档:“如果通过传递_TRUNCATE启用字符串截断,则这些函数将仅复制适合目标缓冲区的字符串,使目标缓冲区以空字符结尾,并成功返回”。 - Valentin Milea
3
截至2014年6月,即使在Visual Studio Update 2中也仍然没有完全支持C99。这篇博客介绍了MSVC 2013的C99支持概况。由于snprintf()函数现在已成为C++11标准的一部分,因此MSVC在C++11的实现方面落后于clang和gcc! - fnisi
2
使用VS2014,添加了C99标准的snprintf和vsnprintf功能。请参阅http://blogs.msdn.com/b/vcblog/archive/2014/06/18/crt-features-fixes-and-breaking-changes-in-visual-studio-14-ctp1.aspx。 - vulcan raven
1
Mikael Lepistö:真的吗?对我来说,只有在启用_CRT_SECURE_NO_WARNINGS时_snprintf才能正常工作。这种解决方法在没有这一步骤的情况下也可以很好地工作。 - FvD
在修复几个地方时发现了一个相关的问题 - MS在VS2005等中提供了vsnprintf(),但它不兼容!它只是_vsnprintf()的同义词,并在截断时返回-1,而不是所需的完整字符数。因此,这就像做#define snprintf _snprintf,但更糟糕,因为MS基本上已经为您完成了不良定义。因此,上面的代码可以正常工作,但不要试图采取捷径,使用MS版本的vsnprintf(),而不是上面列出的c99_vsnprintf()! - Andy Krouwel
显示剩余6条评论

34

snprintf不是C89的一部分,它只在C99中标准化。微软没有支持C99的计划(参见此处)。

(但它也是C++0x的标准...!)

查看下面的其他回答以获取解决方法。


5
然而,这不是一个好的解决方法,因为 snprintf 和 _snprintf 的行为存在差异。当处理不足缓冲区空间时,_snprintf 处理空终止符的方式非常糟糕。 - Andrew
7
@DeadMG的说法是错误的。cl.exe支持/Tc选项,可以将文件编译为C代码。此外,MSVC还提供了标准C库的版本。 - Andrew
3
@DeadMG - 但它确实支持C90标准和部分C99,使其成为一款C编译器。 - Andrew
15
只有你的出生年份在1990年到1999年之间,才符合条件。 - Puppy
6
微软的_snprintf是一个不安全的函数,它的行为与snprintf不同(它不一定会添加空终止符),因此这个答案中给出的建议是误导性和危险的。 - interjay
显示剩余5条评论

8
如果您不需要返回值,您也可以将snprintf定义为_snprintf_s。
#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)

3
我相信Windows的等效函数是。

7
sprintf_ssnprintf 的行为不同。 - interjay
具体来说,sprintf_s文档表示:“如果缓冲区对于要打印的文本过小,则该缓冲区将被设置为空字符串”。相比之下,snprintf会将截断的字符串写入输出。 - Andrew Bainbridge
2
@AndrewBainbridge -- 你截断了文档。完整句子是:“如果缓冲区对于正在打印的文本来说太小,则将缓冲区设置为空字符串并调用无效参数处理程序。” 无效参数处理程序的默认行为是终止您的程序。如果您想使用 _s 系列进行截断,则需要使用 snprintf_s 和 _TRUNCATE 标志。是的,不幸的是,_s 函数没有提供方便的截断方式。另一方面,_s 函数确实使用模板魔法来推断缓冲区大小,这是非常好的。 - Bruce Dawson

2

FFmpeg提供了另一个安全替代snprintf()vsnprintf()的方法。您可以在此处检查源代码(建议)。


1
我尝试了@Valentin Milea的代码,但出现了访问冲突错误。唯一对我有效的是Insane Coding的实现:http://asprintf.insanecoding.org/ 具体来说,我正在使用VC++2008遗留代码。从Insane Coding的实现中(可从上面的链接下载),我使用了三个文件:asprintf.casprintf.hvasprintf-msvc.c。其他文件适用于其他版本的MSVC。
[编辑]为了完整起见,它们的内容如下:

asprintf.h:

#ifndef INSANE_ASPRINTF_H
#define INSANE_ASPRINTF_H

#ifndef __cplusplus
#include <stdarg.h>
#else
#include <cstdarg>
extern "C"
{
#endif

#define insane_free(ptr) { free(ptr); ptr = 0; }

int vasprintf(char **strp, const char *fmt, va_list ap);
int asprintf(char **strp, const char *fmt, ...);

#ifdef __cplusplus
}
#endif

#endif

asprintf.c:

#include "asprintf.h"

int asprintf(char **strp, const char *fmt, ...)
{
  int r;
  va_list ap;
  va_start(ap, fmt);
  r = vasprintf(strp, fmt, ap);
  va_end(ap);
  return(r);
}

vasprintf-msvc.c:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include "asprintf.h"

int vasprintf(char **strp, const char *fmt, va_list ap)
{
  int r = -1, size = _vscprintf(fmt, ap);

  if ((size >= 0) && (size < INT_MAX))
  {
    *strp = (char *)malloc(size+1); //+1 for null
    if (*strp)
    {
      r = vsnprintf(*strp, size+1, fmt, ap);  //+1 for null
      if ((r < 0) || (r > size))
      {
        insane_free(*strp);
        r = -1;
      }
    }
  }
  else { *strp = 0; }

  return(r);
}

使用方法(由 Insane Coding 提供的 test.c 的一部分):

#include <stdio.h>
#include <stdlib.h>
#include "asprintf.h"

int main()
{
  char *s;
  if (asprintf(&s, "Hello, %d in hex padded to 8 digits is: %08x\n", 15, 15) != -1)
  {
    puts(s);
    insane_free(s);
  }
}

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