printf (del inglés print formatted , "impresión formateada"): un nombre generalizado para una familia de funciones o métodos de bibliotecas comerciales estándar o conocidas, u operadores integrados de algunos lenguajes de programación utilizados para salida formateada - salida a varios flujos de valores de diferentes tipos formateados de acuerdo con una plantilla determinada. Esta plantilla está determinada por una cadena compuesta de acuerdo con reglas especiales (cadena de formato).
El miembro más notable de esta familia es la función printf , así como una serie de otras funciones derivadas de printfnombres en la biblioteca estándar de C (que también forma parte de las bibliotecas estándar de C++ y Objective-C ).
La familia de sistemas operativos UNIX también tiene una utilidad printf que cumple los mismos propósitos que la salida formateada.
El operador FORMAT de Fortran puede considerarse un prototipo temprano de tal función . La función de inferencia impulsada por cadenas apareció en los precursores del lenguaje C ( BCPL y B ). En la especificación de la biblioteca estándar de C , recibió su forma más conocida (con banderas, ancho, precisión y tamaño). La sintaxis de cadena de plantilla de salida (a veces denominada cadena de formato , cadena de formato o cadena de formato ) fue utilizada posteriormente por otros lenguajes de programación (con variaciones para adaptarse a las características de estos lenguajes). Por regla general, las funciones correspondientes de estos lenguajes también se denominan printf y/o derivados del mismo.
Algunos entornos de programación más recientes (como .NET ) también utilizan el concepto de formato de salida controlado por cadena, pero con una sintaxis diferente.
Fortran ya tenía operadores que proporcionaban salida formateada. La sintaxis de las sentencias WRITE e PRINT incluía una etiqueta que hacía referencia a una sentencia FORMAT no ejecutable que contenía una especificación de formato. Los especificadores formaban parte de la sintaxis del operador, y el compilador podía generar inmediatamente código que realizaba directamente el formateo de datos, lo que aseguraba el mejor rendimiento en las computadoras de aquellos tiempos. Sin embargo, hubo las siguientes desventajas:
El primer prototipo de la futura función printf aparece en el lenguaje BCPL en la década de 1960 . La función WRITEF toma una cadena de formato que especifica el tipo de datos por separado de los datos mismos en la variable de cadena (el tipo se especificó sin los campos de marca, ancho, precisión y tamaño, pero ya estaba precedido por un signo de porcentaje %). [1] El propósito principal de la cadena de formato era pasar tipos de argumentos (en lenguajes de programación con tipado estático , determinar el tipo del argumento pasado para una función con una lista no fija de parámetros formales requiere un mecanismo complejo e ineficiente para pasar información de tipo en el caso general). La función WRITEF en sí era un medio para simplificar la salida: en lugar de un conjunto de funciones WRCH (salida de un carácter), WRITES (salida de una cadena), WRITEN , WRITED , WRITEOCT , WRITEHEX (salida de números en varias formas), una sola llamada se utilizó en el que era posible intercalar "solo texto" con valores de salida.
El lenguaje Bee que le siguió en 1969 ya usaba el nombre printf con una cadena de formato simple (similar a BCPL ), especificando solo uno de los tres tipos posibles y dos representaciones numéricas: decimal ( ), octal ( ), cadenas ( ) y caracteres ( ), y la única forma de formatear la salida en estas funciones era agregar caracteres antes y después de la salida del valor de la variable. [2]%d%o%s%c
Desde la introducción de la primera versión del lenguaje C ( 1970 ), la familia printf se ha convertido en la principal herramienta de salida de formato. El costo de analizar la cadena de formato con cada llamada de función se consideró aceptable y las llamadas alternativas para cada tipo por separado no se introdujeron en la biblioteca. La especificación de funciones se incluyó en los dos estándares lingüísticos existentes , publicados en 1990 y 1999 . La especificación de 1999 contiene algunas innovaciones de la especificación de 1990.
El lenguaje C++ utiliza la biblioteca C estándar (según el estándar de 1990), incluida toda la familia printf .
Como alternativa, la biblioteca estándar de C++ proporciona un conjunto de clases de flujo de entrada y salida. Las declaraciones de salida de esta biblioteca son de tipo seguro y no requieren el análisis de cadenas de formato cada vez que se las llama. Sin embargo, muchos programadores siguen utilizando la familia printf , ya que la secuencia de salida suele ser más compacta y la esencia del formato utilizado es más clara.
Objective-C es un complemento bastante "delgado" para C, y los programas en él pueden usar directamente las funciones de la familia printf .
Además de C y sus derivados (C++, Objective-C), muchos otros lenguajes de programación utilizan la sintaxis de cadena de formato similar a printf:
Además, gracias a la utilidad printf incluida con la mayoría de los sistemas tipo UNIX, printf se usa en muchos scripts de shell (para sh , bash , csh , zsh , etc.).
Algunos lenguajes y entornos de programación más recientes también utilizan el concepto de salida impulsada por cadena de formato, pero con una sintaxis diferente.
Por ejemplo, .Net Core Class Library (FCL) tiene una familia de métodos System.String.Format , System.Console.Write y System.Console.WriteLine , algunas sobrecargas de las cuales generan sus datos de acuerdo con una cadena de formato. Dado que la información completa sobre los tipos de objetos está disponible en el tiempo de ejecución de .Net, no es necesario pasar esta información en la cadena de formato.
Todas las funciones tienen la raíz printf en sus nombres . Los prefijos antes del nombre de la función significan:
Todas las funciones toman una cadena de formato como uno de los parámetros ( formato ) (descripción de la sintaxis de la cadena a continuación). Devuelve el número de caracteres escritos (impresos), sin incluir el carácter nulo al final de . El número de argumentos que contienen datos para la salida formateada debe ser al menos tanto como se menciona en la cadena de formato. Los argumentos "extra" se ignoran.
Las funciones de la familia n ( snprintf , vsnprintf ) devuelven la cantidad de caracteres que se imprimirían si el parámetro n (que limita la cantidad de caracteres que se imprimirán) fuera lo suficientemente grande. En el caso de codificaciones de un solo byte , el valor devuelto corresponde a la longitud deseada de la cadena (sin incluir el carácter nulo al final).
Las funciones de la familia s ( sprintf , snprintf , vsprintf , vsnprintf ) toman como primer parámetro( s ) un puntero al área de memoria donde se escribirá la cadena resultante. Las funciones que no tienen un límite en la cantidad de caracteres escritos son funciones no seguras , ya que pueden provocar un error de desbordamiento del búfer si la cadena de salida es mayor que el tamaño de la memoria asignada para la salida.
Las funciones de la familia f escriben una cadena en cualquier flujo abierto (el parámetro de flujo ), en particular, en los flujos de salida estándar ( stdout , stderr ). fprintf(stdout, format, …)equivalente a printf(format, …).
Las funciones de la familia v toman argumentos no como un número variable de argumentos (como todas las demás funciones printf), sino como una lista va lista . En este caso, cuando se llama a la función, la macro va end no se ejecuta.
Las funciones de la familia w (primer carácter) son una implementación limitada de Microsoft de la familia de funciones s : wsprintf , wnsprintf , wvsprintf , wvnsprintf . Estas funciones se implementan en las bibliotecas dinámicas user32.dll y shlwapi.dll ( n funciones). No admiten salida de punto flotante, y wnsprintf y wvnsprintf solo admiten texto alineado a la izquierda.
Las funciones de la familia w ( wprintf , swprintf ) implementan soporte para codificaciones multibyte, todas las funciones de esta familia trabajan con punteros a cadenas multibyte ( wchar_t ).
Las funciones de la familia a ( asprintf , vasprintf ) asignan memoria para la cadena de salida utilizando la función malloc , la memoria se libera en el procedimiento de llamada, en caso de error al ejecutar la función, la memoria no se asigna.
Valor de retorno: valor negativo — signo de error; si tiene éxito, las funciones devuelven la cantidad de bytes escritos/salidos (ignorando el byte nulo al final), la función snprintf imprime la cantidad de bytes que se escribirían si n fuera lo suficientemente grande.
Al llamar a snprintf , n puede ser cero (en cuyo caso s puede ser un puntero nulo ), en cuyo caso no se realiza ninguna escritura, la función solo devuelve el valor de retorno correcto.
En C y C++, una cadena de formato es una cadena terminada en nulo. Todos los caracteres, excepto los especificadores de formato, se copian en la cadena resultante sin cambios. El signo estándar del comienzo del especificador de formato es el carácter %( signo de porcentaje ), para mostrar el signo en sí %, se utiliza su duplicación %%.
El especificador de formato se parece a:
% [ banderas ] [ ancho ] [ . precisión ][ tamaño ] tipoLos componentes necesarios son el carácter de inicio del especificador de formato ( %) y el tipo .
BanderasSeñal | Firmar nombre | Sentido | En ausencia de este signo | Nota |
---|---|---|---|---|
- | menos | el valor de salida se alinea a la izquierda dentro del ancho de campo mínimo | a la derecha | |
+ | una ventaja | especifique siempre un signo (más o menos) para el valor numérico decimal mostrado | solo para numeros negativos | |
espacio | coloque un espacio antes del resultado si el primer carácter del valor no es un signo | La salida puede comenzar con un número. | El carácter + tiene prioridad sobre el carácter de espacio. Solo se utiliza para valores decimales con signo. | |
# | enrejado | "forma alternativa" de salida de valor | Al generar números en formato hexadecimal u octal, el número estará precedido por una función de formato (0x o 0, respectivamente). | |
0 | cero | rellenar el campo al ancho especificado en el campo de ancho de secuencia de escape con el símbolo0 | almohadilla con espacios | Se utiliza para los tipos d , i , o , u , x , X , a , A , e , E , f , F , g , G . Para los tipos d , i , o , u , x , X , si se especifica precisión , se ignora este indicador. Para otros tipos, el comportamiento no está definido.
Si se especifica un indicador menos '-', ese indicador también se ignora. |
El ancho (carácter decimal o asterisco ) especifica el ancho mínimo del campo (incluido el signo de los números). Si la representación del valor es mayor que el ancho del campo, entonces la entrada está fuera del campo (por ejemplo, %2i para un valor de 100 dará un valor de campo de tres caracteres), si la representación del valor es menor que el número especificado, luego se rellenará (por defecto) con espacios a la izquierda, el comportamiento puede variar dependiendo de otras banderas configuradas. Si se especifica un asterisco como ancho, el ancho del campo se especifica en la lista de argumentos antes del valor de salida (por ejemplo, printf( "%0*x", 8, 15 );mostrará texto 0000000f). Si se especifica un modificador de ancho negativo de esta manera, el indicador - se considera establecido y el valor del modificador de ancho se establece en absoluto.
Modificador de precisiónLa precisión se especifica como un punto seguido de un número decimal o un asterisco ( * ), si no hay ningún número o asterisco (solo hay un punto), se supone que el número es cero. Se utiliza un punto para indicar precisión incluso si se muestra una coma al generar números de punto flotante.
Si se especifica un carácter de asterisco después del punto, al procesar la cadena de formato, el valor del campo se lee de la lista de argumentos. (Al mismo tiempo, si el carácter de asterisco está tanto en el campo de ancho como en el campo de precisión, primero se indica el ancho, luego la precisión y solo luego el valor de salida). Por ejemplo, printf( "%0*.*f", 8, 4, 2.5 );mostrará el texto 002.5000. Si se especifica un modificador de precisión negativo de esta manera, entonces no hay modificador de precisión. [19]
Modificador de tamañoEl campo de tamaño le permite especificar el tamaño de los datos pasados a la función. La necesidad de este campo se explica por las peculiaridades de pasar un número arbitrario de parámetros a una función en el lenguaje C: la función no puede determinar "independientemente" el tipo y el tamaño de los datos transferidos, por lo que la información sobre el tipo de parámetros y su el tamaño exacto debe pasarse explícitamente.
Teniendo en cuenta la influencia de las especificaciones de tamaño en el formato de los datos enteros, cabe señalar que en los lenguajes C y C++ hay una cadena de pares de tipos enteros con y sin signo que, en orden de tamaño no decreciente, son arreglado de la siguiente manera:
tipo firmado | tipo sin firmar |
---|---|
carácter firmado | carácter sin firmar |
firmado corto ( corto ) | int corto sin signo ( corto sin signo ) |
firmado int ( int ) | int sin firmar ( sin firmar ) |
int largo con signo ( largo ) | int largo sin signo ( largo sin signo ) |
firmado largo largo int ( largo largo ) | int largo largo sin signo ( largo largo sin signo ) |
Se desconocen los tamaños exactos de los tipos, con la excepción de los tipos de caracteres con signo y sin signo .
Los tipos emparejados firmados y sin firmar tienen el mismo tamaño, y los valores representables en ambos tipos tienen la misma representación en ellos.
El tipo char tiene el mismo tamaño que los tipos char firmado y char sin firmar y comparte un conjunto de valores representables con uno de esos tipos. Además, se supone que char es otro nombre para uno de estos tipos; tal suposición es aceptable para la presente consideración.
Además, C tiene el tipo _Bool , mientras que C++ tiene el tipo bool .
Al pasar argumentos a una función que no se corresponden con los parámetros formales en el prototipo de función (que son todos los argumentos que contienen valores de salida), estos argumentos se someten a promociones estándar , a saber:
Por lo tanto, las funciones printf no pueden tomar argumentos de tipo float , _Bool o bool , o tipos enteros menores que int o unsigned .
El conjunto de especificadores de tamaño utilizados depende del especificador de tipo (ver más abajo).
especificador | %d, %i, %o, %u, %x,%X | %n | Nota |
---|---|---|---|
perdido | int o int sin firmar | puntero a int | |
l | int largo o int largo sin signo | puntero a long int | |
hh | El argumento es de tipo int o int sin firmar , pero se fuerza a escribir char firmado o char sin firmar , respectivamente | puntero a char firmado | existen formalmente en C desde el estándar de 1999 y en C++ desde el estándar de 2011. |
h | El argumento es de tipo int o unsigned int , pero está obligado a escribir short int o unsigned short int , respectivamente | puntero a int corto | |
ll | int largo largo o int largo largo sin signo | puntero a long long int | |
j | intmax_t o uintmax_t | puntero a intmax_t | |
z | size_t (o tipo con signo de tamaño equivalente) | puntero a un tipo firmado equivalente en tamaño a size_t | |
t | ptrdiff_t (o un tipo sin firmar equivalente) | puntero a ptrdiff_t | |
L | __int64 o __int64 sin firmar | puntero a __int64 | Para Borland Builder 6 (el especificador llespera un número de 32 bits) |
Las especificaciones hy hhse utilizan para compensar las promociones de tipo estándar junto con las transiciones de tipos firmados a tipos sin firmar, o viceversa.
Por ejemplo, considere una implementación de C donde el tipo char está firmado y tiene un tamaño de 8 bits, el tipo int tiene un tamaño de 32 bits y se usa una forma adicional de codificar enteros negativos.
carácter c = 255 ; printf ( "%X" , c );Tal llamada producirá una salida FFFFFFFF, que puede no ser la esperada por el programador. De hecho, el valor de c es (char)(-1) y, después de la promoción de tipo, es -1 . La aplicación del formato %Xhace que el valor dado se interprete como sin signo, es decir, 0xFFFFFFFF .
carácter c = 255 ; printf ( "%X" , ( caracter sin firmar ) c ) ; carácter c = 255 ; printf ( "%hhX" , c );Estas dos llamadas tienen el mismo efecto y producen la salida FF. La primera opción le permite evitar la multiplicación de signos al promocionar el tipo, la segunda lo compensa ya "dentro" de la función printf .
especificador | %a, %A, %e, %E, %f, %F, %g,%G |
---|---|
perdido | doble |
L | largo doble |
especificador | %c | %s |
---|---|---|
perdido | El argumento es de tipo int o unsigned int , pero se fuerza a escribir char | char* |
l | El argumento es de tipo wint_t , pero está obligado a escribir wchar_t | wchar_t* |
El tipo indica no solo el tipo del valor (desde el punto de vista del lenguaje de programación C), sino también la representación específica del valor de salida (por ejemplo, los números se pueden mostrar en formato decimal o hexadecimal). Escrito como un solo carácter. A diferencia de otros campos, es obligatorio. El tamaño de salida máximo admitido de una sola secuencia de escape es, según los estándares, al menos 4095 caracteres; en la práctica, la mayoría de los compiladores admiten cantidades de datos sustancialmente mayores.
Escriba valores:
Dependiendo de la configuración regional actual , se pueden usar tanto una coma como un punto (y posiblemente otro símbolo) cuando se muestran números de punto flotante. El comportamiento de printf con respecto al carácter que separa la parte fraccionaria y la entera del número está determinado por la configuración regional en uso (más precisamente, la variable LC NUMERIC ). [veinte]
Macros especiales para un conjunto extendido de alias de tipos de datos enterosEl segundo estándar C (1999) proporciona un conjunto extendido de alias para tipos de datos enteros int N _t , uint N _t , int_least N _t , uint_least N _t , int_fast N _t , uint_fast N _t (donde N es la profundidad de bits requerida), intptr_t , uintptr_t , intmax_t , uintmax_t .
Cada uno de estos tipos puede o no coincidir con cualquiera de los tipos de enteros incorporados estándar. Hablando formalmente, al escribir código portátil, el programador no sabe de antemano qué especificación de tamaño estándar o extendido debe aplicar.
int64_t x = 100000000000 ; ancho int = 20 ; printf ( "%0*lli" , ancho , x ); Incorrecto, porque int64_t puede no ser lo mismo que long long int .Para poder inferir los valores de los objetos o expresiones de estos tipos de manera portátil y conveniente, la implementación define para cada uno de estos tipos un conjunto de macros cuyos valores son cadenas que combinan especificaciones de tamaño y tipo.
Los nombres de las macros son los siguientes:
Un par de tipos firmados y sin firmar | Nombre de la macro |
---|---|
int N_t y uint N_t _ _ | PRITN |
int_least N _t y uint_least N _t | PRITLEASTN |
int_fastN_t y uint_fastN_t _ _ _ _ | PRITFASTN |
intmax_t y uintmax_t | PRITMAX |
intptr_t y uintptr_t | PRITPTR |
Aquí T , es una de las siguientes especificaciones de tipo: d, i, u, o, x, X.
int64_t x = 100000000000 ; ancho int = 20 ; printf ( "%0*" PRIi64 , ancho , x ); La forma correcta de generar un valor de tipo int64_t en lenguaje C.Puede notar que los tipos intmax_t y uintmax_t tienen un especificador de tamaño estándar , por lo que lo más probable es jque la macro siempre se defina como . PRITMAX"jT"
Bajo el estándar Single UNIX (prácticamente equivalente al estándar POSIX ), se definen las siguientes adiciones a printf en relación con ISO C, bajo la extensión XSI (X/Open System Interface):
La biblioteca GNU C ( libc ) agrega las siguientes extensiones:
GNU libc admite el registro de tipo personalizado, lo que permite al programador definir el formato de salida para sus propias estructuras de datos. Para registrar un nuevo tipo , utilice la función
int register_printf_function (int type, printf_function handler-function, printf_arginfo_function arginfo-function), donde:
Además de definir nuevos tipos, el registro permite redefinir tipos existentes (como s , i ).
Microsoft Visual CMicrosoft Visual Studio para los lenguajes de programación C/C++ en el formato de la especificación printf (y otras funciones de la familia) proporciona las siguientes extensiones:
valor de campo | tipo de |
---|---|
I32 | firmado __int32 , sin firmar __int32 |
I64 | firmado __int64 , sin firmar __int64 |
yo | ptrdiff_t , tamaño_t |
w | equivalente a l para cadenas y caracteres |
El entorno matemático de Maple también tiene una función printf que tiene las siguientes características:
FormateoEjemplo:
> printf("%a =%A", `+`, `+`); `+` = + > printf("%a =%m", `+`, `+`); `+` = I"+f*6"F$6#%(incorporadoGF$"$Q"F$F$F$F"%*protegidoG ConclusiónLa función fprintf de Maple toma un descriptor de archivo (devuelto por fopen) o un nombre de archivo como primer argumento. En este último caso, el nombre debe ser del tipo "símbolo", si el nombre del archivo contiene puntos, entonces debe encerrarse entre comillas graves o convertirse con la función convertir (nombre_archivo, símbolo).
Las funciones de la familia printf toman una lista de argumentos y su tamaño como un parámetro separado (en la cadena de formato). Una discrepancia entre la cadena de formato y los argumentos pasados puede provocar un comportamiento impredecible, daños en la pila, ejecución de código arbitrario y destrucción de áreas de memoria dinámica. Muchas funciones de la familia se denominan "inseguras" ( del inglés unsafe ), ya que ni siquiera tienen la capacidad teórica de proteger contra datos incorrectos.
Además, las funciones de la familia s (sin n , como sprintf , vsprintf ) no tienen límites en el tamaño máximo de la cadena escrita y pueden generar un error de desbordamiento del búfer (cuando los datos se escriben fuera del área de memoria asignada).
Como parte de la convención de llamada cdecl , la función de llamada realiza la limpieza de la pila . Cuando se llama a printf , los argumentos (o punteros a ellos) se colocan en el orden en que están escritos (de izquierda a derecha). A medida que se procesa la cadena de formato, la función printf lee los argumentos de la pila. Las siguientes situaciones son posibles:
Las especificaciones del lenguaje C describen solo dos situaciones (operación normal y argumentos adicionales). Todas las demás situaciones son erróneas y conducen a un comportamiento indefinido del programa (en realidad, conducen a resultados arbitrarios, hasta la ejecución de secciones de código no planificadas).
Demasiados argumentosAl pasar una cantidad excesiva de argumentos, la función printf lee los argumentos necesarios para procesar correctamente la cadena de formato y vuelve a la función de llamada. La función que llama, de acuerdo con la especificación, borra la pila de los parámetros pasados a la función llamada. En este caso, los parámetros adicionales simplemente no se utilizan y el programa continúa sin cambios.
No hay suficientes argumentosSi hay menos argumentos en la pila al llamar a printf de los necesarios para procesar la cadena de formato, los argumentos que faltan se leen de la pila, a pesar de que hay datos arbitrarios en la pila (no relevantes para el trabajo de printf ) . Si el procesamiento de datos fue "exitoso" (es decir, no finalizó el programa, no se bloqueó ni escribió en la pila), después de regresar a la función de llamada, el valor del puntero de la pila vuelve a su valor original y el el programa continúa.
Al procesar valores de pila "extra", son posibles las siguientes situaciones:
Formalmente, cualquier discrepancia entre el tipo de argumento y la expectativa provoca un comportamiento indefinido del programa. En la práctica, hay varios casos que son particularmente interesantes desde el punto de vista de la práctica de la programación:
Otros casos, por regla general, conducen a un comportamiento obviamente incorrecto y se detectan fácilmente.
Discrepancia en el tamaño del argumento de punto flotante o enteroPara un argumento de número entero (con una especificación de formato de número entero), son posibles las siguientes situaciones:
Para un argumento real (con una especificación de formato real), para cualquier discrepancia de tamaño, el valor de salida, como regla, no coincide con el valor pasado.
Como regla general, si el tamaño de cualquier argumento es incorrecto, el procesamiento correcto de todos los argumentos posteriores se vuelve imposible, ya que se introduce un error en el puntero a los argumentos. Sin embargo, este efecto se puede compensar alineando valores en la pila.
Alineación de valores en la pilaMuchas plataformas tienen reglas de alineación de valores reales y/o enteros que requieren (o recomiendan) que se coloquen en direcciones que son múltiplos de su tamaño. Estas reglas también se aplican a pasar argumentos de función en la pila. En este caso, una serie de desajustes en los tipos de parámetros esperados y reales pueden pasar desapercibidos, creando la ilusión de un programa correcto.
uint32_t a = 1 ; uint64_t segundo = 2 , c = 3 ; printf ( "%" PRId64 "%" PRId64 "%" PRId64 , b , a , c ); En este ejemplo, el parámetro de atipo real tiene uint32_tuna especificación de formato no válida asociada %"PRId64"con el tipo uint64_t. Sin embargo, en algunas plataformas con un tipo de 32 bits int, según el orden de bytes aceptado y la dirección de crecimiento de la pila, el error puede pasar desapercibido. Los parámetros reales by cse alinearán en una dirección que sea un múltiplo de su tamaño (el doble del tamaño de a). Y “entre” los valores a, bquedará un espacio vacío (normalmente puesto a cero) de 32 bits de tamaño; cuando se procesa la lista de materiales, el %"PRId64"valor de 32 bits a, junto con este espacio en blanco, se interpretará como un único valor de 64 bits.Tal error puede aparecer inesperadamente al transferir el código del programa a otra plataforma, cambiar el compilador o el modo de compilación.
Discrepancia de tamaño potencialLas definiciones de los lenguajes C y C++ describen solo los requisitos más generales para el tamaño y la representación de los tipos de datos. Por lo tanto, en muchas plataformas, la representación de algunos tipos de datos formalmente diferentes resulta ser la misma. Esto hace que algunas discrepancias de tipo pasen desapercibidas durante mucho tiempo.
Por ejemplo, en la plataforma Win32, generalmente se acepta que los tamaños de los tipos inty long intson los mismos (32 bits). Así, la llamada printf("%ld", 1)o printf("%d", 1L)se ejecutará "correctamente".
Tal error puede aparecer inesperadamente al transferir el código del programa a otra plataforma, cambiar el compilador o el modo de compilación.
Al escribir programas en el lenguaje C++, se debe tener cuidado al derivar los valores de las variables declaradas usando alias de tipo entero, en particular size_t, y ptrdiff_t; la definición formal de la biblioteca estándar de C++ se refiere al primer estándar de C (1990). El Segundo Estándar C (1999) define especificadores de tamaño para tipos size_ty para una serie de otros tipos para usar con objetos similares. ptrdiff_tMuchas implementaciones de C++ también las admiten.
tamaño_t s = 1 ; printf ( "%u" , s ); Este ejemplo contiene un error que puede ocurrir en plataformas sizeof (unsigned int)donde sizeof (size_t). tamaño_t s = 1 ; printf ( "%zu" , s ); La forma correcta de inferir el valor de un objeto tipo es size_ten lenguaje C. No coincide el tipo cuando el tamaño coincideSi los argumentos pasados son del mismo tamaño pero tienen un tipo diferente, entonces el programa a menudo se ejecutará "casi correctamente" (no causará errores de acceso a la memoria), aunque es probable que el valor de salida no tenga sentido. Cabe señalar que la combinación de tipos enteros emparejados (con signo y sin signo) está permitida, no provoca un comportamiento indefinido y, a veces, se usa deliberadamente en la práctica.
Cuando se utiliza una especificación de formato %s, un valor de argumento de tipo entero, real o de puntero distinto de char*, se interpretará como la dirección de una cadena. Esta dirección, en términos generales, puede apuntar arbitrariamente a un área de memoria inexistente o inaccesible, lo que provocará un error de acceso a la memoria, o a un área de memoria que no contiene una línea, lo que generará una salida sin sentido, posiblemente muy grande. .
Dado que printf (y otras funciones de la familia) pueden generar el texto de la cadena de formato sin cambios, si no contiene secuencias de escape, entonces el texto generado por el comando es posible .
printf(text_to_print);
Si text_to_print se obtiene de fuentes externas (leer de un archivo , recibido del usuario o del sistema operativo), la presencia de un signo de porcentaje en la cadena resultante puede tener consecuencias extremadamente indeseables (hasta la congelación del programa).
Ejemplo de código incorrecto:
printf(" Current status: 99% stored.");
este ejemplo contiene una secuencia de escape "% s" que contiene el carácter de secuencia de escape (%), una marca (espacio) y un tipo de datos de cadena ( s ). La función, habiendo recibido la secuencia de control, intentará leer el puntero a la cadena desde la pila. Dado que no se pasaron parámetros adicionales a la función, el valor que se leerá de la pila no está definido. El valor resultante se interpretará como un puntero a una cadena terminada en nulo. La salida de tal "cadena" puede conducir a un volcado de memoria arbitrario, un error de acceso a la memoria y una corrupción de pila. Este tipo de vulnerabilidad se denomina ataque de cadena de formato . [21]
La función printf , al generar un resultado, no está limitada por el número máximo de caracteres de salida. Si por error o descuido se muestran más caracteres de los esperados, lo peor que puede pasar es la “destrucción” de la imagen en pantalla. Creada por analogía con printf , la función sprintf tampoco estaba limitada en el tamaño máximo de la cadena resultante. Sin embargo, a diferencia del terminal "infinito", la memoria que la aplicación asigna para la cadena resultante siempre es limitada. Y en caso de ir más allá de los límites esperados, la grabación se realiza en áreas de memoria pertenecientes a otras estructuras de datos (o, en general, en áreas de memoria inaccesibles, lo que hace que el programa se cuelgue en casi todas las plataformas). Escribir en áreas arbitrarias de la memoria conduce a efectos impredecibles (que pueden aparecer mucho más tarde y no en forma de un error de programa, sino en forma de corrupción de datos del usuario). La falta de un límite en el tamaño máximo de la cadena es un error de planificación fundamental al desarrollar una función. Es por esto que las funciones sprintf y vsprintf tienen el estado inseguro . En su lugar, desarrolló las funciones snprintf , vsnprintf , que toman un argumento adicional que limita la cadena máxima resultante. La función swprintf , que apareció mucho más tarde (para trabajar con codificaciones de varios bytes), tiene en cuenta esta deficiencia y utiliza un argumento para limitar la cadena resultante. (Es por eso que no hay una función snwprintf ).
Un ejemplo de una llamada peligrosa a sprintf :
búfer de carbón[65536]; char* nombre = get_user_name_from_keyboard(); sprintf(búfer, "Nombre de usuario:%s", nombre);El código anterior asume implícitamente que el usuario no escribirá 65 mil caracteres en el teclado y que el búfer "debería ser suficiente". Pero el usuario puede redirigir la entrada desde otro programa o aún ingresar más de 65,000 caracteres. En este caso, las áreas de memoria se dañarán y el comportamiento del programa se volverá impredecible.
Las funciones de la familia printf utilizan tipos de datos C. Los tamaños de estos tipos y sus proporciones pueden variar de una plataforma a otra. Por ejemplo, en plataformas de 64 bits, según el modelo elegido ( LP64 , LLP64 o ILP64 ), los tamaños de los tipos int y long pueden diferir. Si el programador establece la cadena de formato en "casi correcta", el código funcionará en una plataforma y dará un resultado incorrecto en otra (en algunos casos, posiblemente provocando daños en los datos).
Por ejemplo, el código printf( "text address: 0x%X", "text line" );funciona correctamente en una plataforma de 32 bits ( tamaño de ptrdiff_t y tamaño de int de 32 bits) y en un modelo IPL64 de 64 bits (donde los tamaños de ptrdiff_t e int son de 64 bits), pero dará un resultado incorrecto en un modelo de 64 bits. Plataforma de bits de un modelo LP64 o LLP64, donde el tamaño de ptrdiff_t es de 64 bits y el tamaño de int es de 32 bits. [22]
En Oracle Java , los tipos envueltos con identificación dinámicaprintf se utilizan en el análogo de una función , [6] en Embarcadero Delphi - una capa intermedia , [23] en varias implementaciones en C ++ [24] - sobrecarga de operaciones , en C + + 20 - plantillas variables. Además, los formatos ( , etc.) no especifican el tipo de argumento, sino solo el formato de salida, por lo que cambiar el tipo de argumento puede causar una emergencia o romper la lógica de alto nivel (por ejemplo, "romper" el diseño de la mesa) - pero no estropear la memoria. array of const%d%f
El problema se ve agravado por la estandarización insuficiente de las cadenas de formato en diferentes compiladores: por ejemplo, las versiones anteriores de las bibliotecas de Microsoft no eran compatibles "%lld"(había que especificar "%I64d"). Todavía existe una división entre Microsoft y GNU por tipo size_t: %Iuel primero y %zuel segundo. GNU C no requiere una swprintflongitud máxima de cadena en una función (tiene que escribir snwprintf).
Las funciones de la familia printfson convenientes para la localización de software : por ejemplo, es más fácil de traducir «You hit %s instead of %s.»que los fragmentos de «You hit »cadena « instead of »y «.». Pero aquí también hay un problema: es imposible reorganizar las cadenas sustituidas en lugares para obtener: «Вы попали не в <2>, а в <1>.».
Las extensiones printfutilizadas en Oracle Java y Embarcadero Delphi aún le permiten reorganizar los argumentos.
Dentro del estándar POSIX , se describe la utilidad printf , que da formato a los argumentos según el patrón apropiado, similar a la función printf .
La utilidad tiene el siguiente formato de llamada: , donde printf format [argument …]