GCC - a raiz para tudo

ArticleCategory:

Software Development

AuthorImage:[Here we need a little image from you]

[Lorne Bailey]

TranslationInfo:[Author + translation history. mailto: or http://homepage]

original in en Lorne Bailey 

en to pt Bruno Sousa 

AboutTheAuthor:[A small biography about the author]

O Lorne vive em Chicago e trabalha como consultor inform�tico, especializado em obter dados de e para bases de dados Oracle. Desde que se mudou para um ambiente de programa��o *nix, evitou por completo a 'DLL Hell'. Est�, presentemente a trabalhar no mestrado sobre Ci�ncia de Computa��o.

Abstract:[Here you write a little summary]

Este artigo assume que sabe as bases da linguagem C e introduzir-lhe-� o gcc como um compilador. Certificarnos-emos que consegue invocar o compilador a partir da linha de comandos utilizando c�digo fonte C simples. Depois veremos muito rapidamente o que est� a acontecer agora e como pode controlar a compila��o dos seus programas. Daremos uma pequena entrada em como utilizar um depurador.

ArticleIllustration:[One image that will end up at the top of the article]

[Illustration]

ArticleBody:[The main part of the article]

Regras do GCC

Consegue-se imaginar a compilar software livre com um compilador propriet�rio e fechado? Como � que sabe o que vai no seu execut�vel? Pode haver qualquer tipo de "back door" ou cavalo de Tr�ia. O Ken Thompson, numa das piratarias de todos os tempos, escreveu um compilador que deixava uma "back door" no programa de 'login' e perpetuava o cavalo de Tr�ia quando o compilador se apercebia que estava a compilar a si mesmo. Leia a descri��o dele para todos cl�ssicos de todos os tempos aqui. Felizmente, temos o gcc. Sempre que faz um configure; make; make install o gcc faz um trabalho pesado que n�o se v�. Como � que fazemos o gcc trabalhar para n�s? Come�aremos por escrever um jogo de cartas, mas s� escreveremos o necess�rio para demonstrar a funcionalidade do compilador. Visto que estamos a come�ar do zero, � preciso compreender o processo de compila��o para saber o que deve ser feiro para se ter um execut�vel e em que ordem. Daremos uma visto de olhos geral como um programa C � compilado e as op��es que o gcc tem para fazer o que queremos. Os passos (e os utilit�rios que os fazem) s�o Pr�-compila��o (gcc -E), Compila��o (gcc), Assemblagem (as), e Linkagem (ld) - Liga��o.

No Princ�pio...

Primeiro pensamento, dev�amos saber como invocar o compilador em primeiro lugar. � realmente simples. Come�aremos com o cl�ssico de todos os tempos, o primeiro programa C. (Os de velhos tempos que me perdoem).

#include <stdio.h>

int main()
{ printf("Hello World!\n"); }

Guarde este ficheiro como game.c. Pode compil�-lo na linha de comandos, correndo:

gcc game.c
Por omiss�o, o compilador C cria um execut�vel com o nome de a.out. Pode corr�-lo digitando:
a.out
Hello World

Cada vez que compilar o programa, o novo a.out sobrepor� o programa anterior. N�o conseguir� dizer que programa criou o a.out actual. Podemos resolver este problema dizendo ao gcc o nome que queremos dar com a op��o -o. Chamaremos este programa de game, contudo pod�amos nome�-lo com qualquer coisa, visto que o C n�o tem as restri��es que o Java tem, para os nomes.
gcc -o game game.c
game
Hello World

At� este ponto, ainda estamos longe de um programa �til. Se pensa que isto � uma coisa m�, deve reconsiderar o facto que temos um programa que compila e corre. � medida que adicionarmos funcionalidade, a pouco e pouco, queremos certificar-nos que o mantemos capaz de correr. Parece que todos os programadores iniciantes querem escrever 1,000 linhas de c�digo e depois corrigi-las de uma vez s�. Ningu�m, mas mesmo ningu�m pode fazer isto. Voc� faz um programa pequeno que correm faz as altera��es e torna-o execut�vel novamente. Isto limita os erros que tem de corrigir de uma s� vez. E ainda por cima, voc� sabe exactamente o que fez e que n�o trabalha, ent�o sabe onde concentrar-se. Isto evita-lhe criar algo que voc� pensa que trabalha e at� compile mas nunca se torna num execut�vel. Lembre-se que s� por ter compilado n�o quer dizer que esteja correcto.

O nosso pr�ximo passo � criar um ficheiro cabe�alho para o nosso jogo. Um ficheiro cabe�alho concentra os tipos de dados e a declara��o de fun��es num s� s�tio. Isto assegura que as estruturas de dados est�o definidas consistentemente, assim qualquer parte do nosso programa v� tudo, exactamente do mesmo modo.

#ifndef DECK_H
#define DECK_H

#define DECKSIZE 52

typedef struct deck_t
{
  int card[DECKSIZE];
  /* number of cards used */
  int dealt;
}deck_t;

#endif /* DECK_H */

Guarde este ficheiro como deck.h. S� o ficheiro .c � que � compilado, assim temos de alterar o nosso game.c. Na linha 2 do game.c, escreva #include "deck.h". Na linha 5, escreva deck_t deck; para ter a certeza que n�o falh�mos nada, compile-o novamente.

gcc -o game game.c

Se n�o houver erros, n�o h� problema. Se n�o compilar resolva-o at� compilar.

Pr�-compila��o

Como � que o compilador sabe que tipo � o deck_t ? Porque durante a pr�-compila��o, ele, na verdade copia o ficheiro "deck.h" para dentro do ficheiro "game.c". As directivas do pr�-compilador no c�digo fonte come�am por um "#". Pode invocar o pr�-compilador atrav�s do frontend do gcc com a op��o -E.

gcc -E -o game_precompile.txt game.c
wc -l game_precompile.txt
  3199 game_precompile.txt
Praticamente 3,200 linhas de sa�da! A maioria delas vem do ficheiro inclu�do stdio.h, mas se der uma vista de olhos nele, as suas declara��es tamb�m l� est�o. Se n�o der um nome de ficheiro para sa�da com a op��o -o, ele escreve para a consola. O processo de pr�-compila��o d� mais flexibilidade ao c�digo ao atingir tr�s grandes objectivos.
  1. Copia os ficheiros "#included" no c�digo fonte para serem compilados.
  2. Substirui o texto "#define" pelo seu valor actual.
  3. Substitui as macros nas linhas onde s�o chamadas.
Isto permite-lhe ter constantes com nomes (por exemplo, a DECKSIZE representa o n�mero de cartas num baralho) utilizadas ao longo do c�digo e definidas num s� s�tio e automaticamente actualizadas sempre que o seu valor se altera. Na pr�tica, quase num utilizar� a op��o -E isoladamente, mas deixar� passar a sua sa�da para o compilador.

Compila��o

Como um passo intermedi�rio, o gcc traduz o seu c�digo em linguagem Assembler. Para fazer isto, ele deve descobrir o que � que tinha inten��o de fazer ao passar por todo o seu c�digo. Se cometer um erro de sintaxe ele dir-lhe-� e a compila��o falhar�. Muitas vezes as pessoas confundem este passo com todo o processo inteiro. Mas ainda h� mais trabalho para o gcc fazer.

Assemblagem

O as transforma o c�digo Assembler em c�digo objecto. O c�digo objecto n�o pode ainda correr no CPU, as est� muito perto. A op��o do compilador -c transforma um ficheiro .c num ficheiro objecto com a extens�o .o. Se corrermos:

gcc -c game.c
criamos automaticamente um ficheiro chamado game.o. Aqui ca�mos num ponto importante. Podemos tomar qualquer ficheiro .c e criar um ficheiro objecto a partir dele. Como vemos abaixo, podemos combinar estes ficheiros objecto em execut�veis no passo de Liga��o. Continuemos com o nosso exemplo. Visto estarmos a programar um jogo de cartas e definimos um baralho de cartas como um deck_t, escreveremos um fun��o para baralhar o baralho. Esta fun��o recebe um ponteiro do tipo deck e correga-o com um valores aleat�rios para as cartas. Mant�m rasto das cartas j� desenhadas, com o vector de 'desenho'. Este vector com membros do DECKSIZE evita-nos duplicar o valor de uma carta.

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "deck.h"

static time_t seed = 0; 

void shuffle(deck_t *pdeck)
{ 
  /* Keeps track of what numbers have been used */
  int drawn[DECKSIZE] = {0}; 
  int i;           

  /* One time initialization of rand */
  if(0 == seed) 
  {
    seed = time(NULL);  
    srand(seed);
  }
  for(i = 0; i < DECKSIZE; i++)
  {
    int value = -1;
    do
    {
      value = rand() % DECKSIZE;
    }
    while(drawn[value] != 0); 

    /* mark value as used */ 
    drawn[value] = 1; 

    /* debug statement */
    printf("%i\n", value); 
    pdeck->card[i] = value;
  }
  pdeck->dealt = 0;
  return;
}

Guarde este ficheiro como shuffle.c. Pusemos uma frase de depura��o no c�digo para quando correr, escrever o n�mero das cartas que gera. Isto n�o adiciona nenhuma funcionalidade ao programa, mas agora, � crucial para vermos o que se passa. Visto estarmos somente a come�ar o nosso jogo, n�o temos outro modo sen�o que termos a certeza que a nossa fun��o est� a fazer o que pretendemos. Com a frase printf, podemos ver exactamente o que est� acontecer e assim quando passarmos para a pr�xima fase sabemos que o baralho esta bem baralhado. Depois de estarmos satisfeitos com o seu funcionamento podemos remover esta linha do nosso c�digo. Esta t�cnica de fazer depura��o aos programas parece arcaica mas f�-lo com um m�nimo de trabalho irrelevante. Discutiremos mais tardes depuradores mais sofisticados.

Note duas coisas.
  1. Passamos por par�metro o seu endere�o, pode dizer isto pelo '&' (endere�o de) operador. Isto passa o endere�o de m�quina da vari�vel para a fun��o, assim a fun��o pode alterar a pr�pria vari�vel. � poss�vel programar com vari�veis globais, mas deviam ser utilizadas raramente. Os ponteiros s�o uma parte importante do C e devia entende-los tamb�m.
  2. Estamos a utilizar uma chamada de fun��o a partir do novo ficheiro .c. O sistema operativo procura sempre por uma fun��o chamada 'main' e come�a a sua execu��o a�. O shuffle.c n�o tem uma fun��o 'main' por conseguinte n�o pode ser um execut�vel por si s�. Devemos combin�-lo com outro programa que tenha uma fun��o 'main' e que chame a fun��o shuffle.

Corra o comando

gcc -c shuffle.c
e certifique-se que cria um novo ficheiro chamado shuffle.o. Edite o ficheiro game.c, e na linha 7, ap�s a declara��o da vari�vel deck_t deck, adicione a linha
shuffle(&deck);
Agora, se tentarmos criar um execut�vel do mesmo modo como antes obtemos um erro
gcc -o game game.c

/tmp/ccmiHnJX.o: In function `main':
/tmp/ccmiHnJX.o(.text+0xf): undefined reference to `shuffle'
collect2: ld returned 1 exit status
O compilador teve sucesso porque a nossa sintaxe estava correcta. A fase de liga��o falhou porque n�o dissemos ao compilador onde se encontra a fun��o 'shuffle'. O que � que � a liga��o e como � que dizemos ao compilador onde pode encontrar esta fun��o?

Liga��o

O linker, ld, pega no c�digo objecto previamente criado com as e transforma-o num execut�vel atrav�s do comando

gcc -o game game.o shuffle.o
Isto combinar� os dois objectos e criar� o execut�vel game.

O linker encontra a fun��o shuffle a partir do objecto shuffle.o e inclui-o no execut�vel. A verdadeira beleza dos ficheiros objecto vem do facto se quisermos utilizarmos esta fun��o novamente, s� temos de incluir o ficheiro "deck.h" e ligar ao c�digo do novo execut�vel o ficheiro objecto shuffle.o.

O aproveitamento do c�digo est� sempre a acontecer. N�o escrevemos o c�digo da fun��o printf quando a cham�mos em cima como uma declara��o de depura��o, O linker encontra a sua defini��o no ficheiro que inclu�mos #include <stdlib.h> e liga-o ao c�digo objecto armazenada na biblioteca C (/lib/libc.so.6). Deste modo podemos utilizar a fun��o de algu�m que sabemos trabalhar correctamente e preocuparmo-nos em resolver os nossos problemas. � por este motivo que os ficheiros de cabe�alho s� cont�m as defini��es de dados e de fun��es e n�o o corpo das fun��es. Normalmente voc� cria os ficheiros objecto ou bibliotecas para o linker por no execut�vel. Um problema podia ocorrer com o nosso c�digo visto que n�o pusemos nenhum defini��o no nosso ficheiro cabe�alho. O que � que podemos fazer para ter a certeza que tudo corre sem problemas?

Mais duas Op��es Importantes

A op��o -Wall activa todo o tipo de avisos relativamente � sintaxe da linguagem para nos ajudar a ter a certeza que o nosso c�digo est� correcto e port�vel tanto quanto poss�vel. Quando utilizamos esta op��o e compilamos o nosso c�digo podemos ver algo como:

game.c:9: warning: implicit declaration of function `shuffle'
Isto diz-nos que temos mais algum trabalho a fazer. Precisamos de p�r uma linha no ficheiro de cabe�alho, onde diremos ao compilador tudo sobre a nossa fun��o shuffle assim poder� fazer as verifica��es que precisa de fazer. Parece complicado, mas separa a defini��o da implementa��o e permite-nos utilizar a fun��o em qualquer lado bastando incluir o nosso novo ficheiro cabe�alho e lig�-lo ao nosso c�digo objecto. Introduziremos esta linha no ficheiro deck.h.
void shuffle(deck_t *pdeck);
Isto evitar� a mensagem de aviso.

Uma outra op��o comum do compilador � a optimiza��o -O# (ou seja -O2). Isto diz ao compilador o n�vel de optimiza��o que quer. O compilador tem um saco cheio de truques para tornar o seu c�digo mais r�pido. Para um pequeno programa como o nosso n�o notaremos qualquer diferen�a, mas para programas grandes pode melhorar um pouco a rapidez. Voc� v� esta op��o em todo o lado por isso devia saber o que significa.

Depura��o

Como todos sabemos, s� porque o nosso c�digo compilou n�o quer dizer que vai trabalhar do modo que queremos. Pode verificar que s�o utilizados todos os n�meros de uma s� vez correndo

game | sort - n | less
e vendo que n�o falta nada. O que � que devemos fazer se houver um problema? Como � que olhamos por debaixo da madeira e encontramos o erro?

Pode verificar o seu c�digo com um depurador. A maioria das distribui��es fornecem um depurador cl�ssico, o gdb. Se a linha de comandos o atrapalha como a mim, o KDE oferece um front-end bastante simp�tico, com o KDbg. Existem outros front-ends, e s�o muito semelhantes. Para come�ar a depurar, escolha File->Executable e depois encontre o seu programa, o jogo. Quando prime F5 ou escolhe Execution->Run a partir do menu, voc� devia ver uma sa�da numa janela � parte. O que � que acontece ? N�o vemos nada na janela. N�o se preocupe, o KDbg n�o est� a falhar. O problema vem do facto de n�o termos posto nenhuma informa��o de depura��o no execut�vel, assim i KDbg n�o nos pode dizer o que se passa internamente. A flag do compilador -g p�e a informa��o necess�ria dentro dos ficheiros objecto. Deve compilar os ficheiros objecto (extens�o .o) com esta flag, assim o comando passa a ser:
gcc -g -c shuffle.c game.c
gcc -g -o game game.o shuffle.o
Isto p�e marcas no execut�vel que permitem ao gdb e ao KDbg saber o que est� a fazer. Fazer depura��o � uma tarefa importante e vale a pena o tempo gasto em aprender como o fazer correctamente. O modo como os depuradores ajudam os programadores � a habilidade de definir "pontos de paragem" no c�digo fonte. Tente agora definir um atrav�s de um clique com o bot�o esquerdo na linha com a chamada � fun��o shuffle. Deve aparecer um pequeno circulo vermelho na linha seguinte. Agora, prime F5 e o programa p�ra a execu��o nessa linha. Prima F8 para entrar dentro da fun��o shuffle. Bem, estamos agora a olhar para o c�digo a partir do ficheiro shuffle.c! Podemos controlar a execu��o passo a passo e ver o que se passa. Se deixar o apontador sobre uma vari�vel local, ver� o valor que guarda. Apreci�vel. Muito melhor que aquelas frases com printf's, n�o �?

Resumo

Esta artigo apresentou uma visita clara � compila��o e depura��o aos programas C. Discutimos os passos que o compilador faz e as op��es que devemos passar ao gcc para ele fazer esses passos. Introduzimos a liga��o a bibliotecas partilhadas e termin�mos com uma introdu��o aos depuradores. Requer bastante trabalho para saber realmente, o que est� a fazer, mas espero que isto o ajude a come�ar com o p� direito. Pode encontrar mais informa��o nas p�ginas man e info acerca do gcc, as e do ld.

Escrever c�digo por si mesmo ensina-lhe o mais importante. Para praticar, podia utilizar estas bases simples do programa de jogo de cartas utilizado neste artigo e escrever um jogo de blackjack. Aproveite o tempo para aprender a utilizar um depurador. � muito mais f�cil come�ar com um GUI como o KDbg. Se adicionar funcionalidade aos poucos, saber� as coisas sem se dar por isso. Lembre-se mantenha-o a correr!

Aqui est�o algumas coisas que poder� precisas para criar um jogo completo.

Liga��es