Determinar el tamaño del búfer sprintf: ¿cuál es el estándar?

11 minutos de lectura

avatar de usuario
Dominic Bou Samra

Al convertir un int así:

char a[256];
sprintf(a, "%d", 132);

¿Cuál es la mejor manera de determinar qué tan grande a ¿debiera ser? Supongo que configurarlo manualmente está bien (como lo he visto en todas partes), pero ¿qué tan grande debería ser? ¿Cuál es el valor int más grande posible en un sistema de 32 bits? ¿Hay alguna forma complicada de determinarlo sobre la marcha?

  • Por supuesto, si usar C++ es una opción, entonces puede usar std::string y std::stringstream para lograr lo que desea sin siquiera pensar en los requisitos de memoria. Pero eso realmente depende. Sé que la pregunta es para C, pero tal vez esto pueda ser útil de todos modos.

    – Roberto Massaioli

    13 de octubre de 2010 a las 0:33

  • @Robert: si usar Python es una opción, entonces puedes usar str ;-pags

    –Steve Jessop

    13 de octubre de 2010 a las 0:35


  • @Robert: Es para una tarea de la universidad. Odiaba a C antes de esta unidad. Ahora me encanta la sencillez. Es implacable, pero muy satisfactorio, con una gran curva de aprendizaje dado que suelo incursionar en Python/lenguajes administrados.

    – Dominic Bou Samra

    13 de octubre de 2010 a las 1:11

  • @Robert: Yo diría que si tiene que pensar en este tipo de problema, ningún otro lenguaje que no sea C o ensamblaje podría cumplir con sus requisitos. Cualquier otro idioma tendrá un uso de pila monstruoso y difícil de predecir, fragmentación de montón, etc.

    – R.. GitHub DEJA DE AYUDAR A ICE

    13 de octubre de 2010 a las 2:19

  • En el mundo GNU tienes asprintfque internamente malloc la cantidad necesaria de memoria.

    – cielo utópico

    01/04/2013 a las 20:39

avatar de usuario
Daniel Standage

Algunos aquí argumentan que este enfoque es excesivo, y para convertir enteros en cadenas, podría estar más inclinado a estar de acuerdo. Pero cuando no se puede encontrar un límite razonable para el tamaño de la cadena, he visto que se usa este enfoque y lo he usado yo mismo.

int size = snprintf(NULL, 0, "%d", 132);
char * a = malloc(size + 1);
sprintf(a, "%d", 132);

Voy a desglosar lo que está pasando aquí.

  1. En la primera línea, queremos determinar cuántos caracteres necesitamos. Los 2 primeros argumentos para snprintf dile que quiero escribir 0 caracteres del resultado para NULL. Cuando hacemos esto, snprintf en realidad no escribirá ningún carácter en ninguna parte, simplemente devolverá la cantidad de caracteres que se habrían escrito. Esto es lo que queríamos.
  2. En la segunda línea, estamos asignando memoria dinámicamente a un char puntero. Asegúrese de agregar 1 al tamaño requerido (para el final \0 carácter de terminación).
  3. Ahora que hay suficiente memoria asignada al char puntero, podemos usar con seguridad sprintf para escribir el entero a la char puntero.

Por supuesto, puede hacerlo más conciso si lo desea.

char * a = malloc(snprintf(NULL, 0, "%d", 132) + 1);
sprintf(a, "%d", 132);

A menos que este sea un programa “rápido y sucio”, siempre querrá asegurarse de liberar la memoria con la que llamó malloc. Aquí es donde el enfoque dinámico se complica con C. Sin embargo, en mi humilde opinión, si no desea asignar grandes char punteros cuando la mayor parte del tiempo solo usará una porción muy pequeña de ellos, entonces no creo que este sea un mal enfoque.

  • En realidad, a menudo podría simplemente usar alloca en lugar de malloc. Y si el código resultante sigue siendo demasiado voluminoso, cree una macro para él.

    – el jh

    19 de agosto de 2013 a las 14:31

  • ¿Qué tan portátil es? alloca? Ciertamente no es ANSI C.

    – Daniel Standage

    19 de agosto de 2013 a las 19:24

  • ¿Seguramente la alternativa portátil C99 a alloca es solo usar una matriz de longitud variable? int size = ...; char a[size+1]; sprintf(...?

    – tommy

    19/04/2016 a las 12:50


  • No olvide manejar el caso en el que malloc() devuelve NULL, a menos que no le importe si su programa falla o es inseguro. Esto es especialmente cierto si el tamaño depende de la entrada desde fuera del programa.

    –Jeff Learman

    14 de agosto de 2017 a las 20:28

  • Definitivamente +1, no entiendo por qué esta no es la respuesta aceptada. Es exactamente lo que explica la página del manual.

    – Marco Bonelli

    18 de enero de 2019 a las 15:33

avatar de usuario
Regis Portalez

Es posible hacer que la solución de Daniel Standage funcione para cualquier cantidad de argumentos usando vsnprintf que está en C++ 11/C99.

int bufferSize(const char* format, ...) {
    va_list args;
    va_start(args, format);
    int result = vsnprintf(NULL, 0, format, args);
    va_end(args);
    return result + 1; // safe byte for \0
}

Como se especifica en estándar c99sección 7.19.6.12 :

La función vsnprintf devuelve el número de caracteres que se habrían escrito si n hubiera sido lo suficientemente grande, sin contar el carácter nulo final, o un valor negativo si se hubiera producido un error de codificación.

  • Es puro C99 a.

    – fshp

    19 de febrero de 2017 a las 23:55


  • Gracias. Ni siquiera me di cuenta. Actualicé mi respuesta.

    -Regis Portalez

    20 de febrero de 2017 a las 7:06


  • _vscprintf es una mejor solución. Mire – stackoverflow.com/a/9369242/987850

    – 23W

    5 de julio de 2017 a las 9:10


  • _vscprintf es solo de Microsoft.

    -Regis Portalez

    5 de julio de 2017 a las 9:18

avatar de usuario
steve jesop

El número máximo posible de bits en un int es CHAR_BIT * sizeof(int)y un dígito decimal “vale” al menos 3 bits, por lo que un límite superior suelto en el espacio requerido para un arbitrario int es (CHAR_BIT * sizeof(int) / 3) + 3. Ese +3 es uno por el hecho de que redondeamos hacia abajo al dividir, uno por el signo, uno por el terminador nulo.

Si por “en un sistema de 32 bits” quiere decir que sabe int es de 32 bits, entonces necesita 12 bytes. 10 para los dígitos, uno para el signo, uno para el terminador nulo.

En su caso específico, donde el int a convertir es 132, necesitas 4 bytes. Badum, tish.

Donde los búferes de tamaño fijo se pueden usar con un límite razonable, son la opción más simple. No sostengo tan humildemente que el límite anterior es razonable (13 bytes en lugar de 12 para 32 bits inty 23 bytes en lugar de 21 para 64 bits int). Pero para casos difíciles, en C99 podría simplemente llamar snprintf para obtener el tamaño, entonces malloc tanto Eso es excesivo para un caso tan simple como este.

  • Utilizando malloc porque esto es ridículo. Complica demasiado su código al agregar un caso de falla que debe verificar, ¿y qué hace si falla? Simplemente use un búfer del tamaño correcto como explicó cómo hacerlo.

    – R.. GitHub DEJA DE AYUDAR A ICE

    13 de octubre de 2010 a las 2:14

  • @R: Usar malloc no es ridículo si la persona que escribe el código está interesada en aprender o conocer los fundamentos del lenguaje en sí. Decirle a la gente que simplemente use std::string porque todo el trabajo duro que ya se ha hecho es ignorante hasta este punto; es decir, querer saber cómo funcionan las cosas debajo del capó, por así decirlo. ¿Tal vez el cartel original quiere saber cómo std::string hace lo que hace?

    –Eric

    28 de junio de 2011 a las 1:41

  • @Eric: esta pregunta es sobre C, nada que ver con std::string.

    –Steve Jessop

    28 de junio de 2011 a las 9:28


  • Esto es peligroso: un escenario potencial clásico de desbordamiento del búfer. La salida exacta del printf familia de funciones depende de la configuración regional. Por ejemplo, una configuración regional puede establecer el separador de ‘miles’.

    – Brett Hale

    26 de julio de 2014 a las 5:26

  • @Brett: me pusiste en marcha por un minuto allí, pero por supuesto %d no usa el separador de miles. %'d lo haría, pero esa no es la cuestión.

    –Steve Jessop

    26 de julio de 2014 a las 8:54


avatar de usuario
Espero eso ayude

Veo que esta conversación tiene un par de años, pero la encontré mientras intentaba encontrar una respuesta para MS VC++ donde no se puede usar snprintf para encontrar el tamaño. Publicaré la respuesta que finalmente encontré en caso de que ayude a alguien más:

VC++ tiene la función _scprintf específicamente para encontrar el número de caracteres necesarios.

avatar de usuario
pegaso épsilon

Si está imprimiendo un número entero simple y nada más, hay una manera mucho más simple de determinar el tamaño del búfer de salida. Al menos computacionalmente más simple, el código es un poco obtuso:

char *text;
text = malloc(val ? (int)log10((double)abs(val)) + (val < 0) + 2 : 2);

log10(valor) devuelve la cantidad de dígitos (menos uno) necesarios para almacenar un valor positivo distinto de cero en base 10. Se sale un poco de los rieles para números menores que uno, por lo que especificamos abs() y codificamos una lógica especial para cero (el operador ternario, test ? truecase : falsecase). Agregue uno para el espacio para almacenar el signo de un número negativo (val < 0), uno para compensar la diferencia de log10 y otro para el terminador nulo (para un total general de 2), y acaba de calcular la cantidad exacta de espacio de almacenamiento requerido para un número dado, sin llamar a snprintf() o equivalentes dos veces para hacer el trabajo. Además, generalmente usa menos memoria de la que requerirá INT_MAX.

Sin embargo, si necesita imprimir muchos números muy rápidamente, moleste en asignar el búfer INT_MAX y luego imprima en él repetidamente. Menos memoria thrashing es mejor.

También tenga en cuenta que es posible que en realidad no necesite el (doble) en lugar de un (flotante). No me molesté en comprobar. Lanzar de un lado a otro de esa manera también puede ser un problema. YMMV en todo eso. Sin embargo, funciona muy bien para mí.

  • Digamos que su número entero es INT_MIN. Luego abs(val) es INT_MIN, log10 aplicado a él devuelve NaN, y convertir NaN a int es un comportamiento indefinido. Además, si está imprimiendo números enteros de 64 bits, los más grandes no se pueden representar exactamente como double.

    – Pascal Cuoq

    13 de junio de 2014 a las 17:08

  • @Pascal Cuoq: peculiaridad interesante, gracias por eso. Supongo que la solución no está realmente completa sin verificar si estamos tratando de asignar almacenamiento para la representación de cadena de INT_MIN, pero me siento perezoso, así que lo dejaré como un ejercicio para cualquiera que realmente quiera hacerlo. . — PS “comportamiento indefinido” es un eufemismo. En GCC, printf(“%d\n”, (int)log10((doble)abs(-2147483647 – 1))); escupe “-2147483648” en lugar del “9” esperado, qué extraño.

    – Pegaso épsilon

    16 de enero de 2015 a las 14:02


avatar de usuario
EboMike

En primer lugar, sprintf es el diablo. En todo caso, use snprintf, o corre el riesgo de destrozar la memoria y bloquear su aplicación.

En cuanto al tamaño del búfer, es como todos los demás búfer: tan pequeño como sea posible, tan grande como sea necesario. En su caso, tiene un entero con signo, así que tome el tamaño más grande posible y siéntase libre de agregar un poco de relleno de seguridad. No hay un “tamaño estándar”.

Tampoco depende del sistema en el que se esté ejecutando. Si define el búfer en la pila (como en su ejemplo), depende del tamaño de la pila. Si usted mismo creó el hilo, entonces usted mismo determinó el tamaño de la pila, por lo que conoce los límites. Si va a esperar recursividad o un seguimiento de pila profunda, también debe tener mucho cuidado.

  • Digamos que su número entero es INT_MIN. Luego abs(val) es INT_MIN, log10 aplicado a él devuelve NaN, y convertir NaN a int es un comportamiento indefinido. Además, si está imprimiendo números enteros de 64 bits, los más grandes no se pueden representar exactamente como double.

    – Pascal Cuoq

    13 de junio de 2014 a las 17:08

  • @Pascal Cuoq: peculiaridad interesante, gracias por eso. Supongo que la solución no está realmente completa sin verificar si estamos tratando de asignar almacenamiento para la representación de cadena de INT_MIN, pero me siento perezoso, así que lo dejaré como un ejercicio para cualquiera que realmente quiera hacerlo. . — PS “comportamiento indefinido” es un eufemismo. En GCC, printf(“%d\n”, (int)log10((doble)abs(-2147483647 – 1))); escupe “-2147483648” en lugar del “9” esperado, qué extraño.

    – Pegaso épsilon

    16 de enero de 2015 a las 14:02


avatar de usuario
Arun

Es bueno que esté preocupado por el tamaño del búfer. Para aplicar ese pensamiento en el código, usaría snprintf

snprintf( a, 256, "%d", 132 );

o

snprintf( a, sizeof( a ), "%d", 132 );  // when a is array, not pointer

  • snprintf solo resuelve la mitad del problema. Si no está seguro de que su búfer sea lo suficientemente grande, debe usar snprintf para probar el tamaño necesario o asegurarse de que su código no tenga errores cuando la salida se trunca. La respuesta de Steve es mucho mejor.

    – R.. GitHub DEJA DE AYUDAR A ICE

    13 de octubre de 2010 a las 2:15

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad