As extensões do GCC às linguagens C/C++

- por Sergio Prado

Categorias: Linguagem C Tags: ,

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-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

Faça um Comentário

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.