Compilador GCC

Contribuíram neste tutorial Giovanni Bertão, Guilherme Vieira Leite

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

Nesta disciplina e durante sua carreira como computeir@, é provável que você se depare diversas vezes com alguma distribuição de GNU/Linux. Quanto mais cedo você conseguir fazer essa transição, melhor. Caso você não possa ou não queira instalar uma versão nativa agora, você pode utilizar uma versão WSL do Ubuntu que roda diretamente no Windows 10 ou superior. Siga os passos abaixo para instalar e utilizar o gcc, gdb.

  1. Se ainda não tiver instalado, utilize o tutorial para instalar WSL.

  2. Nos passos seguintes, vamos utilizar o VSCode para realizar as tarefas da disciplina. Se ainda não tiver instalado, instale o VSCode seguindo as instruções do tutorial.

  3. Antes de iniciar o WSL, você precisa instalar uma extensão no VSCode. Abra o VSCode, selecione o ícone de extensões (ou digite Ctrl + Shift + X), busque por WSL e instale a extensão correspondente.

  4. Depois, vamos atualizar seu terminal GNU/Linux e garantir que ele funcione com todas as ferramentas de que você vai necessitar. Abra o terminal do WSL (se o terminal do WSL já estava aberto enquanto você instalou o VSCode, será necessário fechar e abrir o WSL novamente).

  5. Atualize o índice de pacotes do sistema e instale os pacotes necessários para a disciplina:

    usuario@PC:/mnt/c/Users/usuario$ sudo apt-get update
    usuario@PC:/mnt/c/Users/usuario$ sudo apt-get install build-essential gdb valgrind
    
  6. Por padrão o WSL já deve ter instalado o git. Faça um clone de seu repositório e mude para a pasta:

    usuario@PC:/mnt/c/Users/usuario$ git clone https://gitlab.ic.unicamp.br/mc202-2022/raXXXXXX.git
    usuario@PC:/mnt/c/Users/usuario$ cd raXXXXXX
    
  7. Abra o diretório do repositório usando o VS Code usando o modo WSL. Por exemplo, no terminal Ubuntu, a partir do diretório, digite:

    usuario@PC:/mnt/c/Users/usuario/raXXXXXX$ code .
    
  8. Se aparecer WSL: Ubuntu no canto inferior esquerdo, deu tudo certo!

  9. Abra algum arquivo.c e clique nesse botão Play para compilar e rodar. Você também pode configurar breakpoints e executar passo a passo.

  10. Parabéns, agora você está com tudo pronto!

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