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()(які не записують null-термінатор при переповненні) та _snprintf_s()(які можуть примусово виконувати null-завершення, але повертає -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 (count == -1) {if (size> 0) str [size-1] = 0; count = _vscprintf (формат, ап); }
Лотар

1
@Lothar: Буфер завжди недійсний. Відповідно до MSDN: "якщо увімкнення обрізання рядків шляхом передачі _TRUNCATE, ці функції скопіюють лише стільки рядка, скільки підходить, залишивши буфер призначення недійсним, і повернуться успішно".
Валентин Мілеа

2
Станом на червень 2014 року, у Visual Studio, навіть із оновленням 2, досі немає "повної" підтримки C99. Цей блог дає короткий опис підтримки C99 для MSVC 2013. Оскільки сімейні функції snprintf () тепер є частиною стандарту C ++ 11 , MSVC відстає від clang та gcc у впровадженні C ++ 11!
fnisi

2
З VS2014 додаються стандарти C99 із snprintf та vsnprintf. Див blogs.msdn.com/b/vcblog/archive/2014/06/18 / ... .
вулкан ворон

1
Мікаел Лепісто: Дійсно? Для мене _snprintf працює лише в тому випадку, якщо я ввімкнув _CRT_SECURE_NO_WARNINGS. Ця обробка працює без жодного кроку.
FvD

33

snprintfне входить до складу C89. Він стандартний лише у C99. Microsoft не планує підтримувати C99 .

(Але це також стандартно в C ++ 0x ...!)

Дивіться інші відповіді нижче для вирішення.


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

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

Зокрема, документи sprintf_s кажуть: "Якщо буфер занадто малий для тексту, який друкується, буфер встановлюється в порожній рядок". На противагу snprintf записує усічену рядок у висновок.
Ендрю Бейнбрідж

2
@AndrewBainbridge - ви врізали документацію. Повне речення: "Якщо буфер занадто малий для тексту, який друкується, то буфер встановлюється в порожній рядок і викликається недійсний обробник параметрів." Поведінка за замовчуванням для недійсної ручки параметра полягає у припиненні вашої програми. Якщо ви хочете усікання з сім'єю _s, тоді вам потрібно використовувати snprintf_s та прапор _TRUNCATE. Так, прикро, що функції _s не дають зручного способу обрізання. З іншого боку, функції _s дійсно використовують магію шаблонів, щоб визначити розміри буфера, і це чудово.
Брюс Доусон


1

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

Зокрема, я працював зі спадковим кодом VC ++ 2008. Від реалізації божевільної Coding ( в можна скачати за посиланням вище), я використовував три файли: asprintf.c, asprintf.hі vasprintf-msvc.c. Інші файли були для інших версій MSVC.

[EDIT] Для повноти їх вміст такий:

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);
  }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.