Eu recriei a clássica função printf() do C do zero!
Olá, pessoal do TabNews! Sophia aqui com mais um post!
Há pouco tempo compartilhei aqui a minha experiência desenvolvendo a libft, minha primeira biblioteca própria em C na 42 São Paulo. Hoje estou de volta para contar como foi encarar o segundo grande desafio dessa jornada de Cadete: recriar do zero uma das funções mais utilizadas, famosas e misteriosas do início do aprendizado de baixo nível: a printf().
O projeto se chama ft_printf e, seguindo a filosofia da escola, a regra de ouro se mantém: não podemos utilizar funções prontas da biblioteca padrão (além da permissão estrita da write e da própria libft que criamos anteriormente). Tudo precisa ser construído na unha!
O que é a ft_printf?
O objetivo principal parece simples: analisar uma string de formatação (o primeiro argumento) e imprimir os caracteres na saída padrão (stdout). O desafio real começa quando encontramos o caractere %, que funciona como uma flag avisando que precisamos capturar um argumento variável e formatá-lo corretamente para exibir na tela.
Pelas regras do projeto, precisei implementar obrigatoriamente o suporte para as seguintes conversões:
- %c para um único caractere
- %s para strings de caracteres
- %p para o endereço de memória de um ponteiro (em formato hexadecimal)
- %d e %i para inteiros com sinal em base decimal
- %u para inteiros sem sinal em base decimal
- %x para números hexadecimais em letras minúsculas (base 16)
- %X para números hexadecimais em letras maiúsculas (base 16)
- %% para imprimir o próprio caractere de porcentagem
Além disso, há um detalhe crucial: a função precisa retornar exatamente o mesmo valor inteiro que a original, que representa a contagem precisa de todos os caracteres impressos. Se houver um desvio de um único byte sequer nos testes, a Moulinette (o sistema automático de correção) zera o projeto sem dó 😬.
Variadic Functions: o grande...
Quando bati o olho no protótipo da função pela primeira vez, confesso que deu um nó na cabeça:
int ft_printf(const char *format, ...);
O que diabos significam aquelas reticências no final? 🤯 kkkk
Foi aí que descobri e precisei dominar o conceito de Funções Variádicas. Em C, para criar uma função capaz de receber uma quantidade n-ésima e dinâmica de argumentos passados pelo usuário, precisamos recorrer à biblioteca <stdarg.h> e suas macros:
- va_list: um tipo de dado especial que serve para rastrear e armazenar os argumentos adicionais
- va_start: inicializa a lista de argumentos, apontando para o primeiro elemento logo após o argumento fixo (format)
- va_arg: resgata o próximo argumento da lista, exigindo que você informe especificamente o tipo esperado (ex.: int, char *)
- va_end: limpa a memória e finaliza o acesso à lista de argumentos
Entender como o computador navega pela memória empilhando esses argumentos sem que eles tenham um nome fixo foi um baita aprendizado de como as coisas funcionam por baixo dos panos.
Os maiores desafios da implementação
Se na libft o maior vilão foi entender o gerenciamento de memória bruta com ponteiros e alocações, na ft_printf a dor de cabeça foi com a arquitetura do código e a precisão do fluxo.
-
A saga dos Hexadecimais e Ponteiros (%p): Converter um número inteiro longo para hexadecimal exige uma lógica de divisões sucessivas pela base 16. No entanto, o tratamento do %p exige atenção redobrada: ele precisa injetar o prefixo 0x na frente e lidar corretamente com o caso de ponteiros nulos (NULL), exibindo a string (nil) de forma idêntica ao comportamento padrão do sistema operacional.
-
A Meticulosa Contagem de Caracteres: Como o retorno da função principal depende da soma exata de tudo o que foi escrito, cada pequena função auxiliar (seja imprimindo um número negativo enorme ou uma string vazia) precisa retornar a quantidade exata de caracteres que enviou para o terminal. Uma falha de lógica em um único if e o retorno final quebrava.
Desta vez, o papel e a caneta voltaram a ser meus melhores amigos: desenhar o fluxo do parser (mapeando a leitura caractere por caractere e os desvios de fluxo ao encontrar o %) me poupou horas valiosas de depuração.
Jump Tables: apenas deixe isso simples
Eu tentei bancar a esperta e economizar nos if-else, então fui em busca de alternativas. Acabei encontrando sobre "Jump Tables", que é basicamente chamar funções em uma tabela de array. O problema foi essas linhas de código aqui:
while (i < 256)
{
ctx->table[i] = NULL;
i++;
}
Esse código serve para definir toda a minha tabela para NULL até 256, evitando lixos da memória. A questão é que, a cada chamada da minha ft_printf, esse while fará essa limpeza várias e várias vezes, sendo bem mal performatico. Eu fiz essa correção na minha "mega-libft", onde concentrei o ft_printf e a get_next_line, que irei falar sobre posteriormente. Lá apenas usei o básico e simples if-else.
O Projeto
Para quem tiver curiosidade em conferir como estruturei as funções, organizei os arquivos auxiliares e configurei o Makefile para gerar a biblioteca, o repositório completo está público no meu GitHub:
🔗 https://github.com/ciproterona/42_ft_printf
Vale destacar que eu não fiz o bônus, que incluí precisão de floats (%.2f). Pretendo fazê-la mais a frente apenas pelo aprendizado, pois não iremos usar essa funcionalidade do printf, então não vi necessidade imediata de implementá-la.
Nota sobre contribuições: Por se tratar de um projeto puramente acadêmico e individual da grade da 42, não aceito merges de Pull Requests para alterar o escopo principal. Porém, sinta-se totalmente livre para abrir uma Issue ou PR educacional para propor melhorias de performance, discutir conceitos ou apontar alguma otimização na lógica! 👐🏼
Superar o desafio da printf me trouxe uma confiança legal para continuar. Ver uma função tão clássica e robusta rodando perfeitamente a partir de linhas de código que eu mesma escrevi é uma sensação indescritível.
Adoraria saber de vocês: Como foi o primeiro contato de vocês lidando com funções variádicas ou manipulação de strings em baixo nível? Para quem já fez esse tipo de projeto, vocês costumam estruturar o parser com grandes blocos de if/else ou preferem seguir minha loucura e usar tabelas de ponteiros para funções (array of function pointers)?
Estarei trazendo atualizações aqui no TabNews para os próximos passos da jornada. Até mais! \(^-^)/