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:
- traduzir o código fonte em código em uma linguagem de montagem ou assembly;
- montar o código assembly em código objeto binário;
- 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.
Instalação
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:
user@desktop:~$ gcc --version
Se tudo foi instalado como esperado uma mensagem irá aparecer. A mensagem é semelhante a essa:
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
-std=c99
: utiliza o padrão C99.-Wall
: habilita todos os warnings.-Werror
: classifica os warnings como erros.
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