c string printf compiler-warnings format-specifiers

¿Cuál es la diferencia subyacente entre printf(s) y printf("% s", s)?



string compiler-warnings (5)

Entonces, ¿cuál es la diferencia subyacente entre printf (s) y printf ("% s", s)

"printf (s)" tratará s como una cadena de formato. Si s contiene especificadores de formato, printf los interpretará y buscará varargs. Dado que en realidad no existen varargs, es probable que se desencadene un comportamiento indefinido.

Si un atacante controla "s", es probable que esto sea un agujero de seguridad.

printf ("% s", s) solo imprimirá lo que está en la cadena.

¿Y por qué recibo una advertencia en un solo caso?

Las advertencias son un equilibrio entre la captura de estupidez peligrosa y no crear demasiado ruido.

Los programadores de C están en el hábito de usar printf y varias funciones similares a printf * como funciones de impresión genéricas, incluso cuando en realidad no necesitan formato. En este entorno, es fácil para alguien cometer el error de escribir printf (s) sin pensar en el origen. Dado que el formateo es bastante inútil sin datos para formatear printf (s) tiene poco uso legítimo.

printf (s, formato, argumentos), por otro lado, indica que el programador intentó deliberadamente que el formateo tuviera lugar.

Afaict esta advertencia no está activada de forma predeterminada en gcc en sentido ascendente, pero algunas distros la están activando como parte de sus esfuerzos para reducir los agujeros de seguridad.

* Ambas funciones estándar de C como sprintf y fprintf y funciones en bibliotecas de terceros.

La pregunta es simple y simple, s es una cadena, de repente se me ocurrió la idea de intentar usar printf(s) para ver si funcionaba y recibí una advertencia en un caso y ninguna en el otro.

char* s = "abcdefghij/n"; printf(s); // Warning raised with gcc -std=c11: // format not a string literal and no format arguments [-Wformat-security] // On the other hand, if I use char* s = "abc %d efg/n"; printf(s, 99); // I get no warning whatsoever, why is that? // Update, I''ve tested this: char* s = "random %d string/n"; printf(s, 99, 50); // Results: no warning, output "random 99 string".

Entonces, ¿cuál es la diferencia subyacente entre printf(s) y printf("%s", s) y por qué recibo una advertencia en un solo caso?


En el primer caso, la cadena de formato no literal podría provenir del código de usuario o los datos proporcionados por el usuario (tiempo de ejecución), en cuyo caso podría contener %s u otras especificaciones de conversión, para las cuales no ha pasado los datos. . Esto puede llevar a todo tipo de problemas de lectura (y problemas de escritura si la cadena incluye %n , vea printf() o las páginas del manual de su biblioteca de C).

En el segundo caso, la cadena de formato controla la salida y no importa si alguna cadena que se imprima contiene especificaciones de conversión o no (aunque el código que se muestra imprime un número entero, no una cadena). El compilador (GCC o Clang se usa en la pregunta) asume que debido a que hay argumentos después de la cadena de formato (no literal), el programador sabe lo que están haciendo.

El primero es una vulnerabilidad de ''cadena de formato''. Puede buscar más información sobre el tema.

GCC sabe que la mayoría de las veces el único argumento printf() con una cadena de formato no literal es una invitación a problemas. Podrías usar puts() o fputs() lugar. Es suficientemente peligroso que GCC genere las advertencias con el mínimo de provocación.

El problema más general de una cadena de formato no literal también puede ser problemático si no tiene cuidado, pero es extremadamente útil suponiendo que sea cuidadoso. Tienes que trabajar más duro para que GCC se queje: requiere tanto -Wformat como -Wformat-nonliteral para recibir la queja.

De los comentarios:

Entonces, ignorando la advertencia, como si realmente supiera lo que estoy haciendo y no habrá errores, ¿es uno u otro más eficiente de usar o son los mismos? Teniendo en cuenta tanto el espacio como el tiempo.

De sus tres declaraciones printf() , dado el contexto estrecho de que la variable s está asignada inmediatamente por encima de la llamada, no hay problema real. Sin embargo, podría usar el puts(s) fputs(s, stdout) puts(s) si omitió la nueva línea de la cadena o fputs(s, stdout) como está y obtiene el mismo resultado, sin la sobrecarga de printf() analizando toda la cadena para descubrir que es todo Caracteres sencillos para imprimir.

La segunda declaración printf() también es segura como está escrita; La cadena de formato coincide con los datos pasados. No hay una diferencia significativa entre eso y simplemente pasar la cadena de formato como un literal, excepto que el compilador puede hacer más comprobaciones si la cadena de formato es un literal. El resultado en tiempo de ejecución es el mismo.

El tercer printf() pasa más argumentos de datos que los que necesita la cadena de formato, pero eso es benigno. Aunque no es ideal. Nuevamente, el compilador puede verificar mejor si la cadena de formato es un literal, pero el efecto en tiempo de ejecución es prácticamente el mismo.

Desde la especificación de printf() enlazada en la parte superior:

Cada una de estas funciones convierte, formatea e imprime sus argumentos bajo el control del formato . El formato es una cadena de caracteres, que comienza y termina en su estado de cambio inicial, si corresponde. El formato se compone de cero o más directivas: caracteres ordinarios, que simplemente se copian al flujo de salida, y especificaciones de conversión, cada una de las cuales dará como resultado la obtención de cero o más argumentos. Los resultados no están definidos si no hay argumentos suficientes para el formato. Si el formato se agota mientras permanecen los argumentos, los argumentos en exceso se evaluarán, pero de lo contrario se ignorarán.

En todos estos casos, no hay una indicación clara de por qué la cadena de formato no es un literal. Sin embargo, una razón para querer una cadena de formato no literal podría ser que a veces imprime los números de punto flotante en notación %f y otras veces en notación %e , y debe elegir cuál en tiempo de ejecución. (Si se basa simplemente en el valor, %g puede ser apropiado, pero hay ocasiones en las que desea el control explícito, siempre %e o siempre %f ).


Hay dos cosas en juego en tu pregunta.

Jonathan Leffler cubre brevemente el primero: la advertencia que está recibiendo es porque la cadena no es literal y no tiene ningún especificador de formato.

El otro es el misterio de por qué el compilador no emite una advertencia de que su número de argumentos no coincide con el número de especificadores. La respuesta corta es "porque no lo hace", pero más específicamente, printf es una función variable. Toma cualquier número de argumentos después de la especificación de formato inicial, desde 0 en adelante. El compilador no puede verificar si usted dio la cantidad correcta; eso depende de la propia función printf, y conduce al comportamiento indefinido que Joachim mencionó en los comentarios.

EDITAR : Voy a dar una respuesta más a su pregunta, como un medio de conseguir en una pequeña caja de jabón.

¿Cuál es la diferencia entre printf(s) y printf("%s", s) ? Simple: en este último, está utilizando printf como se declara. "%s" es un carácter constante *, y posteriormente no generará el mensaje de advertencia.

En sus comentarios a otras respuestas, mencionó "Ignorar la advertencia ...". No hagas esto Las advertencias existen por una razón, y deben resolverse (de lo contrario son solo ruido, y se perderán las advertencias que realmente importan entre los cruceros de todas las que no lo hacen).

Su problema se puede resolver de varias maneras.

const char* s = "abcdefghij/n"; printf(s);

resolverá la advertencia, porque ahora estás usando un puntero de const, y no hay ninguno de los peligros que Jonathan mencionó. (También puede declararlo como const char* const s , pero no tiene que hacerlo. La primera const es importante, porque luego coincide con la declaración de printf , y porque const char* s significa que los caracteres apuntados por s pueden '' t cambio, es decir, la cadena es un literal.)

O, incluso más simple, simplemente haz:

printf("abcdefghij/n");

Esto es implícitamente un puntero de const, y tampoco un problema.


La advertencia lo dice todo.

Primero, para discutir sobre el problema , según la firma, el primer parámetro para printf() es una cadena de formato que puede contener especificadores de formato ( especificador de conversión ). En el caso, una cadena contiene un especificador de formato y el argumento correspondiente no se proporciona, invoca un comportamiento indefinido .

Por lo tanto, un enfoque más limpio ( o más seguro ) (de imprimir una cadena que no necesita una especificación de formato) sería puts(s); sobre printf(s); ( el primero no procesa s para ningún especificador de conversión, eliminando el motivo de la posible UB en el último caso ). Puede elegir fputs() , si le preocupa la nueva línea de finalización que se agrega automáticamente a puts() .

Dicho esto, con respecto a la opción de advertencia, -Wformat-security del manual en línea de gcc

Actualmente, esto advierte sobre las llamadas a las funciones printf y scanf donde la cadena de formato no es una cadena literal y no hay argumentos de formato, como en printf (foo); . Esto puede ser un agujero de seguridad si la cadena de formato proviene de una entrada no confiable y contiene %n .

En su primer caso, solo se proporciona un argumento a printf() , que no es una cadena literal, sino una variable , que se puede generar / rellenar muy bien en el tiempo de ejecución, y si contiene especificadores de formato inesperados , puede invocar UB . El compilador no tiene forma de verificar la presencia de ningún especificador de formato en eso. Ese es el problema de seguridad allí.

En el segundo caso, se proporciona el argumento adjunto, el especificador de formato no es el único argumento pasado a printf() , por lo que el primer argumento no necesita ser verificado. Por lo tanto, la advertencia no está allí.

Actualizar:

Con respecto al tercero, con el argumento en exceso que requiere la cadena de formato suministrada

printf(s, 99, 50);

citando de C11 , capítulo §7.21.6.1

[...] Si el formato se agota mientras permanecen los argumentos, los argumentos en exceso se evalúan (como siempre), pero de lo contrario se ignoran. [...]

Por lo tanto, pasar el argumento en exceso no es un problema (desde la perspectiva del compilador) y está bien definido. NO hay margen para cualquier advertencia allí.


La razón subyacente: printf se declara como:

int printf(const char *fmt, ...) __attribute__ ((format(printf, 1, 2)));

Esto le dice a gcc que printf es una función con una interfaz de estilo printf donde la cadena de formato es lo primero. En mi humilde opinión debe ser literal; No creo que haya una manera de decirle al buen compilador que s es en realidad un puntero a una cadena literal que había visto antes.

Lea más sobre __attribute__ here .