stringtranslate.com

imprimirf

Un ejemplo de la función printf

printf es una función de la biblioteca estándar de C que formatea texto y lo escribe en la salida estándar .

El nombre, printf, es la abreviatura de print formatted , donde print se refiere a la salida a una impresora, aunque las funciones no se limitan a la salida de la impresora.

La biblioteca estándar proporciona muchas otras funciones similares que forman una familia de funciones similares a printf . Estas funciones aceptan un parámetro de cadena de formato y una cantidad variable de parámetros de valor que la función serializa según la cadena de formato y escribe en un flujo de salida o en un búfer de cadena .

La cadena de formato se codifica como un lenguaje de plantilla que consta de texto literal y especificadores de formato que especifican cómo serializar un valor. A medida que la cadena de formato se procesa de izquierda a derecha , se utiliza un valor posterior para cada especificador de formato encontrado. Un especificador de formato comienza con un %carácter y tiene uno o más caracteres siguientes que especifican cómo serializar un valor.

La sintaxis y la semántica de la cadena de formato son las mismas para todas las funciones de la familia similar a printf.

La falta de coincidencia entre los especificadores de formato y el recuento y tipo de valores puede provocar un bloqueo o una vulnerabilidad .

La cadena de formato printf es complementaria a la cadena de formato scanf , que proporciona una entrada formateada ( análisis léxico o sintáctico ). Ambas cadenas de formato proporcionan una funcionalidad relativamente simple en comparación con otros motores de plantillas, analizadores léxicos y analizadores sintácticos.

El diseño de formato ha sido copiado en otros lenguajes de programación .

Historia

Década de 1950: Fortran

Los primeros lenguajes de programación como Fortran usaban instrucciones especiales con una sintaxis diferente a la de otros cálculos para crear descripciones de formato. [1] En este ejemplo, el formato se especifica en la línea 601 y el comando PRINT [a] hace referencia a él por el número de línea:

  IMPRIMIR 601 , IA , IB , ÁREA 601 FORMATO ( 4 H A = , I5 , 5 H B = , I5 , 8 H ÁREA = , F10 , 2 , 13 H UNIDADES CUADRADAS )              

Por la presente:

Una salida con argumentos de entrada 100, 200 y 1500,25 podría verse así:

A= 100 B= 200 ÁREA= 1500,25 UNIDADES CUADRADAS

Década de 1960: BCPL y ALGOL 68

En 1967 apareció BCPL . [2] Su biblioteca incluía la writefrutina. [3] Una aplicación de ejemplo se ve así:

WRITEF("EL PROBLEMA DE %I2-QUEENS TIENE %I5 SOLUCIONES*N", NUMQUEENS, COUNT)

Por la presente:

En 1968, ALGOL 68 tenía una API más funcional , pero aún utilizaba una sintaxis especial (los $delimitadores rodean la sintaxis de formato especial):

printf (( $ "Color " g ", número1 " 6 d , ", número2 " 4 zd , ", hex " 16 r2d , ", float " - d .2 d , ", valor sin signo" -3 d "." l$ , "rojo" , 123456 , 89 , BIN 255 , 3.14 , 250 ));       

A diferencia de Fortran, el uso de llamadas a funciones y tipos de datos normales simplifica el lenguaje y el compilador, y permite que la implementación de la entrada/salida se escriba en el mismo lenguaje.

Se pensaba que estas ventajas superaban a las desventajas (como la falta total de seguridad de tipos en muchos casos) hasta la década del 2000, y en la mayoría de los lenguajes más nuevos de esa era, la E/S no es parte de la sintaxis.

Desde entonces, la gente ha aprendido por las malas [4] que esta creencia es falsa, lo que ha dado lugar a una gran cantidad de consecuencias no deseadas, que van desde vulnerabilidades de seguridad hasta fallos de hardware (por ejemplo, las capacidades de red del teléfono se desactivan permanentemente después de intentar conectarse a un punto de acceso llamado "%p%s%s%s%s%n". [5] ).

Por lo tanto, los lenguajes modernos, como C++20 y posteriores, están tomando medidas para revertir este error e incluyen especificaciones de formato como parte de la sintaxis del lenguaje, [6] lo que restaura la seguridad de tipos en el formato hasta cierto punto y permite al compilador detectar algunas combinaciones no válidas de especificadores de formato y tipos de datos en el momento de la compilación.

Década de 1970: C

En 1973, printfse incluye como una rutina C como parte de la versión 4 de Unix . [7]

Década de 1990: Comando Shell

En 1990, printfse certifica un comando de shell como parte de 4.3BSD-Reno . Está modelado a partir de la función de la biblioteca estándar. [8]

En 1991, printfse incluye un comando con GNU shellutils (ahora parte de GNU Core Utilities ).

Década de 2000: -Seguridad en formato W

La necesidad de hacer algo respecto de la variedad de problemas que resultan de la falta de seguridad de tipos ha impulsado intentos de hacer que el compilador de C++ sea printfconsciente de ellos.

La -Wformatopción de GCC permite realizar comprobaciones en tiempo de compilación de printflas llamadas, lo que permite al compilador detectar un subconjunto de llamadas no válidas (y emitir una advertencia o un error, deteniendo la compilación por completo, dependiendo de otros indicadores). [9] .

Dado que el compilador inspecciona printflos especificadores de formato, habilitar esta opción extiende efectivamente la sintaxis de C++ al hacer que el formato sea parte de ella.

Década de 2020: especificadores de formato de C++20 y versión impresa de C++23

Como se dijo anteriormente, numerosos problemas [10] con printf()la falta de seguridad de tipos dieron como resultado la revisión [11] del enfoque de formato, y C++20 en adelante incluye especificaciones de formato en el lenguaje [12] para permitir un formato seguro de tipos.

El enfoque (y la sintaxis) de C++20 std::formatresultó de la incorporación efectiva de la API de Victor Zverovich libfmt[13] en la especificación del lenguaje [14] (Zverovich escribió [15] el primer borrador de la nueva propuesta de formato); en consecuencia, libfmtes una implementación de la especificación de formato de C++20.

La función de formato se ha combinado con la salida en C++23, que proporciona [16] el std::printcomando como reemplazo de printf().

Como la especificación de formato se ha convertido en parte de la sintaxis del lenguaje, el compilador de C++ puede evitar combinaciones no válidas de tipos y especificadores de formato en muchos casos. A diferencia de la -Wformatopción, esta no es una función opcional.

La especificación de formato de libfmty std::formates, en sí misma, un "minilenguaje" extensible (denominado como tal en la especificación, [17] un ejemplo de un lenguaje específico del dominio ).

Por lo tanto , la incorporación de un mini-lenguaje separado y específico del dominio específicamente para formatear en la sintaxis del lenguaje C++ std::printcompleta el ciclo histórico, devolviendo el estado del arte (a partir de 2024) a lo que era en el caso de la primera PRINTimplementación de FORTRAN en la década de 1950, analizada al comienzo de esta sección.

Especificador de formato

El formato de un valor se especifica como un marcado en la cadena de formato. Por ejemplo, lo siguiente muestra "Su edad es " y luego el valor de la variable ageen formato decimal.

printf ( "Tu edad es %d" , edad ); 

Sintaxis

La sintaxis para un especificador de formato es:

%[ parámetro ][ indicadores ][ ancho ][. precisión ][ longitud ] tipo

Campo de parámetro

El campo de parámetro es opcional. Si se incluye, la correspondencia entre especificadores y valores no es secuencial. El valor numérico, n , selecciona el parámetro de valor n.

Esta es una extensión POSIX ; no C99 .

Este campo permite utilizar el mismo valor varias veces en una cadena de formato en lugar de tener que pasar el valor varias veces. Si un especificador incluye este campo, los especificadores posteriores también deben incluirlo.

Por ejemplo,

printf ( "%2$d %2$#x; %1$d %1$#x" , 16 , 17 )

salidas: 17 0x11; 16 0x10.

Este campo es particularmente útil para localizar mensajes en diferentes idiomas naturales que a menudo utilizan un orden de palabras diferente.

En Microsoft Windows, el soporte para esta característica se realiza a través de una función diferente, printf_p.

Campo de banderas

El campo de banderas puede ser cero o más de (en cualquier orden):

Campo de ancho

El campo de ancho especifica la cantidad mínima de caracteres que se deben mostrar. Si el valor se puede representar con menos caracteres, se rellena con espacios a la izquierda para que la salida tenga la cantidad de caracteres especificada. Si el valor requiere más caracteres, la salida es más larga que el ancho especificado. Un valor nunca se trunca.

Por ejemplo, especifica un ancho de 3 y genera un espacio a la izquierda para generar 3 caracteres. La llamada genera un valor de 4 caracteres, ya que ese es el ancho mínimo para ese valor, aunque el ancho especificado sea 3.printf("%3d", 12) 12printf("%3d", 1234)1234

Si se omite el campo de ancho, la salida es el número mínimo de caracteres para el valor.

Si el campo se especifica como *, entonces el valor de ancho se lee de la lista de valores en la llamada. [18] Por ejemplo, salidas donde el segundo parámetro, 3, es el ancho (coincide con *) y 10 es el valor a serializar (coincide con d).printf("%*d", 3, 10) 10

Aunque no es parte del campo de ancho, un cero inicial se interpreta como el indicador de relleno de ceros mencionado anteriormente, y un valor negativo se trata como el valor positivo junto con el -indicador de alineación a la izquierda también mencionado anteriormente.

El campo de ancho se puede utilizar para dar formato a los valores como una tabla (salida tabulada). Sin embargo, las columnas no se alinean si algún valor es mayor que el que cabe en el ancho especificado. Por ejemplo, observe que el valor de la última línea (1234) no cabe en la primera columna de ancho 3 y, por lo tanto, la columna no está alineada.

 1 1 12 12 123 123 1234 123     

Campo de precisión

El campo de precisión suele especificar un límite máximo de la salida, según el tipo de formato en particular. Para los tipos numéricos de punto flotante, especifica la cantidad de dígitos a la derecha del punto decimal que se debe redondear la salida. Para el tipo de cadena, limita la cantidad de caracteres que se deben generar, después de lo cual se trunca la cadena.

El campo de precisión se puede omitir, o se puede utilizar un valor entero numérico o un valor dinámico cuando se pasa como otro argumento cuando se indica con un asterisco *. Por ejemplo, las salidasprintf("%.*s", 3, "abcdef")abecedario.

Campo de longitud

El campo de longitud se puede omitir o ser cualquiera de los siguientes:

Las opciones de longitud específicas de la plataforma existían antes del uso generalizado de las extensiones ISO C99, entre ellas:

La norma ISO C99 incluye el inttypes.harchivo de encabezado que incluye una serie de macros para printfcodificación independiente de la plataforma. Por ejemplo: especifica el formato decimal para un entero con signo de 64 bits. Dado que las macros se evalúan como una cadena literal y el compilador concatena cadenas literales adyacentes, la expresión se compila como una sola cadena.printf("%" PRId64, t);"%" PRId64

Las macros incluyen:

Tipo de campo

El campo de tipo puede ser cualquiera de los siguientes:

Formato de tipo de datos personalizado

Una forma común de manejar el formato con un tipo de datos personalizado es formatear el valor del tipo de datos personalizado en una cadena y luego usar el %sespecificador para incluir el valor serializado en un mensaje más grande.

Algunas funciones similares a printf permiten extensiones al minilenguaje basado en caracteres de escape , lo que permite al programador utilizar una función de formato específica para tipos no integrados. Una de ellas es la función register_printf_function() de glibc (ahora obsoleta) . Sin embargo, rara vez se utiliza debido a que entra en conflicto con la comprobación de cadenas de formato estáticas. Otra es el formateador personalizado Vstr, que permite añadir nombres de formato de múltiples caracteres.

Algunas aplicaciones (como el servidor HTTP Apache ) incluyen su propia función similar a printf y le incorporan extensiones. Sin embargo, todas ellas tienden a tener los mismos problemas register_printf_function().

La función del núcleo de Linux printk admite varias formas de mostrar las estructuras del núcleo utilizando la %pespecificación genérica, agregando caracteres de formato adicionales. [23] Por ejemplo, %pI4imprime una dirección IPv4 en formato decimal con puntos. Esto permite la verificación estática de la cadena de formato (de la %pparte) a expensas de la compatibilidad total con printf normal.

Familia

Las variantes de printfproporcionan las funciones de formato pero con un comportamiento adicional o ligeramente diferente.

fprintfgenera una salida a un objeto de archivo de sistema que permite una salida distinta a la salida estándar.

sprintfescribe en un buffer de cadena en lugar de en la salida estándar.

snprintfproporciona un nivel de seguridad sprintfya que el llamador proporciona un parámetro de longitud (n) que especifica el número máximo de caracteres a escribir en el búfer.

Para la mayoría de las funciones de la familia printf, existe una variante que acepta va_listen lugar de una lista de parámetros de longitud variable. Por ejemplo, hay una vfprintf, vsprintf, vsnprintf.

Vulnerabilidades

Ataque de cadena de formato

Los parámetros de valor extra se ignoran, pero si la cadena de formato tiene más especificadores de formato que los parámetros de valor pasados, el comportamiento es undefined . Para algunos compiladores de C, un especificador de formato extra da como resultado el consumo de un valor aunque no haya ninguno. Esto puede permitir el ataque de cadena de formato . Generalmente, para C, los argumentos se pasan en la pila. Si se pasan muy pocos argumentos, printf puede leer más allá del final del marco de la pila, lo que permite que un atacante lea la pila.

Algunos compiladores, como GNU Compiler Collection , comprobarán estáticamente las cadenas de formato de funciones similares a printf y advertirán sobre problemas (al usar los indicadores -Wallo -Wformat). GCC también advertirá sobre funciones de estilo printf definidas por el usuario si se aplica el "formato" no estándar __attribute__a la función.

Explotación de cadenas de formato no controlada

La cadena de formato suele ser una cadena literal , lo que permite el análisis estático de la llamada a la función. Sin embargo, la cadena de formato puede ser el valor de una variable, lo que permite un formato dinámico, pero también una vulnerabilidad de seguridad conocida como explotación de cadena de formato no controlada .

Escritura de memoria

Aunque es una función de salida en la superficie, printfpermite escribir en una ubicación de memoria especificada por un argumento a través de %n. Esta funcionalidad se utiliza ocasionalmente como parte de ataques de formato de cadena más elaborados. [24]

La %nfuncionalidad también permite que un conjunto de argumentos bien formado sea Turing-completoprintf por accidente . Un juego de tres en raya escrito en el formato string es un ganador del 27.º IOCCC . [25]

Lenguajes de programación con printf

Lenguajes de programación notables que incluyen printf o funcionalidad similar a printf.

Se excluyen los lenguajes que utilizan cadenas de formato que se desvían del estilo de este artículo (como AMPL y Elixir ), los lenguajes que heredan su implementación de la JVM u otro entorno (como Clojure y Scala ) y los lenguajes que no tienen una implementación nativa estándar de printf pero tienen bibliotecas externas que emulan el comportamiento de printf (como JavaScript ).

Véase también

Notas

  1. ^ Según el manual de Fortran de 1956 [1] , el comando imprime en la impresoraPRINT conectada . El manual también presenta el comando que también utiliza la declaración para escribir en una unidad de cinta .WRITE OUTPUT TAPEFORMAT

Referencias

  1. ^ ab Backus, John Warner ; Beeber, RJ; Best, Sheldon F.; Goldberg, Richard ; Herrick, Harlan L.; Hughes, RA; Mitchell, LB; Nelson, Robert A.; Nutt, Roy ; Sayre, David ; Sheridan, Peter B.; Stern, Harold; Ziller, Irving (15 de octubre de 1956). Sayre, David (ed.). El sistema de codificación automática FORTRAN para IBM 704 EDPM: Manual de referencia del programador (PDF) . Nueva York, EE. UU.: División de Ciencias Aplicadas y Departamento de Investigación en Programación, International Business Machines Corporation . págs. 26–30. Archivado (PDF) desde el original el 4 de julio de 2022 . Consultado el 4 de julio de 2022 .(2+51+1 páginas)
  2. ^ "BCPL". cl.cam.ac.uk . Consultado el 19 de marzo de 2018 .
  3. ^ Richards, Martin; Whitby-Strevens, Colin (1979). BCPL: el lenguaje y su compilador. Cambridge University Press. pág. 50.
  4. ^ "Ataque de cadena de formato".
  5. ^ ""Un error del iPhone interrumpe el WiFi cuando te unes a un punto de acceso con un nombre inusual".
  6. ^ "Especificación de formato estándar C++20".
  7. ^ McIlroy, MD (1987). Un lector de Unix para investigación: extractos anotados del Manual del programador, 1971–1986 (PDF) (Informe técnico). CSTR. Bell Labs. 139.
  8. ^ "printf (4.3+Reno BSD)". man.freebsd.org . Consultado el 1 de abril de 2024 .
  9. ^ "Opción -Wformat del compilador gcc".
  10. ^ "Cómo no codificar: cuidado con printf".
  11. ^ "Propuesta de mejoras en el formato C++20 para permitir comprobaciones en tiempo de compilación".
  12. ^ "C++20 std::formato".
  13. ^ "libfmt: una biblioteca de formato moderna".
  14. ^ "Formato de texto C++20: una introducción".
  15. ^ "Historial de propuestas de formato C++".
  16. ^ "Impresión C++".
  17. ^ "Minilenguaje de especificación de formato".
  18. ^ "printf". cplusplus.com . Consultado el 10 de junio de 2020 .
  19. ^ ISO / IEC (1999). ISO/IEC 9899:1999(E): Lenguajes de programación – C §7.19.6.1 párrafo 7 .
  20. ^ ""Manual de referencia de la biblioteca C de GNU", "12.12.3 Tabla de conversiones de salida"". Gnu.org . Consultado el 17 de marzo de 2014 .
  21. ^ "printf" ( %a añadido en C99)
  22. ^ "Formato de salida de impresión numérica". Tutoriales de Java . Oracle Inc. Recuperado el 19 de marzo de 2018 .
  23. ^ "Documentación del kernel de Linux/printk-formats.txt". Git.kernel.org. Archivado desde el original el 29 de abril de 2015. Consultado el 17 de marzo de 2014 .
  24. ^ https://www.exploit-db.com/docs/english/28476-linux-format-string-exploitation.pdf [ URL desnuda PDF ]
  25. ^ "Lo mejor de la muestra: abuso de libc". Ioccc.org . Consultado el 5 de mayo de 2022 .
  26. ^ ""Especificaciones básicas de Open Group, número 7, edición de 2018", "POSIX awk", "Declaraciones de salida"". pubs.opengroup.org . Consultado el 29 de mayo de 2022 .
  27. ^ "Biblioteca estándar de Printf". Manual del lenguaje Julia . Consultado el 22 de febrero de 2021 .
  28. ^ "Tipos integrados: formato de cadenas al estilo printf", The Python Standard Library , Python Software Foundation , consultado el 24 de febrero de 2021

Enlaces externos