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 .
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:
4H
indica una cadena de 4 caracteres " A= "
( H
significa campo Hollerith );I5
indica un campo entero de ancho 5;F10.2
indica un campo de punto flotante de ancho 10 con 2 dígitos después del punto decimal.Una salida con argumentos de entrada 100, 200 y 1500,25 podría verse así:
A= 100 B= 200 ÁREA= 1500,25 UNIDADES CUADRADAS
En 1967 apareció BCPL . [2] Su biblioteca incluía la writef
rutina. [3] Un ejemplo de aplicación se ve así:
WRITEF("EL PROBLEMA DE %I2-QUEENS TIENE %I5 SOLUCIONES*N", NUMQUEENS, COUNT)
Por la presente:
%I2
indica un entero de ancho 2 (el orden del ancho y tipo del campo de especificación de formato se invierte en comparación con C printf
);%I5
indica un entero de ancho 5;*N
es una secuencia de escape del lenguaje BCPL que representa un carácter de nueva línea (para el cual C utiliza la secuencia de escape \n
).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.
En 1973, printf
se incluye como una rutina C como parte de la versión 4 de Unix . [7]
En 1990, printf
se 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, printf
se incluye un comando con GNU shellutils (ahora parte de GNU Core Utilities ).
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 printf
consciente de ellos.
La -Wformat
opción de GCC permite realizar comprobaciones en tiempo de compilación de printf
las 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 printf
los especificadores de formato, habilitar esta opción extiende efectivamente la sintaxis de C++ al hacer que el formato sea parte de ella.
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::format
resultó 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, libfmt
es 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::print
comando 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 -Wformat
opción, esta no es una función opcional.
La especificación de formato de libfmt
y std::format
es, 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::print
completa el ciclo histórico, devolviendo el estado del arte (a partir de 2024) a lo que era en el caso de la primera PRINT
implementación de FORTRAN en la década de 1950, analizada al comienzo de esta sección.
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 age
en formato decimal.
printf ( "Tu edad es %d" , edad );
La sintaxis para un especificador de formato es:
%[ parámetro ][ indicadores ][ ancho ][. precisión ][ longitud ] tipo
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
.
El campo de banderas puede ser cero o más de (en cualquier orden):
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)
12
printf("%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
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.
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.h
archivo de encabezado que incluye una serie de macros para printf
codificació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:
El campo de tipo puede ser cualquiera de los siguientes:
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 %s
especificador 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 %p
especificación genérica, agregando caracteres de formato adicionales. [23] Por ejemplo, %pI4
imprime una dirección IPv4 en formato decimal con puntos. Esto permite la verificación estática de la cadena de formato (de la %p
parte) a expensas de la compatibilidad total con printf normal.
Las variantes de printf
proporcionan las funciones de formato pero con un comportamiento adicional o ligeramente diferente.
fprintf
genera una salida a un objeto de archivo de sistema que permite una salida distinta a la salida estándar.
sprintf
escribe en un buffer de cadena en lugar de en la salida estándar.
snprintf
proporciona un nivel de seguridad sprintf
ya 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_list
una lista de parámetros de longitud variable en lugar de una variable. Por ejemplo, existe una vfprintf
, vsprintf
, vsnprintf
.
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 -Wall
o -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.
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 .
Aunque es una función de salida en la superficie, printf
permite 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 %n
funcionalidad 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 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 ).
%
operador) [28]printf
, sprintf
, y fmt
)print()
y FileStream.printf()
)iostream
printf
(Unix)printk
(imprimir mensajes del kernel)scanf
PRINT
conectada . El manual también presenta el comando que también utiliza la declaración para escribir en una unidad de cinta .WRITE OUTPUT TAPE
FORMAT