Há algum tempo tinha vontade de escrever algo sobre os padrões da linguagem C e recentemente, motivado por uma discussão na lista exploits-brasil [1] e aproveitando um domingo nublado no Rio de Janeiro, decidi começar.
Este artigo vai ajudá-lo a entender (e quando utilizar) os padrões de programação da linguagem C. Não ache que a utilidade é teórica, pois não é. Seguir um padrão pode ajudar na segurança de código, otimização e até aumentar a produtividade, como veremos mais a diante (sabia que C tem o tipo boolean, por exemplo?).
1. Introdução
A maioria das linguagens de programação seguem padrões abertos, que os criadores de compiladores (ou interpretadores) precisam seguir para suportar a linguagem completamente. A linguagem C foi criada por Denis Ritchie (falecido em 2011) entre 1969 e 1973 com o objetivo de substituir o Assembly (o que explica a existência comandos como goto na linguagem). Desde então, várias versões de compiladores foram criadas, por várias pessoas e empresas, sem seguir uma especificação oficial. Por isso, em 1983, o American National Standards Institute, mais conhecido pela siga ANSI, formou um comitê para estabelecer um padrão, que só foi concluído em 1989.
2. Linha do tempo da linguagem C
- 1969-1973: Denis Ritchie cria a linguagem C
- 1983: O ANSI cria um comitê para especificar a linguagem
- 1989: A especificação ANSI X3.159-1989 é concluída. O nome informal dela é C89 e também é conhecida por ANSI C.
- 1990: O ISO (International Organization for Standardization) assume a especificação de linguagem e cria um grupo de trabalho (working group) chamado WG14 [2] para cuidar da especificação do C, que é renomeada para ISO/IEC 9899:1990, informalmente conhecida como C90. Não houve mudanças na linguagem. Esta especificação se mantém por vários anos e define a linguagem C que “todos conhecem”.
- 1999: O WG14 publica a especificação ISO/IEC 9899:1999, informalmente chamada de C99. Esta já traz várias novidades que serão tratadas mais à frente neste artigo.
- 2011: O WG14 publica a ISO/IEC 9899:2011, conhecida como C11. Este é o padrão atual da linguagem.
Vários recursos cobertos nas especificações mais recentes já eram implementados pelos desenvolvedores de compiladores por sua conta. No caso do GCC, por exemplo, o Projeto GNU criou os padrões GNU89, GNU99 e GNU11 com suas próprias extensões. O Visual C++ da Microsoft também tem várias extensões da linguagem (lembre-se que em geral qualquer compilador C++ compila C, pois a linguagem é a mesma, mas a recíproca não é verdadeira).
Vou mostrar alguns recursos das especificações atuais, utilizando o gcc 4.6 para os exemplos.
A opção -std do gcc define qual especificação usar. Já a -pedantic faz o gcc alertar sobre qualquer construção que seja proibida pela especificação, de forma bem rigorosa.
3. Recursos da C99
- Tipo booleano:
Incluindo a stdbool.h, você pode declarar variáveis do tipo bool e usar os estados verdadeiro e falso, assim:
#include <stdbool.h> void main() { bool var; if (var) var = false; }
É fato que isso já era feito usando-se 1 e 0 e na verdade os estados true e false não passam de outros nomes (#define) para estes números, respectivamente. No entanto, a legibilidade do código melhora com o tipo bool.
- Comentários com //
A C89 só permite comentários entre /* e */, o que dá um trabalho extra para comentários de uma linha. Usando a C99, você pode iniciar a linha com //, como no C++, PHP etc. e esta linha será um comentário perfeitamente válido.
- Funções em linha
Quando você chama uma função no código, o compilador normalmente gera uma instrução CALL (do Assembly) para desviar o fluxo de código para o endereço da função. Isso envolve passagem de parâmetros pela pilha de memória (stack), retorno etc. Ou seja, isto gera um ônus de processamento e consumo de memória considerável, principalmente associado ao número e tamanho dos argumentos. Um código que não use funções é mais otimizado, no entanto, é mais difícil de manter. Para resolver isso, você pode dizer que uma função é em linha (inline). Dessa forma, toda vez que a função for chamada, o código dela será copiado para o trecho onde a chamada aconteceu. É como se você copiasse e colasse a função em vários trechos, mas sem precisar fazer isso.
Imagine que temos a função:
inline int soma(int num1, num2) { return num1 + num2; }
Como ela foi declarada como inline, ao chamá-la por exemplo como:
var = soma(3, 5);
O compilador vai substiuir por:
var = 3 + 5;
- Vetores de tamanho variável
Na C99 você pode declarar um vetor assim:
void funcao(int numero) { char vet[numero]; }
O tamanho do array de char vet depende do parâmetro recebido. Isso era resolvido antes com funções para alocação dinâmica.
Há adições também do tipo complex, para números complexos (e imaginários) e muito mais novidades. No site do WG14 [2] há toda a especificação para download.
Para utilizar a C99 com o gcc, use:
$ gcc -std=c99 fonte.c
4. Recursos da C11
- Novas funções seguras (com bound checking)
As funções tidas como inseguras como strcpy, strcat etc ganharam novas versões sufixadas com “_s”, de safe. Basicamente elas recebem um argumento a mais que é o número de caracteres para trabalhar. São elas:
- Multithreading (thread.h)
Apesar de os SOs modernos fornecerem funções de API para multithreading, ao ser inserida oficialmente no C possivelmente vai ajudar na portabilidade de aplicativos que utilizem este recurso. É algo muito bom para a linguagem.
- Estruturas e uniões anônimas
Útil quando estão aninhados, este foi um recurso que senti falta quando programei a primeira versao do pev, que usa muitas estruturas. Se queremos que um campo de uma estrutura seja outra estrutura, antes da C11, é preciso fazer:
struct ponto { int x; int y; } struct reta { struct ponto p; }
Aí para acessar:
struct reta r; r.p.x = 10;
Com a C11, a estrutura reta pode ser:
struct reta { struct ponto; }
E o acesso:
r.x = 10;
Dizemos então que a estrutura reta possui uma estrutura anônima (sem nome) como campo.
5. Qual especificação usar?
Na hora de usar uma especificação e seguir suas regras, você deve pesar como é o seu projeto, em quais sistemas ele vai rodar, como será distribuído etc. Por exemplo, se é um projeto interno e o compilador que você utiliza suporta a C99, não vejo por que não utilizá-la. Agora, se você vai distribuir seu código, aí já tem que pensar nas versões dos compiladores de quem for baixar. Será que o gcc do usuário do seu software é a última versão? Será que suporta a C11?
No hdump, um dumper hexa/ASCII que fiz para usar no Windows e no Solaris, eu optei por trabalhar com C89 por dois motivos: o código é pequeno e eu precisava levar o fonte para compilar em versões antigas do Solaris, com compiladores que não suportavam a C99, por exemplo.
Já para o pev um analisador de binários de Windows, eu comecei em ANSI mas já estou atualizando para C99, para deixar o código mais legível e utilizar os novos recursos. Eu distribuo o fonte e binários já compilados, inclusive para Windows, então não tem problema para os usuários.
O importante é ler as especificações e perceber se tem algo que te ajuda/atrapalha no projeto, pensando no escopo, usuários e sistemas alvo. Só a C11 que acho que ainda não dá para distribuir um código em produção, visto que nenhum compilador à suporta completamente. Recomendo usá-la internamente e esperar um pouco antes de liberar código com ela, pois está muito recente.
6. Conclusão
Existem outros recursos interessantes na C11 [3] mas ainda não conheço um compilador que a suporte 100%. Na verdade o gcc também não suporta a C99 por inteiro ainda. Existe até uma página onde você pode acompanhar as mudanças no suporte à C99 [4]. Na versão 4.6, o gcc já estreiou o suporte inicial à C11, sob a opção -std=c1x. A versão 4.7 já vai vir com suporte a mais recursos desta especificação, sob a opção -std=c11. Inslusive cabe um comentário: é muito bom poder contar com um conjunto de compiladores constantemente atualizado e bem feitos como são os do GCC. Essa maravilhosa suíte inclui compiladores para C, C++, Objective-C, Fortran, Java, Ada, e Go. Todos livres. É um espetáculo mesmo.
O último rascunho ISO/IEC 9899:201x, divulgado em abril do ano passado está disponível em PDF [5] mas a especificação mesmo, publicada em dezembro, está à venda por cerca de R$ 450,00 na ISO Store [6].
Referências
[1] https://groups.google.com/forum/?fromgroups#!forum/exploits-brasil
[2] http://www.open-std.org/JTC1/SC22/WG14/
[3] http://en.wikipedia.org/wiki/C11_%28C_standard_revision%29
[4] http://gcc.gnu.org/gcc-4.6/c99status.html
[5] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
[6] http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=57853