original in en Lorne Bailey
en to pt Bruno Sousa
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.
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.
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.cPor omiss�o, o compilador C cria um execut�vel com o nome de
a.out
.
Pode corr�-lo digitando:
a.out Hello WorldCada 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.
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.txtPraticamente 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.
-E
isoladamente, mas
deixar� passar a sua sa�da para o compilador.
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.
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.ccriamos 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.
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.ce 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 statusO 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.oIsto 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?
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.
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 | lesse 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.oIsto 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 �?
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.