string de formato printf - printf format string

Um exemplo da função printf

string de formato printf refere-se a um parâmetro de controle usado por uma classe de funções nas bibliotecas de entrada / saída de C e muitas outras linguagens de programação . A string é escrita em uma linguagem de modelo simples : os caracteres geralmente são copiados literalmente na saída da função, mas os especificadores de formato , que começam com um %caractere, indicam o local e o método para traduzir um dado (como um número) em caracteres.

"printf" é o nome de uma das principais funções de saída C, e significa " f impressão ormatted". As strings de formato printf são complementares às strings de formato scanf , que fornecem entrada formatada ( análise ). Em ambos os casos, eles fornecem funcionalidade simples e formato fixo em comparação com mecanismos ou analisadores de modelo mais sofisticados e flexíveis, mas são suficientes para muitos propósitos.

Muitas linguagens diferentes de C copiam a sintaxe da string de formato printf de forma próxima ou exata em suas próprias funções de E / S.

As incompatibilidades entre os especificadores de formato e o tipo de dados podem causar travamentos e outras vulnerabilidades. A string de formato em si é muitas vezes uma string literal , o que permite a análise estática da chamada de função. No entanto, também pode ser o valor de uma variável, o que permite a formatação dinâmica, mas também uma vulnerabilidade de segurança conhecida como exploração de string de formato não controlado .

História

As primeiras linguagens de programação, como Fortran, usavam instruções especiais com sintaxe completamente diferente de outros cálculos para construir descrições de formatação. Neste exemplo, o formato é especificado na linha 601 e o comando WRITE se refere a ele pelo número da linha:

      WRITE OUTPUT TAPE 6, 601, IA, IB, IC, AREA
 601  FORMAT (4H A= ,I5,5H  B= ,I5,5H  C= ,I5,
     &        8H  AREA= ,F10.2, 13H SQUARE UNITS)

ALGOL 68 tinha mais APIs semelhantes a funções , mas ainda usava sintaxe especial (os $delimitadores circundam a sintaxe de formatação especial):

printf(($"Color "g", number1 "6d,", number2 "4zd,", hex "16r2d,", float "-d.2d,", unsigned value"-3d"."l$,
         "red", 123456, 89, BIN 255, 3.14, 250));

Mas usar as chamadas de função normais e tipos de dados simplifica a linguagem e o compilador, e permite que a implementação da entrada / saída seja escrita na mesma linguagem. Essas vantagens superam as desvantagens (como uma completa falta de segurança de tipo em muitos casos) e na maioria das linguagens mais recentes, I / O não faz parte da sintaxe.

C? S printftem as suas origens em BCPL de writeffunção (1966). Em comparação com Ce printf, *Né uma sequência de escape de linguagem BCPL que representa um caractere de nova linha (para o qual C usa a sequência de escape \n) e a ordem da largura e tipo do campo de especificação de formato é invertida em writef:

WRITEF("%I2-QUEENS PROBLEM HAS %I5 SOLUTIONS*N", NUMQUEENS, COUNT)

Provavelmente, a primeira cópia da sintaxe fora da linguagem C foi o printfcomando shell Unix , que apareceu pela primeira vez na versão 4 , como parte da porta para C.

Especificação de espaço reservado de formato

A formatação ocorre por meio de marcadores na string de formato. Por exemplo, se um programa quisesse imprimir a idade de uma pessoa, ele poderia apresentar a saída prefixando "Sua idade é" e usando o caractere especificador decimal dcom sinal para indicar que queremos que o número inteiro da idade seja mostrado imediatamente depois dessa mensagem, podemos usar a string de formato:

printf("Your age is %d", age);

Sintaxe

A sintaxe para um espaço reservado de formato é

% [ parâmetro ] [ sinalizadores ] [ largura ] [. precisão ] [ comprimento ] tipo

Campo de parâmetro

Esta é uma extensão POSIX e não em C99 . O campo Parâmetro pode ser omitido ou pode ser:

Personagem Descrição
n $ n é o número do parâmetro a ser exibido usando este especificador de formato, permitindo que os parâmetros fornecidos sejam produzidos várias vezes, usando especificadores de formato variados ou em ordens diferentes. Se qualquer espaço reservado especifica um parâmetro, todos os demais espaços reservados DEVEM também especificar um parâmetro.
Por exemplo, printf("%2$d %2$#x; %1$d %1$#x",16,17)produz 17 0x11; 16 0x10.

Esse recurso vê seu uso principalmente na localização, onde a ordem de ocorrência dos parâmetros varia devido à convenção dependente do idioma.

No Microsoft Windows não POSIX, o suporte para esse recurso é colocado em uma função printf_p separada.

Campo de bandeiras

O campo Sinalizadores pode ser zero ou mais (em qualquer ordem) de:

Personagem Descrição
-
(menos)
Alinhe à esquerda a saída deste espaço reservado. (O padrão é alinhar a saída à direita.)
+
(mais)
Precede um sinal de mais para tipos numéricos com sinal positivos. positivo = + , negativo = - .
(O padrão não precede nada antes de números positivos.)

(espaço)
Anexa um espaço para tipos numéricos com sinal positivos. positivo = , negativo = - . Este sinalizador será ignorado se o sinalizador + existir.
(O padrão não precede nada antes de números positivos.)
0
(zero)
Quando a opção 'largura' é especificada, zeros precede os tipos numéricos. (O padrão adiciona espaços.)
Por exemplo, printf("%4X",3)produz 3, enquanto printf("%04X",3)produz 0003.
'
(apóstrofo)
O inteiro ou expoente de um decimal tem o separador de agrupamento de milhares aplicado.
#
(hash)
Forma Alternativa:
Para g e G tipos, zeros à direita não são removidos.
Para os tipos f , F , e , E , g , G , a saída sempre contém um ponto decimal.
Para os tipos o , x , X , o texto 0 , 0x , 0X , respectivamente, é prefixado a números diferentes de zero.

Campo de largura

O campo Largura especifica um número mínimo de caracteres para a saída e é normalmente usado para preencher campos de largura fixa na saída tabulada, onde os campos seriam de outra forma menores, embora não cause o truncamento de campos grandes.

O campo de largura pode ser omitido, ou um valor numérico inteiro ou um valor dinâmico quando passado como outro argumento quando indicado por um asterisco * . Por exemplo, printf("%*d", 5, 10)resultará em 10impressão, com largura total de 5 caracteres.

Embora não faça parte do campo de largura, um zero é interpretado como a bandeira-preenchimento com zeros mencionado acima, e um valor negativo é tratado como o valor positivo em conjunto com o alinhamento à esquerda - bandeira também mencionado acima.

Campo de precisão

O campo Precisão geralmente especifica um limite máximo na saída, dependendo do tipo de formatação específico. Para tipos numéricos de ponto flutuante, especifica o número de dígitos à direita do ponto decimal que a saída deve ser arredondada. Para o tipo de string, ele limita o número de caracteres que devem ser produzidos, após o qual a string é truncada.

O campo de precisão pode ser omitido, ou um valor numérico inteiro ou um valor dinâmico quando passado como outro argumento quando indicado por um asterisco * . Por exemplo, printf("%.*s", 3, "abcdef")resultará em abcimpressão.

Campo de comprimento

O campo Comprimento pode ser omitido ou qualquer um dos seguintes:

Personagem Descrição
hh Para tipos inteiros, faz com que printf espere um argumento inteiro int -sized que foi promovido de um char .
h Para tipos inteiros, faz com que printf espere um argumento inteiro int -sized que foi promovido de um short .
eu Para tipos inteiros, faz com que printf espere um argumento inteiro de tamanho longo .

Para tipos de ponto flutuante, isso é ignorado. argumentos float são sempre promovidos a double quando usados ​​em uma chamada de varargs.

tudo Para tipos inteiros, faz com que printf espere um argumento inteiro longo de tamanho longo .
eu Para tipos de ponto flutuante, faz com que printf espere um argumento duplo longo .
z Para tipos inteiros, faz com que printf espere um argumento inteiro size_t -sized.
j Para tipos inteiros, faz com que printf espere um argumento inteiro intmax_t -sized.
t Para tipos inteiros, faz com que printf espere um argumento inteiro de tamanho ptrdiff_t .

Além disso, várias opções de comprimento específicas da plataforma surgiram antes do uso generalizado das extensões ISO C99:

Personagens Descrição
eu Para tipos inteiros assinados, faz com que printf espere o argumento de inteiro ptrdiff_t -sized; para tipos inteiros não assinados, faz com que printf espere um argumento inteiro size_t -sized. Normalmente encontrado em plataformas Win32 / Win64.
I32 Para tipos inteiros, faz com que printf espere um argumento inteiro de 32 bits (palavra dupla). Normalmente encontrado em plataformas Win32 / Win64.
I64 Para tipos inteiros, faz com que printf espere um argumento inteiro de 64 bits (palavra quádrupla). Normalmente encontrado em plataformas Win32 / Win64.
q Para tipos inteiros, faz com que printf espere um argumento inteiro de 64 bits (palavra quádrupla). Normalmente encontrado em plataformas BSD.

ISO C99 inclui o inttypes.harquivo de cabeçalho que inclui várias macros para uso em printfcodificação independente de plataforma . Eles devem estar fora das aspas duplas, por exemploprintf("%" PRId64 "\n", t);

Macros de exemplo incluem:

Macro Descrição
PRId32 Normalmente equivalente a I32d ( Win32 / Win64 ) ou d
PRId64 Normalmente equivalente a I64d ( Win32 / Win64 ), lld ( plataformas de 32 bits ) ou ld ( plataformas de 64 bits )
PRIi32 Normalmente equivalente a I32i ( Win32 / Win64 ) ou i
PRIi64 Normalmente equivalente a I64i ( Win32 / Win64 ), lli ( plataformas de 32 bits ) ou li ( plataformas de 64 bits )
PRIu32 Normalmente equivalente a I32u ( Win32 / Win64 ) ou u
PRIu64 Normalmente equivalente a I64u ( Win32 / Win64 ), llu ( plataformas de 32 bits ) ou lu ( plataformas de 64 bits )
PRIx32 Normalmente equivalente a I32x ( Win32 / Win64 ) ou x
PRIx64 Normalmente equivalente a I64x ( Win32 / Win64 ), llx ( plataformas de 32 bits ) ou lx ( plataformas de 64 bits )

Campo de tipo

O campo Tipo pode ser qualquer um dos seguintes:

Personagem Descrição
% Imprime um caractere % literal (este tipo não aceita campos de sinalizadores, largura, precisão, comprimento).
d , eu int como um inteiro assinado . % d e % i são sinônimos para saída, mas são diferentes quando usados ​​com scanfpara entrada (onde usar % i interpretará um número como hexadecimal se for precedido por 0x e octal se for precedido por 0 ).
você Imprime int decimais sem sinal .
f , F duplo na notação normal ( ponto fixo ). f e F apenas diferem em como as strings para um número infinito ou NaN são impressas ( inf , infinito e nan para f ; INF , INFINITY e NAN para F ).
e , E valor duplo na forma padrão ( d . ddd e ± dd ). Uma conversão E usa a letra E (em vez de e ) para introduzir o expoente. O expoente sempre contém pelo menos dois dígitos; se o valor for zero, o expoente é 00 . No Windows, o expoente contém três dígitos por padrão, por exemplo , 1.5e002 , mas isso pode ser alterado por _set_output_formatfunção específica da Microsoft .
g , G dobro em notação normal ou exponencial, o que for mais apropriado para sua magnitude. g usa letras minúsculas, G usa letras maiúsculas. Este tipo difere ligeiramente da notação de ponto fixo em que zeros insignificantes à direita da casa decimal não são incluídos. Além disso, o ponto decimal não é incluído em números inteiros.
x , X int sem sinal como um número hexadecimal . x usa letras minúsculas e X usa letras maiúsculas.
o int sem sinal em octal.
s string terminada em nulo .
c char (personagem).
p void * (ponteiro para void) em um formato definido pela implementação.
a , A duplo em notação hexadecimal, começando com 0x ou 0X . a usa letras minúsculas, A usa letras maiúsculas. (C ++ 11 iostreams tem um hexfloat que funciona da mesma forma).
n Não imprime nada, mas escreve o número de caracteres escritos até agora em um parâmetro de ponteiro inteiro.
Em Java, isso imprime uma nova linha.

Marcadores de posição de formato personalizado

Existem algumas implementações de printf-como funções que permitem extensões para o escapar caracteres baseados em mini-linguagem , permitindo assim que o programador ter uma função de formatação específica para tipos não-embutidas. Um dos mais conhecidos é o (agora obsoleto) glibc 's register_printf_function(). No entanto, raramente é usado devido ao fato de que entra em conflito com a verificação estática de strings de formato. Outro são os formatadores personalizados Vstr , que permitem adicionar nomes de formato com vários caracteres.

Alguns aplicativos (como o Apache HTTP Server ) incluem sua própria printffunção semelhante a ela e incorporam extensões a ela. No entanto, todos estes tendem a ter os mesmos problemas que register_printf_function()tem.

A função do kernel do Linux printk oferece suporte a várias maneiras de exibir estruturas do kernel usando a %pespecificação genérica , acrescentando caracteres de formato adicionais. Por exemplo, %pI4imprime um endereço IPv4 no formato decimal com pontos. Isso permite a verificação da string de formato estático (da %pparte) às custas da compatibilidade total com o printf normal.

A maioria das linguagens que têm uma printffunção semelhante a - contorna a falta desse recurso apenas usando o %sformato e convertendo o objeto em uma representação de string.

Vulnerabilidades

Especificações de conversão inválidas

Se houver poucos argumentos de função fornecidos para fornecer valores para todas as especificações de conversão na string do modelo, ou se os argumentos não forem dos tipos corretos, os resultados são indefinidos e podem falhar. As implementações são inconsistentes sobre se os erros de sintaxe na string consomem um argumento e que tipo de argumento eles consomem. Excesso de argumentos são ignorados. Em vários casos, o comportamento indefinido levou a vulnerabilidades de segurança de " ataque de string de formato " . Na maioria das convenções de chamada C ou C ++, os argumentos podem ser passados ​​na pilha, o que significa que, no caso de poucos argumentos, printf irá ler além do final do stackframe atual, permitindo assim que o invasor leia a pilha.

Alguns compiladores, como o GNU Compiler Collection , verificarão estaticamente as strings de formato de funções do tipo printf e alertarão sobre problemas (ao usar os sinalizadores -Wallou -Wformat). O GCC também avisará sobre funções de estilo printf definidas pelo usuário se o "formato" não padrão __attribute__for aplicado à função.

Largura do campo versus delimitadores explícitos na saída tabular

Usar apenas larguras de campo para fornecer tabulação, como %8d%8d%8dacontece com um formato como para três inteiros em três colunas de 8 caracteres, não garante que a separação de campos será mantida se grandes números ocorrerem nos dados. A perda de separação de campo pode facilmente levar a uma saída corrompida. Em sistemas que encorajam o uso de programas como blocos de construção em scripts, esses dados corrompidos podem frequentemente ser encaminhados e corrompidos em processamento adicional, independentemente de o programador original esperar que a saída só seja lida por olhos humanos. Esses problemas podem ser eliminados incluindo delimitadores explícitos, até mesmo espaços, em todos os formatos de saída tabular. Simplesmente alterar o exemplo anterior para %7d %7d %7dresolver isso, formatando de forma idêntica até que os números se tornem maiores, mas, em seguida, evitando explicitamente que eles sejam mesclados na saída devido aos espaços incluídos explicitamente. Estratégias semelhantes se aplicam a dados de string.

Gravação de memória

Embora seja uma função de saída na superfície, printfpermite a gravação em um local da memória especificado por um argumento via %n. Esta funcionalidade é ocasionalmente usada como parte de ataques de strings de formato mais elaborados.

A %nfuncionalidade também torna printfacidentalmente Turing completa, mesmo com um conjunto de argumentos bem formado. Um jogo de jogo da velha escrito na string de formato é o vencedor do 27º IOCCC .

Linguagens de programação com printf

Linguagens que usam strings de formato que divergem do estilo neste artigo (como AMPL e Elixir ), linguagens que herdam sua implementação da JVM ou outro ambiente (como Clojure e Scala ) e linguagens que não têm um printf nativo padrão implementação, mas têm bibliotecas externas que emulam o comportamento de printf (como JavaScript ) não estão incluídas nesta lista.

Veja também

Referências

links externos