snprintf e Visual Studio 2010


102

Sono abbastanza sfortunato da essere bloccato a utilizzare VS 2010 per un progetto e ho notato che il codice seguente non viene ancora compilato utilizzando il compilatore non conforme agli standard:

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

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

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

    return 0;
}

(fallisce la compilazione con l'errore: C3861: 'snprintf': identificatore non trovato)

Ricordo che questo era il caso di VS 2005 e sono scioccato nel vedere che non è stato ancora risolto.

Qualcuno sa se Microsoft ha in programma di spostare le proprie librerie C standard nell'anno 2010?


1
... oppure puoi semplicemente fare "#define snprintf _snprintf"
Fernando Gonzalez Sanchez

4
... potresti, ma sfortunatamente _snprintf () non è la stessa di snprintf () in quanto non garantisce la terminazione nulla.
Andy Krouwel

Ok quindi dovrai memorizzarlo a zero prima di usare _snprintf (). Anche io sono d'accordo con te. Sviluppare sotto MSVC è orribile. Anche gli errori confondono da morire.
Gufo

Risposte:


88

Breve storia: Microsoft ha finalmente implementato snprintf in Visual Studio 2015. Nelle versioni precedenti è possibile simularlo come di seguito.


Versione lunga:

Ecco il comportamento previsto per snprintf:

int snprintf( char* buffer, std::size_t buf_size, const char* format, ... );

Scrive al massimo i buf_size - 1caratteri su un buffer. La stringa di caratteri risultante terminerà con un carattere nullo, a meno che non buf_sizesia zero. Se buf_sizeè zero, non viene scritto nulla e bufferpuò essere un puntatore nullo. Il valore restituito è il numero di caratteri che sarebbero stati scritti assumendo un numero illimitato buf_size, senza contare il carattere null finale.

Le versioni precedenti a Visual Studio 2015 non avevano un'implementazione conforme. Ci sono invece estensioni non standard come _snprintf()(che non scrive null-terminator in overflow) e _snprintf_s()(che può imporre la terminazione null, ma restituisce -1 in overflow invece del numero di caratteri che sarebbero stati scritti).

Fallback suggerito per VS 2005 e versioni successive:

#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

Ciò non terminerà sempre la stringa con uno 0 che è richiesto in caso di overflow. Il secondo if in c99_vsnprintf deve essere: if (count == -1) {if (size> 0) str [size-1] = 0; count = _vscprintf (format, ap); }
Lothar

1
@Lothar: il buffer è sempre con terminazione null. Secondo MSDN: "se il troncamento della stringa è abilitato passando _TRUNCATE, queste funzioni copieranno solo la quantità di stringa che si adatterà, lasciando il buffer di destinazione con terminazione null e restituiranno correttamente".
Valentin Milea

2
A partire da giugno 2014, non è ancora disponibile il supporto "completo" per C99 in Visual Studio, anche con l'aggiornamento 2. Questo blog fornisce il brief di supporto per C99 per MSVC 2013. Poiché le funzioni della famiglia snprintf () ora fanno parte dello standard C ++ 11 , MSVC è in ritardo rispetto a clang e gcc nell'implementazione di C ++ 11!
fnisi

2
Con VS2014, vengono aggiunti gli standard C99 con snprintf e vsnprintf. Vedi blogs.msdn.com/b/vcblog/archive/2014/06/18/… .
corvo vulcaniano

1
Mikael Lepistö: Davvero? Per me _snprintf funziona solo se abilito _CRT_SECURE_NO_WARNINGS. Questa soluzione funziona correttamente senza questo passaggio.
FvD

33

snprintfnon fa parte di C89. È standard solo in C99. Microsoft non ha un piano che supporta C99 .

(Ma è anche standard in C ++ 0x ...!)

Vedere altre risposte di seguito per una soluzione alternativa.


5
Non è una buona soluzione alternativa, tuttavia ... poiché ci sono differenze nel comportamento di snprintf e _snprintf. _snprintf gestisce il terminatore null in modo ritardato quando si tratta di spazio nel buffer insufficiente.
Andrew

7
@DeadMG - sbagliato. cl.exe supporta l'opzione / Tc, che indica al compilatore di compilare un file come codice C. Inoltre, MSVC viene fornito con una versione delle librerie C standard.
Andrew

3
@DeadMG - supporta, tuttavia, lo standard C90 e alcuni bit di C99, rendendolo un compilatore C.
Andrew

15
Solo se vivi tra il 1990 e il 1999.
Cucciolo

6
-1, Microsoft _snprintfè una funzione non sicura che si comporta in modo diverso da snprintf(non aggiunge necessariamente un terminatore nullo), quindi il consiglio fornito in questa risposta è fuorviante e pericoloso.
Interjay

8

Se non hai bisogno del valore restituito, puoi anche definire snprintf come _snprintf_s

#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)

3

Credo che l'equivalente di Windows sia sprintf_s


7
sprintf_ssi comporta diversamente da snprintf.
Interjay

In particolare, i documenti sprintf_s dicono: "Se il buffer è troppo piccolo per il testo da stampare, allora il buffer è impostato su una stringa vuota". Al contrario, snprintf scrive una stringa troncata nell'output.
Andrew Bainbridge

2
@AndrewBainbridge - hai troncato la documentazione. La frase completa è "Se il buffer è troppo piccolo per il testo da stampare, il buffer viene impostato su una stringa vuota e viene richiamato il gestore di parametri non validi". Il comportamento predefinito per l'handle di parametro non valido è terminare il programma. Se vuoi il troncamento con la famiglia _s, devi usare snprintf_s e il flag _TRUNCATE. Sì, è un peccato che le funzioni _s non forniscano un modo conveniente per troncare. D'altra parte, le funzioni _s usano la magia dei modelli per dedurre le dimensioni del buffer, e questo è eccellente.
Bruce Dawson


1

Ho provato il codice di @Valentin Milea ma ho degli errori di violazione di accesso. L'unica cosa che ha funzionato per me è stata l'implementazione di Insane Coding: http://asprintf.insanecoding.org/

In particolare, stavo lavorando con il codice legacy di VC ++ 2008. Da Coding Insane di implementazione (può essere scaricato dal link qui sopra), ho usato tre file: asprintf.c, asprintf.he vasprintf-msvc.c. Altri file erano per altre versioni di MSVC.

[EDIT] Per completezza, i loro contenuti sono i seguenti:

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);
}

Utilizzo (parte di test.cfornito da 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);
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.