¿Snprintf() SIEMPRE termina en nulo?

11 minutos de lectura

avatar de usuario
Profesor Falken

¿Snprintf siempre es nulo al terminar el búfer de destino?

En otras palabras, ¿es esto suficiente?

char dst[10];

snprintf(dst, sizeof (dst), "blah %s", somestr);

o tienes que hacer así, si somestr es lo suficientemente largo?

char dst[10];

somestr[sizeof (dst) - 1] = '\0';
snprintf(dst, sizeof (dst) - 1, "blah %s", somestr);

Estoy interesado tanto en lo que dice el estándar como en lo que podría hacer alguna libc popular que no sea un comportamiento estándar.

  • ¿Quiere decir anular la terminación de somestr o dst en el segundo ejemplo?

    – Hudson

    23 de abril de 2013 a las 18:34

  • @chux, Martin Ba cubrió eso en la respuesta aceptada. 🙂

    – Profesor Falken

    06/10/2014 a las 14:53

  • @chux Creo que estuvo bien, tu comentario solo dejó muy claro que si dest i 0 long, no se escribe nada. Tomo cada comentario como una posible excusa para conversar con otros stackoverflowers. 🙂

    – Profesor Falken

    6 oct 2014 a las 16:03

  • @Profe. Falken De acuerdo en que el comentario estuvo bien y fue explícito, pero fue redundante con las respuestas, simplemente lo perdí en mi revisión.

    – chux – Reincorporar a Monica

    06/10/2014 a las 16:21

  • stackoverflow.com/a/8712996/193892 Visual Studio ahora admite snprintf()

    – Profesor Falken

    29 de abril de 2020 a las 13:19


avatar de usuario
Martín Ba

Como establecen las otras respuestas: deberían:

snprintf … Escribe los resultados en un búfer de cadena de caracteres. (…) terminará con un carácter nulo, a menos que buf_size sea cero.

Entonces, todo lo que debe tener cuidado es no pasarle un búfer de tamaño cero, porque (obviamente) no puede escribir un cero en “ninguna parte”.


Sin embargo, tener cuidado que la biblioteca de Microsoft no tiene una función llamada snprintf pero en vez históricamente solamente tenía una función llamada _snprintf (tenga en cuenta el guión bajo inicial) que no agrega un nulo de terminación. Aquí están los documentos (VS 2012, ~~ VS 2013):

http://msdn.microsoft.com/en-us/library/2ts7cx93%28v=vs.110%29.aspx

Valor de retorno

Sea len la longitud de la cadena de datos formateada (sin incluir el nulo final). len y count están en bytes para _snprintf, caracteres anchos para _snwprintf.

  • Si len < count, los caracteres len se almacenan en el búfer, se agrega un terminador nulo y se devuelve len.

  • Si len = contar, entonces los caracteres len se almacenan en el búfer, no se añade ningún terminador nulo y se devuelve len.

  • Si len > count, los caracteres de conteo se almacenan en el búfer, no se agrega ningún terminador nulo y se devuelve un valor negativo.

(…)

estudio visual 2015 (VC14) aparentemente introdujo la conformidad snprintf función, pero la heredada con el guión bajo inicial y el no el comportamiento de terminación nula todavía está allí:

los snprintf La función trunca la salida cuando len es mayor o igual que contar, colocando un terminador nulo en
buffer[count-1]. (…)

Para todas las funciones otro que snprintfsi len = recuento, los caracteres len se almacenan en el búfer, no se agrega ningún terminador nulo(…)

  • En nombre de Aslan, ¿en qué estaban pensando los ingenieros de Microsoft cuando presentaron _snprintf que en silencio elimina una característica clave de seguridad de snprintf y permite que la cadena no termine en nulo?!

    – Colin D. Bennett

    06/10/2014 a las 15:50


  • @ColinDBennett: es extraño y muy molesto y no tengo ni idea de si alguien pensó en absoluto 🙂

    – Martín Ba

    06/10/2014 a las 18:07

  • @MartinBa sí, lo siento, lo que probé fue template <size_t size> int _snprintf_s(char (&buffer)[size], size_t count, const char *format [, argument] ...); y también debo mencionar que esto sucede solo con el indicador de compilación /GS (Comprobación de seguridad). Esa función conoce el tamaño, el conteo y la longitud.

    – sekmet64

    17 de noviembre de 2014 a las 14:53

  • Tenga en cuenta que mingw64 usó (¿usa?) la implementación de microsoft _snprintf como snprintf “normal” a menos que se especifique lo contrario nvd.nist.gov/vuln/detail/CVE-2018-1000101

    – Domenukk

    19 de diciembre de 2018 a las 18:48

  • @Sajjon Es una exclamación de exasperación ciertamente tonta (y quizás totalmente original) ( idioms.thefreedictionary.com/en+el+nombre+de+Dios ) quizás ligeramente como un juramento picado ( en.wikipedia.org/wiki/Minced_oath ). Otro ejemplo podría ser “¡¿Qué, en el nombre de Zeus…?!” ( forum.wordreference.com/threads/en-el-nombre-de-zeus.2132965 )

    – Colin D. Bennett

    15 de abril de 2020 a las 16:09

avatar de usuario
piotr

Según la página de manual de snprintf(3).

Las funciones snprintf() y vsnprintf() escribir a lo sumo size bytes (incluido el byte nulo final (‘\0’)) a str.

Entonces, sí, no es necesario terminar si el tamaño es >= 1.

  • Y gracias a Dios por eso; este es el único diseño sensato. El objetivo de las versiones comprobadas de estas funciones es ser a salvoy sería terrible si tuvieras que hacer toda la tontería de terminación a mano.

    – KerrekSB

    9 oct 2011 a las 22:31

  • Recomendaría probarlo en la(s) plataforma(s) que está utilizando antes de confiar en esto. Incluso si deberían escriba el byte nulo, sé que me encontré con implementaciones que no lo hicieron (podría haber sido con MinGW, que usaba un tiempo de ejecución de MS más antiguo).

    – Dmitri

    10 de octubre de 2011 a las 0:33

avatar de usuario
jonathan leffler

Según el estándar C, a menos que el tamaño del búfer sea 0, vsnprintf() y snprintf() null termina su salida.

los snprintf() función será equivalente a sprintf(), con la adición del argumento n que indica el tamaño del búfer al que se refiere s. Si n es cero, no se escribirá nada y s puede ser un puntero nulo. De lo contrario, los bytes de salida más allá del n-1 se descartarán en lugar de escribirse en la matriz, y se escribirá un byte nulo al final de los bytes realmente escritos en la matriz.

Por lo tanto, si necesita saber qué tamaño de búfer asignar, use un tamaño de cero y luego puede usar un puntero nulo como destino. Tenga en cuenta que me vinculé a las páginas de POSIX, pero estas dicen explícitamente que no se pretende que haya ninguna divergencia entre el Estándar C y POSIX donde cubren el mismo terreno:

La funcionalidad descrita en esta página de referencia está alineada con el estándar ISO C. Cualquier conflicto entre los requisitos descritos aquí y el estándar ISO C no es intencional. Este volumen de POSIX.1-2008 difiere del estándar ISO C.

Tenga cuidado con la versión de Microsoft de vsnprintf(). Definitivamente se comporta de manera diferente a la versión C estándar cuando no hay suficiente espacio en el búfer (devuelve -1 donde la función estándar devuelve la longitud requerida). No está del todo claro que la versión nula de Microsoft finalice su salida en condiciones de error, mientras que la versión C estándar sí lo hace.

Tenga en cuenta también las respuestas a ¿Utiliza las funciones seguras TR 24731? (ver MSDN para la versión de Microsoft del vsprintf_s()) y la solución Mac para las alternativas seguras a las funciones de biblioteca estándar de C no seguras?

  • Oh, malvado, nunca pensé en eso. Por otro lado… 🙂

    – Profesor Falken

    9 oct 2011 a las 22:47

  • ah, creo que MS vsprintf() me mordió, y adquirí ese – 1 hábito

    – Profesor Falken

    9 oct 2011 a las 23:00

Algunas versiones anteriores de SunOS hicieron cosas extrañas con snprintf y es posible que no hayan terminado en NUL la salida y hayan tenido valores de retorno que no coincidían con lo que todos los demás estaban haciendo, pero cualquier cosa que se haya lanzado en los últimos 10 años ha estado haciendo lo que C99 dice.

avatar de usuario
robin kuzmin

La ambigüedad parte del propio estándar C. Tanto C99 como C11 tienen una descripción idéntica de snprintf función. Aquí está la descripción de C99:

7.19.6.5 El snprintf función
Sinopsis

1 #include <stdio.h>
int snprintf(char * restrict s, size_t n, const char * restrict format, ...);

Descripción

2 el snprintf función es equivalente a fprintfexcepto que la salida se escribe en una matriz (especificada por el argumento s) en lugar de a una secuencia. Si n es cero, no hay nada escrito, y s puede ser un puntero nulo. De lo contrario, los caracteres de salida más allá del n-1st se descartan en lugar de escribirse en la matriz, y se escribe un carácter nulo al final de los caracteres realmente escritos en la matriz. Si la copia tiene lugar entre objetos que se superponen, el comportamiento no está definido.
Devoluciones

3 el snprintf La función devuelve el número de caracteres que se habrían escrito si n sido lo suficientemente grande, sin contar el carácter nulo de terminación, o un valor negativo si se produjo un error de codificación. Por lo tanto, la salida terminada en nulo se ha escrito por completo si y solo si el valor devuelto no es negativo y es menor que n.

Por un lado la frase

De lo contrario, caracteres de salida más allá del n-1st se descartan en lugar de escribirse en la matriz, y se escribe un carácter nulo al final de los caracteres realmente escritos en la matriz

dice que
Si el s apunta a una matriz de 3 caracteres de largo, y) n es 3, entonces Se escribirán 2 caracteres, y los caracteres más allá del segundo se descartarán; entonces el el carácter nulo se escribe después de esos 2 (y el carácter nulo será el tercer carácter escrito).

Y esto creo que responde a la pregunta original.
LA RESPUESTA:

Si la copia tiene lugar entre objetos que se superponen, el comportamiento no está definido.
Si n es 0 entonces no se escribe nada en la salida
de lo contrario, si no se encuentran errores de codificación, la salida SIEMPRE termina en nulo (independientemente de si la salida cabe en la matriz de salida o no; si no, algunos caracteres se descartan de modo que la matriz de salida nunca se desborda),
de lo contrario (si se encuentran errores de codificación) la salida puede permanecer sin terminación nula.

Por otro lado

La última oración

Por lo tanto, la salida terminada en nulo se ha escrito por completo si y solo si el valor devuelto no es negativo y es menor que n

da ambigüedad (o mi inglés no es lo suficientemente bueno). Puedo interpretar esta frase al menos de dos maneras:
1. La salida es terminado en nulo si y solo si el valor devuelto no es negativo y menos que n (lo que significa que si el valor devuelto es no menos que nes decir, la salida (incluido el carácter nulo de terminación) no cabe en la matriz, entonces la salida no es nulo-terminado).
2. La salida es completo (no se han descartado caracteres) si y solo si el valor devuelto no es negativo y menos que n.


Creo que la interpretación 1 anterior contradice LA RESPUESTA, provoca malentendidos y largas discusiones. Es por eso que la última oración que describe el snprintf La función necesita un cambio para eliminar cualquier ambigüedad (lo que da motivos para escribir una propuesta para el estándar del lenguaje C).
El ejemplo de redacción no ambigua que creo puede tomarse de http://en.cppreference.com/w/c/io/fprintf (ver 4)), gracias a @”Martin Ba” por el enlace.

Consulte también la pregunta “snprintf: ¿Existen planes/propuestas estándar de C para cambiar la descripción de esta función?”.

  • Su interpretación 1 no me parece del todo plausible. Analizo esa oración como “La salida (que, por cierto, termina en nulo) se ha escrito completamente si …”, que solo puedo entender como # 2.

    – zwol

    25 de mayo de 2018 a las 4:27


  • La negación de la oración “la salida terminada en nulo ha sido completamente escrita” es “la salida terminada en nulo ha no escrito completamente”. Nada más. La oración negada por sí sola no implica que cualquier cosa se ha escrito (esto incluye salida incompleta terminada en nulo, salida incompleta no terminada en nulo o ideas verdes incoloras). Algún otro lugar en el estándar dice qué se escribe exactamente cuando la salida está incompleta, y ese lugar establece que la salida termina en nulo a menos que esté vacía (n == 0).

    – norte 1.8e9-dónde-está-mi-participación m.

    25 de mayo de 2018 a las 9:17

  • Su interpretación 1 no me parece del todo plausible. Analizo esa oración como “La salida (que, por cierto, termina en nulo) se ha escrito completamente si …”, que solo puedo entender como # 2.

    – zwol

    25 de mayo de 2018 a las 4:27


  • La negación de la oración “la salida terminada en nulo ha sido completamente escrita” es “la salida terminada en nulo ha no escrito completamente”. Nada más. La oración negada por sí sola no implica que cualquier cosa se ha escrito (esto incluye salida incompleta terminada en nulo, salida incompleta no terminada en nulo o ideas verdes incoloras). Algún otro lugar en el estándar dice qué se escribe exactamente cuando la salida está incompleta, y ese lugar establece que la salida termina en nulo a menos que esté vacía (n == 0).

    – norte 1.8e9-dónde-está-mi-participación m.

    25 de mayo de 2018 a las 9:17

¿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