Compilador GCC

Contribuíram neste tutorial Giovanni Bertão

Instalando um compilador

Diferentemente dos scripts em Python, um código-fonte em C precisa ser “transformado” em um arquivo binário para que ele possa ser executado pelo computador. Para isso, é necessário passar por algumas etapas:

  1. traduzir o código fonte em código em uma linguagem de montagem ou assembly;
  2. montar o código assembly em código objeto binário;
  3. juntar os códigos objetos e bibliotecas externas em um arquivo binário executável.

Todo esse processo é chamado de compilação e é normalmente realizado por um único programa, o compilador. Na disciplina de MC202, utilizaremos o GCC para compilar código-fonte escrito em C.

Nesse tutorial, você aprenderá a baixar e instalar o GCC e depois a compilar um programa usando o GCC.

GNU/Linux

No Ubuntu ou no Debian, para instalar as principais ferramentas para compilação (incluindo o GCC), digite:

user@desktop:~$ sudo apt update && sudo apt install build-essential -y

Para verificar que o GCC foi instalado, digite gcc --version. Se tudo foi instalado como esperado, então uma mensagem irá semelhante a seguinte:

user@desktop:~$ gcc --version
gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Mac

Windows

Compilando seu primeiro programa

Para compilar um programa utilizando o GCC utilize o comando:

user@desktop:~$ gcc <nome do programa.c> -o <nome do executavel>

Esse tutorial acompanha o código fonte ola.c.

#include <stdio.h>

int main(void) {
    printf("Olá Mundo!\n");
    return 0;
}

Compile esse arquivo utilizando

user@desktop:~$ gcc ola.c -o ola

Em seguida, para executar o programa utilize:

user@desktop:~$ ./ola

É esperado que a saída do programa seja:

Olá Mundo!

Adicionando flags ao compilador

A seção anterior uma forma básica de compilar um único arquivo. O gcc recebe o nome de um arquivo de código-fonte ola.c de entrada além de -o ola indicando que o nome do arquivo executável de saída será ola. Observe que o argumento -o é uma opção do gcc que necessita de um argumento, então o nome do executável virá sempre imediatamente depois de -o.

Além desses argumentos, iremos adicionar diversos outros parâmetros. Eles servem para modificar o processo de compilação e são normalmente chamados de flags de compilação. Por exemplo, na disciplina sempre usaremos flags que determinam qual versão do padrão C iremos utilizar, bem como flags que indicam quais erros devem ser mostrados:

user@desktop:~$ gcc -std=c99 ola.c -Wall -Werror -o ola

Para ver porque essas flags são úteis, compile o seguinte programa, chamado inicializar.c. Esse programa tem um problema importante: ele tente imprimir o valor de uma variável antes mesmo de definir seu valor.

#include <stdio.h>

int main(void) {
    int x;
    printf("O valor de x é %d\n", x);
    x = 10;
    return 0;
}

Compile e execute sem nenhuma flag, apenas com a flag -Wall e, em seguida, com as flags -Wall e -Werror. Qual o comportamento em cada caso?

Finalmente, sempre incluiremos também a flag -g que é bastante útil para debugar nosso programa. Ela anexa ao arquivo binário executável diversas informações de depuração que podem ser utilizadas, por exemplo, pelo GDB enquanto debugamos o programa.

Às vezes também precisamos passar flags para a última etapa da compilação. Por exemplo, a seguinte programa raiz.c utiliza uma biblioteca externa chamada math.h, que nos sistemas Unix é armazenada em um arquivo chamado m.a, que está armazenado em um diretório padrão conhecido pelo GCC.

#include <math.h>
#include <stdio.h>

int main(void) {
    double valor, raiz;
    scanf("%lf", &valor);
    raiz = sqrt(valor);
    printf("%lf\n", raiz);
    return 0;
}

Se tentarmos compilar, teremos um erro:

user@desktop:~$ gcc -std=c99 -Wall -Werror raiz.c -o raiz
/usr/bin/ld: /tmp/ccHWyK6A.o: na função "main":
raiz.c:(.text+0x3d): referência não definida para "sqrt"
collect2: error: ld returned 1 exit status

Você não precisa entender todos os detalhes. O que aconteceu foi o seguinte: as duas primeiras fases da compilação (tradução e montagem) funcionaram sem erros. Tanto é verdade que foi gerado um arquivo de código objeto (no caso, /tmp/ccHWyK6A.o). Mas esse programa depende de uma função sqrt que está em uma biblioteca externa. Para que possamos ligar (link) nosso programa com essa biblioteca, precisamos passar uma flag para o linker (ld) informando para juntar o nosso arquivo objeto com a biblioteca externa m.a. Para isso, precisamos passar a flag -lm. Como essa é a última etapa, o GCC exige que essa flag venha por último. Compilando com todas as devidas flags e executando, obtemos:

user@desktop:~$ gcc -std=c99 -Wall -Werror raiz.c -o raiz -lm
user@desktop:~$ ./raiz
50
7.071068