Conversores customizados para printf

Não seria interessante se pudéssemos criar conversores para usarmos numa string de formatação das funções da família printf? Por exemplo: Suponha que queiramos usar um formato “%b” para imprimirmos o valor binário de um inteiro… Sempre que fizéssemos:

printf("Valor binário: %b\n", value);

O “%b” seria substituído pela representação binária do valor inteiro value

Isso é relativamente simples de ser feito se você usa a biblioteca glibc e o compilador GCC. Existe uma função, hoje “obsoleta” (mas, ainda presente na biblioteca), chamada register_printf_function. Através dessa função podemos registrar nossos próprios conversores. Tudo o que precisamos fazer é escolher um caractere que não é padronizado pelo printf, e associarmos duas funções a ele.

A primeira função é a de conversão, que literalmente obterá o valor e o imprimirá num stream, da maneira como quisermos. A segunda, é uma rotina de verificação do próprio formato na string de formatação. Lembre-se que cada conversor tem o formato:

%[modificadores][precisão].[tamanho]

Seguido da letra do conversor. É o caso de formatos como “%#08x”. O “#0” diz que a string convertida deverá ser precedida por “0x” e preencha como zeros à esquerda até completar 8 caracteres hexadecimais minúsculos… O printf, por si só, verifica se a especificação do conversor é válida, ou seja, se ela segue o padrão básico esperado para qualquer conversor… mas a função que lida com as informações da especificação (a segunda função que citei acima) pode verificar quais modificadores, precisão e tamanho são válidos para o conversor… A função de conversão em si só terá a chance de fazer sua mágica depois que a primeira retornar um valor maior ou igual a zero.

Abaixo vai um exemplo do conversor que citei acima (o “%b”):

/* custum_printf.c */
/* vim: set ts=2 et sw=2 : */
#include <stddef.h>
#include <stdio.h>
#include <printf.h>

/* Essa função 'imprime' os argumentos (args) no 
   stream de acordo com as informações (info) da 
   conversão. */
static int print_bin(FILE *stream, 
                     const struct printf_info *info, 
                     const void * const *args);

/* Essa função checa os argumentos da conversão. */
static int print_bin_info(const struct printf_info *info, 
                          size_t n, int *types);

/* Nossa função de teste. 
   ===================== */
void main(void)
{
  // Pragma que evita o aviso de "deprecated declaration".
  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
  register_printf_function('b', 
    print_bin, print_bin_info);
  #pragma GCC diagnostic pop

  printf("%d -> %1$#x -> %1$#b\n", -1);
}

/* OBS: Poderíamos tratar erros na especificação da 
        conversão aqui! */
int print_bin_info(const struct printf_info *info, 
                   size_t n, int *types)
{
  /* Aceitaremos apenas um argumento e será 
     do tipo 'int'. */
  *types = PA_INT;

  /* retorna a quantidade de argumentos que o
     conversor aceitará. */
  return 1;
}

int print_bin(FILE *stream, 
              const struct printf_info *info,
              const void * const *args)
{
  #define MAX_BUFFER_SIZE 32
  char buffer[MAX_BUFFER_SIZE+1], *ptr;

  /* Neste exempplo, estou assumindo que o 
     argumento é 'int'. */
  unsigned int value = *(unsigned int *)*args;
  unsigned int i;

  /* Converte o valor numa string binária. */
  for (i = MAX_BUFFER_SIZE, ptr = buffer; 
       i--; 
       ptr++, value <<= 1)
  {
    *ptr = !!(value & (1U << 31)) + '0'; 
  } 
  *ptr = '\0'; 

  /* Procura pelo primeiro '1' a partir do MSB */ 
  for (ptr = buffer; *ptr && *ptr == '0'; ptr++) ; 

  /* Se todos os caracteres são '0', aponta para o 
     último. */ 
  if (!*ptr) ptr--; 

  /* Finalmente, imprime no stream. Inclui '0b' se o 
     modificador # for usado. */ 
  return fprintf(stream, "%s%s", info->alt ? "0b" : "", 
            ptr);
}

Este programinha registra o conversor para ‘b’ e imprime, via printf, o valor -1 em hexadecimal e em binário:

$ gcc -o custom_printf custum_printf.c
$ ./custom_printf
-1 -> 0xffffffff -> 0b11111111111111111111111111111111

Note que, na linha onde imprimimos o valor, o printf toma conta de alguns detalhes antes mesmo de nosso conversor entrar em ação. Por exemplo, o modificador “1$” diz para o printf que ele deve usar o primeiro parâmetro como fonte para esse conversor…

Mas, note novamente que register_printf_function é obsoleta (deprecated), embora esteja presente até mesmo na última versão da glibc (2.23, na época em que escrevi esse texto)… Ela poderá sumir da biblioteca, algum dia… Vaja a documentação aqui.

Anúncios