Imprimir

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 5 de abril de 2015; las comprobaciones requieren 72 ediciones .

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.

Historia

Apariencia

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

C y derivados

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 .

Uso en otros lenguajes de programación

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.).

Seguidores

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.

Nomenclatura de funciones familiares

Todas las funciones tienen la raíz printf en sus nombres . Los prefijos antes del nombre de la función significan:

Convenciones generales

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.

Descripción de funciones

Nombres de parámetros

Descripción de funciones

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.

Sintaxis de cadena de formato

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 %%.

Estructura del especificador de formato

El especificador de formato se parece a:

% [ banderas ] [ ancho ] [ . precisión ][ tamaño ] tipo

Los componentes necesarios son el carácter de inicio del especificador de formato ( %) y el tipo .

Banderas
Señ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.

Modificador de ancho

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ón
  • indica el número mínimo de caracteres que deben aparecer al procesar los tipos d , i , o , u , x , X ;
  • indica el número mínimo de caracteres que deben aparecer después del punto decimal (punto) cuando se procesan los tipos a , A , e , E , f , F ;
  • el número máximo de caracteres significativos para los tipos g y G ;
  • el número máximo de caracteres que se imprimirán para el tipo s ;

La 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ño

El 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:

  • los argumentos flotantes se convierten en double ;
  • los argumentos de tipo char unsigned , short unsigned , char firmado y short se convierten en uno de los siguientes tipos:
    • int si este tipo es capaz de representar todos los valores del tipo original, o
    • sin firmar de otra manera;
  • los argumentos de tipo _Bool o bool se convierten en tipo int .

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*
Especificador de tipo

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:

  • d , i  : número decimal con signo, el tipo predeterminado es int . Por defecto se escribe con alineación a la derecha, el signo se escribe solo para números negativos. A diferencia de las funciones de la familia scanf , para las funciones de la familia printf , las especificaciones %d y %i son completamente sinónimas;
  • o  — número octal sin signo, el tipo predeterminado es int sin signo ;
  • u  es un número decimal sin signo, el tipo predeterminado es int sin signo ;
  • x y X  son números hexadecimales sin signo, x usa letras minúsculas (abcdef), X usa letras grandes (ABCDEF), el tipo predeterminado es int sin signo ;
  • f y F  son números de coma flotante, el tipo predeterminado es double . Por defecto, se emiten con una precisión de 6, si el número de módulo es menor que uno, se escribe un 0 antes del punto decimal.Los valores de ±∞ se presentan en la forma [-]inf o [-]infinito (dependiendo de la plataforma); el valor de Nan se representa como [-]nan o [-]nan (cualquier texto a continuación) . El uso de F imprime los valores especificados en letras mayúsculas ( [-]INF , [-]INFINITY , NAN ).
  • e y E  son números de coma flotante en notación exponencial (de la forma 1.1e+44), el tipo predeterminado es double . e genera el carácter "e" en minúsculas, E  - en mayúsculas (3.14E+0);
  • g y G  es un número de punto flotante, el tipo predeterminado es doble . La forma de representación depende del valor de la cantidad ( f o e ). El formato difiere ligeramente del punto flotante en que no se generan los ceros iniciales a la derecha del punto decimal. Además, la parte de punto y coma no se muestra si el número es un número entero;
  • a y A (a partir de los estándares del lenguaje C de 1999 y C++ de 2011): un número de punto flotante en forma hexadecimal, el tipo predeterminado es doble ;
  • c  — salida del símbolo con el código correspondiente al argumento pasado, el tipo por defecto es int ;
  • s  - salida de una cadena con un byte de terminación nulo; si el modificador de longitud es l , se emite la cadena wchar_t* . En Windows, los valores de tipo s dependen del tipo de funciones utilizadas. Si se usa una familia de printffunciones, entonces s denota la cadena char* . Si se usa una familia de wprintffunciones, entonces s denota la cadena wchar_t* .
  • S  es lo mismo que s con el modificador de longitud l ; En Windows, el valor del tipo S depende del tipo de funciones utilizadas. Si se usa una familia de printffunciones, entonces S representa la cadena wchar_t* . Si se usa una familia de wprintffunciones, entonces S denota la cadena char* .
  •  Salida de puntero p , la apariencia puede variar significativamente dependiendo de la representación interna en el compilador y la plataforma (por ejemplo, la plataforma MS-DOS de 16 bits usa la notación de la forma FFEC:1003, la plataforma de 32 bits con direccionamiento plano usa la dirección de la forma 00FA0030);
  • n  - registro por puntero, pasado como argumento, el número de caracteres escritos en el momento de la ocurrencia de la secuencia de comandos que contiene n ;
  • %  : carácter para mostrar el signo de porcentaje (%), que se usa para habilitar la salida de caracteres de porcentaje en la cadena printf, siempre se usa en el formulario %%.
Salida de números de coma flotante

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 enteros

El 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"

Extensiones XSI en el estándar único de Unix

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):

  • Se agrega la capacidad de generar un parámetro arbitrario por número (indicado n$inmediatamente después del carácter del comienzo de la secuencia de control, por ejemplo, printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);).
  • Se agregó el indicador "'" (comilla simple), que para los tipos d , i , o , u prescribe separar las clases con el carácter correspondiente.
  • escriba C equivalente a lc ISO C (salida de caracteres de tipo wint_t ).
  • escriba S equivalente a ls ISO C (salida de cadena como wchar_t* )
  • Se agregaron códigos de error EILSEQ, EINVAL, ENOMEM, EOVERFLOW.

Extensiones no estándar

Biblioteca GNU C

La biblioteca GNU C ( libc ) agrega las siguientes extensiones:

  • type m imprime el valor de la variable global errno (el código de error de la última función).
  • el tipo C es equivalente a lc .
  • la bandera ' (comilla simple) se usa para separar clases al imprimir números. El formato de separación depende de LC_NUMERIC
  • el tamaño de q indica el tipo long long int (en sistemas donde no se admite long long int , esto es lo mismo que long int
  • size Z es un alias de z , se introdujo en libc antes de la llegada del estándar C99 y no se recomienda su uso en código nuevo.
Registro de sus propios tipos

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:

  • type  — letra para el tipo (si type = 'Y', entonces la llamada se verá como '%Y');
  • función-manejador  : un puntero a una función a la que llaman las funciones printf si el tipo especificado en tipo se encuentra en la cadena de formato ;
  • arginfo-function  es un puntero a una función que será llamada por la función parse_printf_format .

Además de definir nuevos tipos, el registro permite redefinir tipos existentes (como s , i ).

Microsoft Visual C

Microsoft 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:

  • caja de tamaño:
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
arce

El entorno matemático de Maple también tiene una función printf que tiene las siguientes características:

Formateo
    • %a, %A: el objeto Maple se devolverá en notación de texto, esto funciona para todos los objetos (por ejemplo, matrices, funciones, módulos, etc.). La letra minúscula indica rodear los caracteres (nombres) con acentos graves que deben estar rodeados de acentos graves en la entrada de printf.
    • %q, %Q: igual que %a/%A, pero no solo se procesará un argumento, sino todo a partir del que coincida con el indicador de formato. Por lo tanto, el indicador %Q/%q solo puede aparecer en último lugar en la cadena de formato.
    • %m: da formato al objeto de acuerdo con su representación interna de Maple. Prácticamente se utiliza para escribir variables en un archivo.

Ejemplo:

> printf("%a =%A", `+`, `+`); `+` = + > printf("%a =%m", `+`, `+`); `+` = I"+f*6"F$6#%(incorporadoGF$"$Q"F$F$F$F"%*protegidoG Conclusión

La 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).

Vulnerabilidades

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).

Comportamiento cuando la cadena de formato y los argumentos pasados ​​no coinciden

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:

  • el número y tipo de argumentos coinciden con los especificados en la cadena de formato (operación de función normal)
  • más argumentos pasados ​​a la función que los especificados en la cadena de formato (argumentos adicionales)
  • Se pasan menos argumentos a la función que los requeridos por la cadena de formato (argumentos insuficientes)
  • Argumentos de tamaño incorrecto pasados ​​​​a la función
  • Se pasaron argumentos del tamaño correcto pero del tipo incorrecto a la función

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 argumentos

Al 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 argumentos

Si 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:

  • Lectura exitosa de un parámetro "extra" para salida (número, puntero, símbolo, etc.) - el valor "casi aleatorio" leído de la pila se coloca en los resultados de salida. Esto no representa un peligro para el funcionamiento del programa, pero puede comprometer algunos datos (salida de valores de pila que un atacante puede usar para analizar el funcionamiento del programa y obtener acceso a la información interna/privada del programa).
  • Un error al leer un valor de la pila (por ejemplo, como resultado de agotar los valores de pila disponibles o acceder a páginas de memoria "inexistentes"): es muy probable que dicho error provoque que el programa se bloquee.
  • Leer un puntero a un parámetro. Las cadenas se pasan usando un puntero, cuando se lee información "arbitraria" de la pila, el valor de lectura (casi aleatorio) se usa como un puntero a un área de memoria aleatoria. El comportamiento del programa en este caso no está definido y depende del contenido de esta área de memoria.
  • Escribir un parámetro con un puntero ( %n): en este caso, el comportamiento es similar a la situación con la lectura, pero se complica por los posibles efectos secundarios de escribir en una celda de memoria arbitraria.
Discrepancia en el tipo de argumento

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:

  • El argumento es del mismo tipo que el esperado, pero de diferente tamaño.
  • El argumento tiene el mismo tamaño que se esperaba, pero un tipo diferente.

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 entero

Para un argumento de número entero (con una especificación de formato de número entero), son posibles las siguientes situaciones:

  • Pasar parámetros que son más grandes de lo esperado (leyendo el más pequeño del más grande). En este caso, según el orden de bytes aceptado y la dirección de crecimiento de la pila, el valor mostrado puede coincidir con el valor del argumento o no estar relacionado con él.
  • Pasar parámetros que son más pequeños de lo esperado (leyendo más grande de más pequeño). En este caso, es posible una situación en la que se leen áreas de la pila que van más allá de los límites de los argumentos pasados. El comportamiento de la función en este caso es similar al comportamiento en una situación con falta de parámetros. En general, el valor de salida no coincide con el valor esperado.

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 pila

Muchas 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 potencial

Las 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 coincide

Si 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. .

Vulnerabilidad de cadena de formato

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]

Desbordamiento de búfer

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.

Dificultades en el uso

Falta de verificación de tipos

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

Falta de estandarización

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).

Incapacidad para reorganizar argumentos

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.

utilidad printf

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 …]

  • format  es una cadena de formato, similar en sintaxis a la cadena de formato de la función printf .
  • argumento  es una lista de argumentos (0 o más) escritos en forma de cadena.

Ejemplos de implementación

Ejemplo 1 C (lenguaje de programación)

#incluir <stdio.h> #include <locale.h> #define IP 3.141593 int principal () { setlocale ( LC_ALL , "RUS" ); numero entero = 7 ; tartas flotantes = 12,75 ; costo int = 7800 ; printf ( "%d concursantes comieron %f pasteles de cereza. \n " , numero , pasteles ); printf ( "El valor de pi es %f \n " , PI ); printf ( "¡Adiós! Tu arte cuesta demasiado (%c%d) \n " , '$' , 2 * costo ); devolver 0 ; }

Ejemplo 2 C (lenguaje de programación)

#incluir <stdio.h> #define PÁGINAS 959 int principal () { printf ( "*%d* \n " , PÁGINAS ); printf ( "*%2d* \n " , PÁGINAS ); printf ( "*%10d* \n " , PÁGINAS ); printf ( "*%-10d* \n " , PÁGINAS ); devolver 0 ; } /* Resultado: *959* *959* * 959* *959 * */

Ejemplo 3 C (lenguaje de programación)

#incluir <stdio.h> #define BLURB "¡Imitación auténtica!" int principal () { const doble RENTA = 3852.99 ; printf ( "*%8f* \n " , ALQUILER ); printf ( "*%e* \n " , ALQUILER ); printf ( "*%4.2f* \n " , ALQUILER ); printf ( "*%3.1f* \n " , ALQUILER ); printf ( "*%10.3f* \n " , ALQUILER ); printf ( "*%10.3E* \n " , ALQUILER ); printf ( "*%+4.2f* \n " , ALQUILER ); printf ( "%x %X %#x \n " , 31 , 31 , 31 ); printf ( "**%d**%d%d** \n " , 42 , 42 , -42 ); printf ( "**%5d**%5.3d**%05d**%05.3d** \n " , 6 , 6 , 6 , 6 ); imprimirf ( " \n " ); printf ( "[%2s] \n " , BLURB ); printf ( "[%24s] \n " , BLURB ); printf ( "[%24.5s] \n " , BLURB ); printf ( "[%-24.5s] \n " , BLURB ); devolver 0 ; } /* resultado *3852.990000* *3.852990e+03* *3852.99* *3853.0* * 3852.990* * 3.853E+03* *+3852.99* 1f 1F 0x1f **42** 42-42 ** ** 6** 006 **00006** 006** [¡Imitación auténtica!] [¡Imitación auténtica!] [Authe] [Authe ] */

Enlaces

  1. Breve descripción del lenguaje BCPL . Consultado el 16 de diciembre de 2006. Archivado desde el original el 9 de diciembre de 2006.
  2. B Language Guide Archivado el 6 de julio de 2006.
  3. Descripción de la función sprintf en la documentación de Perl . Consultado el 12 de enero de 2007. Archivado desde el original el 14 de enero de 2007.
  4. Una descripción del operador de formato para tipos de cadena en Python . Archivado el 9 de noviembre de 2006.
  5. Descripción de la función printf de PHP . Consultado el 23 de octubre de 2006. Archivado desde el original el 6 de noviembre de 2006.
  6. 1 2 Descripción de la función java.io.PrintStream.printf() en Java 1.5 . Consultado el 12 de enero de 2007. Archivado desde el original el 13 de enero de 2007.
  7. Descripción de la función printf en la documentación de Ruby . Consultado el 3 de diciembre de 2006. Archivado desde el original el 5 de diciembre de 2006.
  8. Descripción de la función string.format en la documentación de Lua . Fecha de acceso: 14 de enero de 2010. Archivado desde el original el 15 de noviembre de 2013.
  9. Descripción de la función de formato en la documentación de TCL . Consultado el 14 de abril de 2008. Archivado desde el original el 4 de julio de 2007.
  10. Descripción del patrón de cadena para printf en la documentación de GNU Octave . Consultado el 3 de diciembre de 2006. Archivado desde el original el 27 de octubre de 2006.
  11. Descripción de printf en la documentación de Maple{{subst:AI}}
  12. R. Fourer, D. M. Gay y B. W. Kernighan. AMPL: un lenguaje de modelado para la programación matemática, 2.ª edición Pacific Grove, CA: Brooks/Cole--Thomson Learning, 2003.
  13. Manual de referencia de GNU Emacs Lisp, Cadenas de formato Archivado el 27 de septiembre de 2007 en Wayback Machine .
  14. Descripción del módulo Printf en la documentación de OCaml . Consultado el 12 de enero de 2007. Archivado desde el original el 13 de enero de 2007.
  15. Descripción del módulo Printf en la documentación de Haskell . Consultado el 23 de junio de 2015. Archivado desde el original el 23 de junio de 2015.
  16. estándar::println! - Óxido . doc.rust-lang.org. Consultado el 24 de julio de 2016. Archivado desde el original el 18 de agosto de 2016.
  17. formato . www.freepascal.org. Consultado el 7 de diciembre de 2016. Archivado desde el original el 24 de noviembre de 2016.
  18. fmt: el lenguaje de programación Go . golang.org. Consultado el 25 de marzo de 2020. Archivado desde el original el 4 de abril de 2020.
  19. §7.19.6.1 ISO/IEC 9899:TC2
  20. § 7.11.1.1 ISO/IEC 9899:TC2, LC_NUMERIC define, en particular, la forma de representación del separador decimal.
  21. Descripción de la vulnerabilidad de Printf, Robert C. Seacord: Codificación segura en C y C++. Addison Wesley, septiembre de 2005. ISBN 0-321-33572-4
  22. Descripción de los problemas de portar aplicaciones de arquitectura de 32 a 64 bits . Consultado el 14 de diciembre de 2006. Archivado desde el original el 8 de marzo de 2007.
  23. System.SysUtils.Format Archivado el 11 de enero de 2013 en Wayback Machine . 
  24. Por ejemplo, boost::formatdocumentación archivada el 26 de marzo de 2013 en Wayback Machine . 

Fuentes

  • printf , fprintf , snprintf , vfprintf , vprintf , vsnprintf , vsprintf en ISO/IEC 9899:TC2 (ISO C) [3]
  • printf , fprintf , sprintf , snprintf en el estándar Single Unix [4]
  • vprintf , vfprintf , vsprintf , vsnprintf en el estándar POSIX [5]
  • wprintf , swprintf , wprintf en el estándar POSIX [6]
  • vfwprintf , vswprintf , vwprintf en el estándar POSIX [7]
  • wsprintf en MSDN [8]
  • wvnsprintf en MSDN [9]
  • wnsprintf en MSDN [10]
  • wvsprintf en MSDN [11]
  • wnsprintf en MSDN [12]
  • asprintf , vasprintf en man -pages en Linux [13] , en la documentación de libc [14]
  • Consulte el manual de libc [15] para obtener una descripción de la sintaxis de la cadena de formato .
  • Descripción de la cadena de formato en la documentación de Microsoft Visual Studio 2005 [16]
  • Descripción de la función register_printf_ [17] , [18]
  • Lenguaje de programación C. Lecciones y ejercicios. Autor: Esteban Prata. ISBN 978-5-8459-1950-2 , 978-0-321-92842-9; 2015

Véase también