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, и я потрясен, увидев, что это до сих пор не исправлено.

Кто-нибудь знает, планирует ли Microsoft перенести свои стандартные библиотеки C в 2010 году?

Андрей
источник
1
... или вы можете просто сделать "#define snprintf _snprintf"
Фернандо Гонсалес Санчес
4
... вы могли бы, но, к сожалению, _snprintf () не то же самое, что snprintf (), поскольку он не гарантирует нулевое завершение.
Энди Кроувел
Хорошо, поэтому вам нужно будет установить его в ноль перед использованием _snprintf (). Тоже согласен с тобой. Разрабатывать под MSVC ужасно. Ошибки тоже чертовски сбивают с толку.
Сова

Ответы:

88

Краткая история: Microsoft наконец-то реализовала snprintf в Visual Studio 2015. В более ранних версиях вы можете смоделировать его, как показано ниже.


Длинная версия:

Вот ожидаемое поведение 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
Валентин Милеа
источник
Это не всегда завершает строку нулем, который требуется при переполнении. Второе if в c99_vsnprintf должно быть: if (count == -1) {if (size> 0) str [size-1] = 0; count = _vscprintf (формат, ap); }
Lothar
1
@Lothar: буфер всегда заканчивается нулем. Согласно MSDN: «если усечение строки разрешено путем передачи _TRUNCATE, эти функции будут копировать только ту часть строки, которая уместится, оставляя буфер назначения оканчивающимся нулем, и успешно возвращаются».
Валентин Милеа
2
По состоянию на июнь 2014 г. в Visual Studio по-прежнему отсутствует "полная" поддержка C99, даже с обновлением 2. В этом блоге содержится краткая информация о поддержке C99 для MSVC 2013. Поскольку функции семейства snprintf () теперь являются частью стандарта C ++ 11 , MSVC отстает от clang и gcc в реализации C ++ 11!
fnisi 03
2
В VS2014 добавлены стандарты C99 с snprintf и vsnprintf. См. Blogs.msdn.com/b/vcblog/archive/2014/06/18/… .
vulcan raven
1
Микаэль Лепистё: Правда? Для меня _snprintf работает, только если я включу _CRT_SECURE_NO_WARNINGS. Этот обходной путь отлично работает без этого шага.
FvD
33

snprintfне является частью C89. Стандартно только в C99. У Microsoft нет планов поддержки C99 .

(Но это также стандарт в C ++ 0x ...!)

См. Другие ответы ниже для обходного пути.

Kennytm
источник
5
Однако это не лучший обходной путь ... поскольку есть различия в поведении snprintf и _snprintf. _snprintf запаздывает с нулевым ограничителем при работе с недостаточным буферным пространством.
Эндрю
7
@DeadMG - неправильно. cl.exe поддерживает параметр / Tc, который указывает компилятору скомпилировать файл как код C. Кроме того, MSVC поставляется с версией стандартных библиотек C.
Эндрю
3
@DeadMG - однако он поддерживает стандарт C90, а также несколько битов C99, что делает его компилятором C.
Эндрю
15
Только если вы живете с 1990 по 1999 год.
Щенок
6
-1, Microsoft _snprintfявляется небезопасной функцией, которая ведет себя иначе snprintf(она не обязательно добавляет нулевой терминатор), поэтому совет, данный в этом ответе, вводит в заблуждение и опасен.
Interjay 04
8

Если вам не нужно возвращаемое значение, вы также можете просто определить snprintf как _snprintf_s

#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)
Стефан Штайгер
источник
3

Я считаю, что эквивалент Windows sprintf_s

Иль-Бхима
источник
7
sprintf_sведет себя иначе, чем snprintf.
Interjay 04
В частности, в документации sprintf_s говорится: «Если буфер слишком мал для печатаемого текста, тогда в буфере устанавливается пустая строка». Напротив, snprintf записывает на выходе усеченную строку.
Эндрю Бейнбридж
2
@AndrewBainbridge - вы урезали документацию. Полное предложение: «Если буфер слишком мал для печатаемого текста, тогда в буфере устанавливается пустая строка и вызывается обработчик недопустимого параметра». Поведение по умолчанию для недопустимого дескриптора параметра - завершить вашу программу. Если вы хотите усечение с семейством _s, вам нужно использовать snprintf_s и флаг _TRUNCATE. Да, очень жаль, что функции _s не предоставляют удобного способа усечения. С другой стороны, функции _s действительно используют магию шаблонов для определения размеров буфера, и это прекрасно.
Брюс Доусон
2

Еще одна безопасная замена snprintf()и vsnprintf()предоставляется ffmpeg. Вы можете проверить источник здесь (рекомендуется).

Марко Пракуччи
источник
1

Я пробовал код @Valentin Milea, но у меня есть ошибки нарушения доступа. Единственное, что у меня сработало, - это реализация Insane Coding: http://asprintf.insanecoding.org/

В частности, я работал с устаревшим кодом VC ++ 2008. От реализации безумной Coding ( в можно скачать по ссылке выше), я использовал три файла: asprintf.c, asprintf.hи vasprintf-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.cпредоставлена ​​Insane 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);
  }
}
Андертаварес
источник