Respostas aos meus leitores: A má compreendida função printf

Este post é um exemplo de reposta longa que darei no meu mais recente grupo no Facebook (C & ASM FAQ). Vou fazer de conta que alguém tenha me perguntado sobre o significado das “marcas de conversão” usadas na função printf (o “f” final vem de “formated”)… Essas marcas são aquelas sub strings começadas com “%” que seguem o padrão:

%[flag][width][.precision][modifier]specifier

Os termos entre colchetes são opcionais.

Quando printf encontra uma sub string começando com “%” ele verifica seu formato. Se um outro “%” seguir o primeiro, então o caracter ‘%’ é impresso, caso contrário, estamos dizendo à função para pegar o próximo argumento da função e convertê-lo para uma string, de acordo com o critério da marca.

Por exemplo:

printf("Valor: %d\n", x);

A marca “%d” diz ao printf que o próximo argumento (x) deve ser convertido para uma string numérica inteira e decimal. Se usássemos “%x”, ao invés de “%d”, o valor do próximo argumento seria convertido para uma string numérica inteira e hexadecimal, com os “algarismos” de ‘a’ até ‘f’ sendo mostrados em forma minúscula… Se substituirmos a marca por “%X” esses “algarismos” seriam mostrados de forma maiúscula.

As marcas de conversão padronizadas são “%c”, “%d” (ou “%i”), “%u”, “%x” (ou “%X”), “%o”, %e”, “%f”, “%g”, “%n” e “%s” para, respectivamente, “caracter”, “decimal inteiro (com sinal)”, “decimal inteiro (sem sinal)”, “hexadecimal inteiro”, “octal inteiro”, “ponto flutuante em ‘notação científica'”, “ponto flutuante ‘fracionário'”, “ponto flutuante (um ou outro anterior)”, “número de caracteres escritos” e “strings”. Dessas marcas, apenas o “%n” não exige um argumento adicional.

Modificador:

As marcas esperam tipos específicos nos parâmetros aos quais estão associadas. “%c” espera um tipo char. “%s” espera um ponteiro para char. “%d” e “%i” esperam um tipo int e as marcas que lidam com ponto flutuante esperam o tipo double. Assim, se quisermos imprimir um caracter do tipo wchar_t, temos que usar o modificador “l” (de “long”), como em “%lc”. A mesma coisa aplica-se ao modificador “%s”, se quisermos usar um ponteiro para wchar_t.

O mesmo modificador “l” é usado para “%d”, “%i” e “%u” para o uso do tipo long int. Mas, podemos querer usar um tipo char como container para um inteiro e, neste caso, podemos uasr o modificador “hh”. como em “%hhd”. Neste caso, o printf esperará ver um char no parâmetro correspondente. O modificador “h” aplica-se ao tipo short e o modificador “ll” ao tipo “long long int”. A mesma coisa com “%u”, mas lembre-se que os tipos serão interpretados como unsigned.

Outro modificador útil é o usado para especificar valores expressos nos tipos size_t ou ssize_t. Esse modificador é o ‘z’, como em “%zu” (para size_t) e “%zd” (para ssize_t).

Tamanho e precisão:

O tamanho especificado em width, se houver um, é o tamanho mínimo, em caracteres, do campo. Se a string convertida tiver menos caracteres que o especificado neste campo, então ela será alinhada à esquerda e preenchida com espaços. Assim, ao usarmos a marca “%20s” e o ponteiro apontar para uma string de 4 caracteres, os 16 caracteres seguintes serão preenchidos com espaços. Note que o campo não especifica o tamanho máximo…

O campo “precisão” (precision) depende da marca… No caso de marcas para ponto flutuante, ela especifica o número máximo de “casas depois da ‘vírgula'” que serão impressas. No caso das marcas de tipo inteiro (“%d”, ‘%i”, “%u”, “%o” ou “%x”), especifica o número máximo de algarismos que serão impressos e, no caso da marca “%s”, o máximo de caracteres.

Este último caso pode ser confuso. Uma marca “%20.10s”, por exemplo, nos diz que, no mínimo, 20 caracteres serão impressos, mas o valor terá, no máximo, 10…

Existe apenas uma diferença para as marcas do tipo ponto flutuante: Para as marcas “%g”, a precisão refere-se ao número de dígitos significativos (a quantidade de algarismos).

Flags:

Já que o campo width nos dá o tamanho mínimo e preenche a sub string com espaços à direita se ela for menor que o valor especificado, podemos usar o flag “-“, como em “%-20s” para dizermos ao printf que ele deve fazer um alinhamento à direita, ou seja, preencher com espaços o lado esquerdo da string.

O flag “+” nos diz, para os tipos numéricos, que ‘+’ ou ‘-‘ sejam impressos, precedendo o valor.

Um simples espaço usado no campo flags diz que ‘-‘ deve ser impresso se o valor for negativo, mas ‘+’ não deve, colocando um espaço no lugar.

O flag ‘0’ pode ser usado para preenchimento com zeros, ao invés de espaços, se a sub string não tiver o tamanho mínimo especificado em width. Por exemplo, “%08x” imprimirá um valor hexadecimal de exatamente 8 algarismos, mesmo que tenha que colocar zeros à esquerda.

Existem 3 outros flags especiais: ‘#’ denota a forma alternativa da marca… Para a marca “%x” a sequência “0x” será impressa antes do valor, como em “%#08x”… Isso imprimirá, por exemplo, um valor “0x0000ffab”; Uma aspa simples “‘” é o flag usado para imprimir os agrupamentos de milhares. Isso depende da configuração de localidade (locale), mas, por exemplo, uma marca “%’.2f” onde o valor 123456.78 for passado como argumento resultará na sub string “123,456.78”, se estivermos usando um locale en-us.

O terceiro flag especial é um indicador do argumento. Isso é uma extensão do GCC e não faz parte do padrão C99 (mas faz parte do padrão POSIX!). Suponha que queiramos imprimir o mesmo valor de uma variável x três vezes no mesmo printf. Poderíamos fazer algo assim:

printf("x = %d (Hexa: %#x, Octal: %#o\n", x, x, x);

Mas o GCC permite dizermos ao printf qual argumento queremos que ele use na marca usando um flag “n$”, onde $n \ge 1$. O valor de n nos dá a posição do argumento na lista e podemos escrever a mesma chamada, acima, assim:

printf("x = %1$d (Hexa: %1$#x, Octal: %1$#o\n", x);

A desvantagem é que todas as marcas devem ter o indicador de argumento.

O retorno de printf:

A função printf tem o seguinte protótipo:

int printf(char *fmt, ...);

Ela retorna um inteiro contendo a quantidade de caracteres impressa ou um valor negativo se houve um erro.

Anúncios