snprintf和Visual Studio 2010


102

不幸的是,我无法在项目中使用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就是这种情况,震惊地看到它仍然没有得到修复。

有谁知道微软是否有计划将其标准C库迁移到2010年?


1
...或者您可以只做“ #define snprintf _snprintf”
费尔南多·冈萨雷斯·桑切斯

4
...可以,但是不幸的是_snprintf()与snprintf()不同,因为它不能保证null终止。
安迪·克鲁维尔

好的,因此在使用_snprintf()之前,您需要将其设置为零。我也同意你的看法。在MSVC下进行开发非常糟糕。这些错误也使人困惑。
猫头鹰

Answers:


88

简短的故事: 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

这并不总是将字符串以0终止,这在溢出时是必需的。c99_vsnprintf中的第二个if必须为:if(count == -1){if(size> 0)str [size-1] = 0; count = _vscprintf(format,ap); }
Lothar 2014年

1
@Lothar:缓冲区始终为空终止。根据MSDN:“如果通过传递_TRUNCATE启用了字符串截断功能,则这些函数将仅复制适当数量的字符串,使目标缓冲区为空终止,并成功返回”。
Valentin Milea 2014年

2
截至2014年6月,即使使用Update 2,Visual Studio仍然没有“完全”的C99支持。此博客提供了MSVC 2013的C99支持简介。由于snprintf()系列功能现已成为C ++ 11标准的一部分,MSVC在C ++ 11实现中落后于clang和gcc!
fnisi 2014年

2
在VS2014中,添加了带有snprintf和vsnprintf的C99标准。参见blogs.msdn.com/b/vcblog/archive/2014/06/18/…
火神乌鸦

1
MikaelLepistö:真的吗?对我来说,_snprintf仅在启用_CRT_SECURE_NO_WARNINGS时有效。如果没有该步骤,此解决方法可以正常工作。
FvD

33

snprintf不属于C89。它仅在C99中是标准的。微软没有计划支持C99

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

解决方法请参见下面的其他答案。


5
但是,这不是一个很好的解决方法,因为snprintf和_snprintf行为存在差异。_snprintf在处理缓冲区空间不足时会延迟处理空终止符。
安德鲁(Andrew)2010年

7
@DeadMG-错误。cl.exe支持/ Tc选项,该选项指示编译器将文件编译为C代码。此外,MSVC附带了一个标准C库的版本。
安德鲁(Andrew)2010年

3
@DeadMG-但是,它确实支持C90标准以及C99的一些功能,使其成为C编译器。
安德鲁(Andrew)2010年

15
仅当您居住在1990年至1999
。–小狗

6
-1,Microsoft _snprintf是不安全的函数,其行为不同于snprintf(它不一定添加空终止符),因此此答案中给出的建议具有误导性和危险性。
interjay 2013年


3

我相信Windows等效 sprintf_s


7
sprintf_s的行为与有所不同snprintf
interjay 2013年

特别是sprintf_s文档说:“如果缓冲区对于要打印的文本而言太小,则将缓冲区设置为空字符串”。相反,snprintf将截断的字符串写入输出。
Andrew Bainbridge 2014年

2
@AndrewBainbridge-您已截断了文档。完整的句子是“如果缓冲区太小而无法打印文本,则将缓冲区设置为空字符串,并调用无效的参数处理程序。” 无效参数句柄的默认行为是终止程序。如果要使用_s系列进行截断,则需要使用snprintf_s和_TRUNCATE标志。是的,不幸的是_s函数没有提供方便的截断方式。另一方面,_s函数确实使用模板魔术来推断缓冲区大小,这非常好。
布鲁斯·道森


1

我尝试了@Valentin Milea的代码,但遇到访问冲突错误。唯一对我有用的是Insane Coding的实现:http : //asprintf.insanecoding.org/

具体来说,我正在使用VC ++ 2008旧版代码。从疯狂的编码的实现(可以从上面的链接下载),我使用了三个文件: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);
}

用法(test.cInsane Coding提供的一部分):

#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);
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.