¿Snprintf () SIEMPRE termina en nulo?

82

¿Snprintf siempre es nulo terminando 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 alguna es lo suficientemente larga?

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 es un comportamiento estándar.

Prof. Falken
fuente
¿Quiere anular la terminación de somestr o dst en el segundo ejemplo?
Hudson
@chux, Martin Ba cubrió eso en la respuesta aceptada. :)
Prof. Falken
@chux Creo que estuvo bien, tu comentario acaba de dejar muy claro que si dest i 0 es largo, no se escribe nada. Tomo cada comentario como una posible excusa para charlar con otros compañeros de stackoverflowers. :)
Prof. Falken
@Profe. Falken está 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 - Restablecer a Monica
stackoverflow.com/a/8712996/193892 Visual Studio ahora es compatible con snprintf ()
Prof. Falken

Respuestas:

71

Como establecen las otras respuestas: Debería :

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.

Así que todo lo que tienes que tener cuidado es no pasarle un búfer de tamaño cero, porque (obviamente) no puede escribir un cero en "ninguna parte".


Sin embargo, tenga en cuenta que la biblioteca de Microsoft no tiene una función llamada, snprintfsino que históricamente solo tenía una función llamada _snprintf(observe el subrayado inicial) que no agrega un nulo final. Aquí están los documentos (VS 2012, ~~ VS 2013):

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

Valor devuelto

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

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

  • Si len = count, los caracteres len se almacenan en el búfer, no se agrega ningún terminador nulo y se devuelve len.

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

(...)

Visual Studio 2015 (VC14) aparentemente introdujo la snprintffunción de conformidad , pero la heredada con el guión bajo inicial y el comportamiento de terminación no nula todavía está ahí:

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

Para todas las funciones que no sean snprintf, si len = count, los caracteres len se almacenan en el búfer, no se agrega ningún terminador nulo , (...)

Martin Ba
fuente
22
En nombre de Aslan, ¿en qué estaban pensando los ingenieros de Microsoft cuando introdujeron _snprintfque eliminasnprintf silenciosamente una característica de seguridad clave y permite que la cadena no tenga terminación nula?
Colin D Bennett
2
@ColinDBennett: es extraño y muy molesto y no tengo ni idea si alguien pensó en absoluto :-)
Martin Ba
2
@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, la cuenta y la longitud.
sekmet64
3
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
2
@Sajjon Es una exclamación de exasperación ciertamente tonta (y quizás totalmente original) ( idioms.thefreedictionary.com/in+the+name+of+God ) tal vez un poco 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/in-the-name-of-zeus.2132965 )
Colin D Bennett
19

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

Las funciones snprintf()y vsnprintf()escriben como máximo sizebytes (incluido el byte nulo final ('\ 0')) en str.

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

piotr
fuente
3
Y gracias a Dios por eso; este es el único diseño sensato. El objetivo de las versiones comprobadas de estas funciones es ser seguras , y sería terrible si tuviera que hacer todo el malarkey de terminación a mano.
Kerrek SB
1
Recomendaría probarlo en la (s) plataforma (s) que está utilizando antes de confiar en esto. Incluso si debería escribir el byte nulo, sé que me he encontrado con implementaciones que no lo hicieron (podría haber sido con MinGW, que usaba un tiempo de ejecución de MS anterior).
Dmitri
10

De acuerdo con el estándar C, a menos que el tamaño del búfer sea 0 vsnprintf()y snprintf()null termine su salida.

La 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-1er se descartarán en lugar de escribirse en la matriz, y se escribirá un byte nulo al final de los bytes escritos realmente 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 POSIX, pero estas dicen explícitamente que no se pretende que haya ninguna divergencia entre Standard 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 la norma 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 lo hace.

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

Jonathan Leffler
fuente
oh, malvado, nunca pensé en eso. Por otro lado ... :)
Prof. Falken
ah, creo que MS vsprintf () me mordió, y adquirí ese - 1 hábito
Prof. Falken
4

Algunas versiones anteriores de SunOS hicieron cosas raras con snprintf y es posible que no hayan terminado con NUL la salida y tengan valores de retorno que no coincidan 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.

Arte
fuente
Noto que XP se lanzó hace poco más de 10 años. :-)
Prof. Falken
Y este año quedó obsoleto. :)
Prof. Falken
4

La ambigüedad comienza en el propio estándar C. Tanto C99 como C11 tienen una descripción de snprintffunción idéntica . Aquí está la descripción de C99:

7.19.6.5 La snprintffunción
Sinopsis
1 #include <stdio.h> int snprintf(char * restrict s, size_t n, const char * restrict format, ...);
Descripción
2 La snprintffunción es equivalente a fprintf, excepto que la salida se escribe en una matriz (especificada por argumento s) en lugar de en una secuencia. Si nes cero, no se escribe nada y spuede ser un puntero nulo. De lo contrario, los caracteres de salida más allá de 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.
Devuelve
3 La snprintffunción devuelve el número de caracteres que se habrían escrito sinha sido 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 completamente si y solo si el valor devuelto es no negativo y menor que n.

Por un lado la sentencia

De lo contrario, los caracteres de salida más allá de 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 ( sapunta a una matriz de 3 caracteres y) nes 3, se escribirán 2 caracteres y se descartarán los caracteres más allá del segundo ; entonces 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 es indefinido.
Si nes 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 encaja en la matriz de salida o no ; si no, algunos caracteres se descartan de manera que la salida matriz 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 frase

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

da ambigüedad (o mi inglés no es lo suficientemente bueno). Puedo interpretar esta oración de al menos dos formas:
1. La salida termina en nulo si y solo si el valor devuelto no es negativo y es menor quen (lo que significa que si el valor devuelto no es menor que n, es decir, la salida (incluida la terminando carácter nulo) no cabe en la matriz, entonces la salida no es terminada en nulo ).
2. La salida está completa (no se han descartado caracteres) si y solo si el valor devuelto es no negativo y menor quen .


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 la snprintffunción necesita un cambio para eliminar cualquier ambigüedad (lo que da motivos para escribir una propuesta para el estándar de lenguaje C).
El ejemplo de redacción no ambigua creo que se puede tomar 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 propuestas / planes de C estándar para cambiar la descripción de esta función? ".

Robin Kuzmin
fuente
4
Su interpretación 1 no me parece en absoluto plausible. Analizo esa oración como "La salida (que es, por cierto, terminada en nulo) se ha escrito completamente si ...", que solo puedo entender como # 2.
zwol
1
La negación de la oración "la salida terminada en nulo se ha escrito completamente" es "la salida terminada en nulo no se ha escrito completamente". Nada mas. La oración negada por sí sola no implica que se haya escrito nada (esto incluye salida incompleta terminada en nulo, salida incompleta sin terminación nula o ideas verdes incoloras). Algún otro lugar en el estándar dice lo que está escrito exactamente cuando la salida está incompleta, y ese lugar establece que la salida es terminada en nulo a menos que esté vacía (n == 0).
n. 'pronombres' m.