As extensões do GCC às linguagens C/C++
- por Sergio Prado
Se algum dia você se deparar com algum código utilizando construções estranhas como uma função implementada dentro de outra função ou a declaração de arrays de zero elementos, desconfie do uso de extensões do GCC!
No momento em que escrevo este artigo, o GCC possui mais de 60 tipos de extensões diferentes que alteram o comportamento e adicionam funcionalidades às linguagens C/C++. Muitas destas extensões são bem interessantes, outras um pouco confusas e algumas um tanto perigosas!
É importante conhecer estas extensões, porque muitas delas são utilizadas por diversos projetos de software livre, incluindo (e principalmente) o kernel Linux.
Irei descrever algumas destas extensões no restante do artigo, e no final tem um link para quem quiser conhecer todas as extensões existentes.
FUNÇÕES ANINHADAS
O GCC permite a definição de funções aninhadas, possibilitando a implementação de uma função dentro de outra função!
1 2 3 4 5 6 7 8 9 10 |
#include <stdio.h> int main(int argc, const char *argv[]) { void p (void) { printf("Inside a nested function!\n"); } p(); return 0; } |
$ gcc nested.c -o nested $ ./nested Inside nested function! |
TYPEOF
O GCC implementa a palavra-chave typeof, que retorna o tipo da variável em tempo de execução.
Este recurso possibilita o uso da técnica de programação genérica em C! A técnica de programação genérica permite basicamente a implementação de algoritmos que não dependem ou se adaptam ao tipo do dado passado, como os templates em C++.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <stdio.h> #define max(a,b) \ ({ typeof (a) _a = (a); \ typeof (b) _b = (b); \ _a > _b ? _a : _b; }) int main(int argc, const char *argv[]) { int x = 1024, y = 4096; char a = 10, b = 20; float j = 1.0, k = 2.0; printf("char max is %d\n", max(a,b)); printf("int max is %d\n", max(x,y)); printf("float max is %f\n", max(j,k)); return 0; } |
$ gcc typeof.c -o typeof $ ./typeof char max is 20 int max is 4096 float max is 2.000000 |
ESTRUTURAS VAZIAS
O GCC permite a definição de estruturas vazias!
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <stdio.h> struct empty { }; int main(int argc, const char *argv[]) { struct empty e; printf("sizeof struct empty is %d\n", sizeof(struct empty)); return 0; } |
$ gcc zerostruct.c -o zerostruct $ ./zerostruct sizeof struct empty is 0 |
CASE RANGES
O GCC permite a definição de faixas de valores em um switch no formato “case low … high”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <stdio.h> void ranges(int index) { switch(index) { case 0: printf("Doing A...\n"); break; case 1 ... 9: printf("Doing B...\n"); break; default: printf("Doing C...\n"); break; } } int main(int argc, const char *argv[]) { ranges(5); return 0; } |
$ gcc ranges.c -o ranges $ ./ranges Doing B... |
ARRAYS DE TAMANHO ZERO
Por fim, o GCC permite a declaração de arrays de tamanho zero.
Esta funcionalidade pode ser interessante no final de uma estrutura que armazena o cabeçalho de um objeto alocado dinamicamente (neste caso o nome do array de tamanho zero pode ser utilizado como ponteiro do objeto alocado dinamicamente).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <stdio.h> #include <stdlib.h> struct msg_t { int type; int size; char buf[0]; }; int main(int argc, const char *argv[]) { struct msg_t *m; int size = 1024; m = (struct msg_t *) malloc(sizeof (struct msg_t) + size); m->type = 0x01; m->size = size; printf("msg struct is at %p\n", m); printf("buffer ptr is at %p\n", m->buf); return 0; } |
$ gcc arrayzero.c -o arrayzero $ ./arrayzero msg struct is at 0x1c43260 buffer prt is at 0x1c43268 |
Todos estes exemplos compilam e funcionam no GCC (os testes foram feitos no GCC 7.3.1). Isso porque o GCC compila um código em C utilizando por padrão a opção -std=gnu11, o que significa suporte ao padrão C11 mais as extensões do GCC.
Por este motivo, os exemplos não são portáveis e dependem das extensões do GCC utilizadas. Se você quiser um código portável e de acordo com a especificação padrão da linguagem, utilize os parâmetros -std e -pedantic-errors. Por exemplo, para compilar um código compatível com o padrão C99, passe para o GCC os parâmetros -std=c99 e -pedantic-errors. Desta forma, um erro de compilação será gerado se alguma extensão do GCC for utilizada.
A lista completa de extensões do GCC está disponível no site do projeto.
Por fim, uma lição de casa para os mais curiosos e interessados no assunto. :-)
O código abaixo utiliza três outras extensões do GCC. Ele compila e funciona. Você consegue identificar quais são estas três extensões?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <stdio.h> int main(int argc, const char *argv[]) { void *p = NULL; int x = 0; x ? : (p += 0b0100); printf("x is %d.\n", x); printf("p is %p.\n", p); return 0; } |
Um abraço!
Sergio Prado