snprintf y Visual Studio 2010

102

Soy lo suficientemente desafortunado como para quedarme atascado usando VS 2010 para un proyecto, y noté que el siguiente código aún no se compila con el compilador que no cumple con los estándares:

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

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

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

    return 0;
}

(falla la compilación con el error: C3861: 'snprintf': identificador no encontrado)

Recuerdo que este es el caso de VS 2005 y me sorprende ver que aún no se ha solucionado.

¿Alguien sabe si Microsoft tiene planes de trasladar sus bibliotecas C estándar al año 2010?

Andrés
fuente
1
... o simplemente puede hacer "#define snprintf _snprintf"
Fernando Gonzalez Sanchez
4
... podría, pero desafortunadamente _snprintf () no es lo mismo que snprintf () ya que no garantiza la terminación nula.
Andy Krouwel
Ok, necesitará ponerlo en cero antes de usar _snprintf (). También estoy de acuerdo contigo. Desarrollar bajo MSVC es terrible. Los errores también son muy confusos.
Búho

Respuestas:

88

Breve historia: Microsoft finalmente implementó snprintf en Visual Studio 2015. En versiones anteriores, puede simularlo como se muestra a continuación.


Versión larga:

Aquí está el comportamiento esperado para snprintf:

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

Escribe como máximo buf_size - 1caracteres en un búfer. La cadena de caracteres resultante terminará con un carácter nulo, a menos que buf_sizesea ​​cero. Si buf_sizees cero, no se escribe nada y bufferpuede ser un puntero nulo. El valor de retorno es el número de caracteres que se habrían escrito asumiendo ilimitados buf_size, sin contar el carácter nulo de terminación.

Las versiones anteriores a Visual Studio 2015 no tenían una implementación conforme. En cambio, hay extensiones no estándar como _snprintf()(que no escribe un terminador nulo en el desbordamiento) y _snprintf_s()(que puede imponer la terminación nula, pero devuelve -1 en el desbordamiento en lugar del número de caracteres que se habrían escrito).

Respaldo sugerido para VS 2005 y posteriores:

#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
Valentin Milea
fuente
Esto no siempre terminará la cadena con un 0 que se requiere en un desbordamiento. El segundo if en c99_vsnprintf debe ser: if (count == -1) {if (size> 0) str [size-1] = 0; count = _vscprintf (formato, ap); }
Lothar
1
@Lothar: El búfer siempre termina en nulo. De acuerdo con MSDN: "si se habilita el truncamiento de cadena pasando _TRUNCATE, estas funciones copiarán solo la cantidad de cadena que quepa, dejando el búfer de destino terminado en nulo y regresando correctamente".
Valentin Milea
2
A partir de junio de 2014, todavía no hay soporte "completo" de C99 en Visual Studio, incluso con la Actualización 2. Este blog brinda el resumen de soporte de C99 para MSVC 2013. Como las funciones de la familia snprintf () ahora son parte del estándar C ++ 11 , MSVC se queda atrás de clang y gcc en la implementación de C ++ 11.
Fnisi
2
Con VS2014, se agregan los estándares C99 con snprintf y vsnprintf. Consulte blogs.msdn.com/b/vcblog/archive/2014/06/18/… .
cuervo vulcano
1
Mikael Lepistö: ¿De verdad? Para mí, _snprintf solo funciona si habilito _CRT_SECURE_NO_WARNINGS. Esta solución alternativa funciona bien sin ese paso.
FvD
33

snprintfno es parte de C89. Es estándar solo en C99. Microsoft no tiene ningún plan que admita C99 .

(¡Pero también es estándar en C ++ 0x ...!)

Consulte otras respuestas a continuación para obtener una solución.

Kennytm
fuente
5
Sin embargo, no es una buena solución ... ya que existen diferencias en el comportamiento de snprintf y _snprintf. _snprintf maneja el terminador nulo con retraso cuando se trata de espacio de búfer insuficiente.
Andrew
7
@DeadMG - mal. cl.exe admite la opción / Tc, que indica al compilador que compile un archivo como código C. Además, MSVC se envía con una versión de las bibliotecas C estándar.
Andrew
3
@DeadMG: sin embargo, es compatible con el estándar C90 y con algunos bits de C99, lo que lo convierte en un compilador de C.
Andrew
15
Solo si vives entre 1990 y 1999.
Puppy
6
-1, Microsoft _snprintfes una función insegura que se comporta de manera diferente a snprintf(no necesariamente agrega un terminador nulo), por lo que el consejo dado en esta respuesta es engañoso y peligroso.
Interjay
8

Si no necesita el valor de retorno, también puede definir snprintf como _snprintf_s

#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)
Stefan Steiger
fuente
3

Creo que el equivalente de Windows es sprintf_s

Il-Bhima
fuente
7
sprintf_sse comporta de manera diferente a snprintf.
Interjay
Específicamente, los documentos de sprintf_s dicen: "Si el búfer es demasiado pequeño para el texto que se está imprimiendo, entonces el búfer se establece en una cadena vacía". Por el contrario, snprintf escribe una cadena truncada en la salida.
Andrew Bainbridge
2
@AndrewBainbridge: ha truncado la documentación. La oración completa es "Si el búfer es demasiado pequeño para el texto que se imprime, entonces el búfer se establece en una cadena vacía y se invoca el controlador de parámetros no válido". El comportamiento predeterminado para el identificador de parámetro no válido es terminar su programa. Si desea truncamiento con la familia _s, debe usar snprintf_s y el indicador _TRUNCATE. Sí, es lamentable que las funciones _s no ofrezcan una forma conveniente de truncar. Por otro lado, las funciones _s usan la magia de plantilla para inferir tamaños de búfer, y eso es excelente.
Bruce Dawson
2

Otro reemplazo seguro de snprintf()y vsnprintf()es proporcionado por ffmpeg. Puede consultar la fuente aquí (sugerido).

Marco Pracucci
fuente
1

Probé el código de @Valentin Milea pero tengo errores de violación de acceso. Lo único que funcionó para mí fue la implementación de Insane Coding: http://asprintf.insanecoding.org/

Específicamente, estaba trabajando con el código heredado de VC ++ 2008. De insana de Codificación de aplicación (se puede descargar desde el siguiente enlace), que utiliza tres archivos: asprintf.c, asprintf.hy vasprintf-msvc.c. Otros archivos eran para otras versiones de MSVC.

[EDITAR] Para completar, su contenido es el siguiente:

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

Uso (parte de test.cproporcionado por 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);
  }
}
andertavares
fuente