Desenvolvimento em Linux com Debian/Ubuntu
From CInLUG
Este artigo tenta esclarecer alguns pontos para quem já programa em C e C++ no Windows em IDEs, como DevC++ e Code::Blocks, e deseja começar no Linux (e outros *nixes). As instruções sobre instalação dos softwares necessários, e a localização de alguns arquivos, será válida para Debian/Ubuntu, de resto o texto é bem genérico, especialmente a primeira parte que é mais teórica que prática. O conteúdo é baseado no material que Rodrigo Costa e eu criamos para um curso ministrado no CTG uns meses atrás. Apesar do assunto ser parecido com o artigo escrito por Pedro Leite para o CInLUG, porém a abordagem é um pouco diferente.
Ferramentas Essenciais
Por padrão o Ubuntu não instala o compilador e outras ferramentas de desenvolvimento, pois é uma distribuição voltada para "human beings", e todos sabem que programadores são pós-humanos. Mesmo assim ainda precisamos de coisinhas essenciais, então instale o pacote build-essential (que inclui make, gcc e g++) e seja feliz.
Conteúdo |
Visão Geral do Desenvolvimento em Linux
O exemplo do diagrama a seguir mostra os componentes envolvidos na geração de um programa executável no Linux.
Temos um programa qualquer, com seu início na função main em main.c, e que inclui as bibliotecas stdio.h (printf, scanf, etc) e math.h (sin, cos e outros amigos de pitágoras), e usa algumas funções do arquivo func.c, cujos cabeçalhos foram declarados em func.h.
Como o diagrama indica, os arquivos de cabeçalho estão em /usr/include, e declaram a assinatura (parâmetros e valor de retorno) das funções das bibliotecas relacionadas, e são necessários para o processo de compilação. O compilador gera um executável ELF[1], a partir de arquivos objeto (main.o e func.o), que consistem de código de máquina. O programa faz uso das bibliotecas compartilhadas (mais sobre isso depois) libc.so e libm.so, em /lib, que provêm a implementação das funções declaradas nos arquivos de cabeçalho stdio.h e math.h, respectivamente. Estas bibliotecas compartilhadas serão utilizadas pelo programa em tempo de execução, como indicado pela linha pontilhada do desenho.
!!!Estrutura Interna de Um Compilador A figura a seguir mostra a estrutura de um compilador qualquer. Nem todo compilador precisa ser feito de forma modular, mas isso ajuda muito em desenvolvimentos futuros.
Normalmente a parte mais complexa do compilador é o Middle-End, onde ocorrem as otimizações no código. O código a seguir é um exemplo de um programa completo numa linguagem qualquer.
a = 3 print a + 5
O resultado desse programa será uma saída em console que mostrará sempre "8", o otimizador do compilador substituirá nosso código pelo seguinte:
print 8
Este exemplo foi bem simplório, as otimizações podem se tornar muito complexas. Note que o produto final não é um binário executável, como no primeiro diagrama, que foi uma "simplificação que funciona", pois o GCC pode fazer isso para nós, como veremos adiante.
GNU Compiler Collection
Agora algo mais concreto. Caso você já não soubesse, os "C"s de "GCC" significam "Compiler Collection" e não "C Compiler", e o diagrama a seguir deixa claro por quê.
Compare com o diagrama do compilador genérico apresentado anteriormente. Os itens à esquerda representam front-ends para diversas linguagens, e os misteriosos "formatos intermediários" são parte do middle-end do GCC, e têm o propósito de generalizar ao máximo a saída dos front-ends das diversas linguagens, além de acrescentar informações importantes para o otimizador. Por fim, temos geradores de código de máquina específico para várias arquiteturas. Olhando para o diagrama podemos imaginar que não deve ser tão difícil acrescentar uma nova linguagem ou arquitetura alvo. Bem, eu nunca fiz um compilador, então não vou dizer que é fácil, mas certamente é mais fácil escrever um front-end ou back-end do que um compilador inteiro. A arquitetura alvo pode até ser uma máquina virtual, como as de Java, Mono ou Python. E o contrário também é possível: compilar linguagens que tradicionalmente usam VMs para binários nativos, como a Red Hat fez com o Eclipse[1]. Lego Way rlz! O gcc se torno mais modular a partir da versão 4.0, cujo desenvolvimento recebeu um grande impulso por parte da Red Hat. Se você se interessa por funcionamento interno de compiladores recomendo fortemente o artigo "From Source to Binary: The Inner Workings of GCC"[2] da RedHat Magazine.
Cross Compiling
Este termo significa compilar um programa para uma arquitetura diferente da máquina onde o compilador está rodando. O exemplo da figura a seguir mostra um programa em C sendo compilado para o processador ARM num computador x86 com o arm-gcc.
Se você tiver uma plataforma ARM por perto é só baixar o executável para ela e testar, contudo, além de nem todos terem um ARM disponível, o processo de baixar e executar o binário pode ser demorado e complexo, o normal é rodar o código num emulador até ele estar pronto (ou quase), e então mandar pra coisa real.
Qemu[3] é um dos mais populares emuladores para Linux e suporta uma boa quantidade de arquiteturas.
Arquivos Objeto (e Não "Orientação a Objetos")
Como foi dito, o compilador apenas gera código de máquina e não um executável pronto, esses códigos se apresentam na forma de Arquivos Objetos. Esses arquivos se organizam internamente em seções de código de máquina e dados (variáveis globais e strings).
Os endereços dos dados e funções num arquivo objeto são relativos, ou seja, válidos apenas para aquele arquivo objeto, que tanto pode ser o único necessário para gerar o executável, quanto apenas um de muitos arquivos objetos que compõem o sistema. Vamos supor dois arquivos objetos com suas funções e variáveis globais, com endereços hipotéticos, um prog.o contendo o ponto de entrada do programa (função "main"), e veremos como ocorre sua composição.
| Endereço | |
|---|---|
| 0 | variável1 |
| 1 | função1() |
| Endereço | |
|---|---|
| 0 | variável2 |
| 1 | função2() |
main em prog.o:
Chama funcao2() em ???
| Endereço | |
|---|---|
| 0 | variável1 |
| 1 | variável2 |
| 2 | função1() |
| 3 | função2() |
main em prog:
Chama funcao2() no endereço "3".
Linker
Chamamos a composição de arquivos objetos de linking (ligação), e o programa que a executa de linker (ligador!? vinculador!? Feio!). O linker transforma todas as seções de mesmo tipo nos arquivos objeto numa seção única. Após este passo cada instrução e variável global terá um endereço único.
GCC Passo-a-Passo
Além do que já vimos, o GCC também chama uma série de ferramentas para nós de forma transparente no momento da compilação.
O pré-processador substitui as ocorrências de nomes declarados em #define no começo do código, por seus valores efetivos. O compilador gera código assembly da arquitetura alvo; no caso do x86, este assembly estará no formato AT&T, que parece bem alienígena para quem conhece o assembly do DOS. O assembler gera o arquivo objeto e o linker o executável. O GCC pode ser instruído para parar o processo em qualquer ponto, entregando o resultado daquele momento. Podemos por exemplo gerar código assembly parando antes da excução do assembler. Neste momento se faz necessário um Hello World! de fé!
#include <stdio.h> int main(void) { return 0; }
E antes de mais nada:
$ gcc hello.c -o hello ./hello Hello World!
Podemos parar o GCC em qualquer das fases mostradas no diagrama usando os parâmetros.
| Parâmetro GCC | Pára após... | Saída |
|---|---|---|
| -E | pré-processamento | código pré-processado (.i) |
| -S | compilação | código assembly AT&T (.s) |
| -c | assembler | código objeto (.o) |
Vamos ver como é o código da AT&T.
$ gcc hello.c -S $ cat hello.s .file "hello.c" .section .rodata .LC0: .string "Hello World!" .text .globl main .type main, @function main: pushl %ebp movl %esp, %ebp subl $8, %esp andl $-16, %esp movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax subl %eax, %esp movl $.LC0, (%esp) call puts movl $0, %eax leave ret .size main, .-main .ident "GCC: (GNU) 4.0.3 (Ubuntu 4.0.3-1ubuntu5)" .section .note.GNU-stack,"",@progbits
Muito usado para trapacear em projetos do tipo "escreva um código em assembly que..." :P
Você ainda pode chamar o gcc dando como entrada os resultados de processos interrompidos para que ele continue o pipeline.
$ gcc hello.s -o hello $ ./hello Hello World!
Bibliotecas
A antiga sabedoria do "não reinvente a roda" é muito importante no mundo do software livre, onde muitas pessoas com problemas semelhantes podem unir esforços para criar coleções de ferramentas para resolver algum problema. Bibliotecas são as mais importantes ferramentas, consistem de funções e classes que podem ser usadas por vários outros programas. A biblioteca math.h, por exemplo, reúne funções matemáticas como seno, cosseno e tangente.
!!Bibliotecas Estáticas Consistem de arquivos que são 'ligados' ao programa que as usa, ou seja, as bibliotecas estáticas usadas serão incorporadas ao arquivo executável produzido pela compilação. Podemos encontrar bibliotecas estáticas em /usr/lib, estas bibliotecas são coleções de arquivos objeto (.o) reunidas num arquivo de extensão .a por um utilitário chamado GNU ar.
!Examinando Bibliotecas Estáticas Com o comando objdump podemos obter diversas informações sobre arquivos objeto, inclusive biblitecas estáticas.
$ objdump -a /usr/lib/libc.a | grep printf.o fprintf.o: file format elf32-i386 rw-r--r-- 0/0 812 Oct 9 18:03 2005 fprintf.o printf.o: file format elf32-i386 rw-r--r-- 0/0 840 Oct 9 18:03 2005 printf.o
Podemos linkar o hello.c estaticamente com a libc (utilizada quando incluímos stdio.h), bastando indicar para o gcc o caminho para sua versão estática. Porém, primeiro vamos ver o tamanho do executável para comparação.
$ gcc hello.c -o hello $ ls hello -lh 6,8K hello $ gcc hello.c -o hello /usr/lib/libc.a $ ls hello -lh 490K hello
Diferença razoável.
Bibliotecas de Ligação Dinâmica
São coleções de arquivos objeto especiais, feitos com o propósito de se ligarem aos programas que os chamam apenas em tempo de execução. Os arquivos objeto são chamados shared object (objetos compartilhados) e vêm na extensão .so. Notem que este é exatamente o mesmo conceito de Dynamic Link Library (DLL) do Windows.
No exemplo acima os dois programas da esquerda chamam a mesma biblioteca compartilhada, o que, juntamente com o exemplo da seção anterior, expõe uma de suas vantagens: a economia de recursos. Se todos os programas se ligassem estaticamente muito código duplicado seria carregado na memória, especialmente no caso de bibliotecas essenciais como a glibc. O gcc por padrão liga os executáveis com bibliotecas dinâmicas, mas podemos especificar isso da mesma forma que fizemos com a estática. No exemplo a seguir, o trecho /usr/lib/libc.so é totalmente dispensável, mas poderia ser útil no caso de uma biblioteca que não faça parte do sistema.
$ gcc hello.c -o hello /usr/lib/libc.so
Criando Shared Objects
Podemos criar nossas próprias bibliotecas compartilhadas apenas com alguns parâmetros do gcc.
$ gcc hello.c -shared -fPIC -o libhello.so
Libhello não é muito útil já que tem apenas uma função main, o normal seria uma coleção de funções, preferencialmente relacionadas, mais um ou mais arquivos de cabeçalho (.h) com as assinaturas das funções.
Dynamic Loader
Podemos saber à quais bibliotecas compartilhadas um programa está ligado com a ferramenta ldd. Usemos o velho chmod como cobaia.
$ ldd /bin/chmod linux-gate.so.1 => (0xffffe000) libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7db5000) /lib/ld-linux.so.2 (0xb7ef5000)
O arquivo da última linha é o dynamic linker/loader, ele carrega as bibliotecas compartilhadas usadas pelos programas em execução.
!!!Dividir Para Conquistar Em qualquer aplicação maior que "Hello World" costumamos quebrar o sistema em várias partes, normalmente em arquivos diferentes para melhorar a organização. Vamos largar o hello.c e passar para uma sofisticada aplicação que conta os caracteres do primeiro parâmetro passado via linha de comando.
Contador de Caracteres
main.c:
#include <stdio.h> #include "func.h" int main(int argc, char *argv[]) { conta_caracteres(argv[1])); return 0; }
func.h:
int conta_caracteres(char *palavra);
func.c:
int conta_caracteres(char *palavra) { int contador = 0; while (palavra[contador] != '\0') { contador++; } return contador; }
Compilando Arquivos Separadamente
Poderíamos passar todos os arquivos .c para o gcc, mas não seria uma boa idéia na maioria dos casos, onde muitos arquivos são necessários para compor o sistema. Usaremos então o parâmetro -c, que pára o gcc antes da "linkagem", gerando arquivos objetos, que podem ser "linkados" posteriormente.
$ gcc main.c -c $ gcc func.c -c $ gcc main.o func.o -o contador
Vamos testar:
$ ./contador sbrubbles sbrubbles tem 9 caracteres
Usando Funções de Bibliotecas
Vamos despropositadamente introduzir no main.c uma linha sem que retorna o seno de um valor qualquer sem motivo aparente.
main.c:
#include <stdio.h> #include <math.h> #include "func.h" int main(int argc, char *argv[]) { double valor = 37.0; conta_caracteres(argv[1])); eh %3.2f\n", valor, sin(valor)); return 0; }
Vamos compilar tudo novamente, dessa vez numa linha só do gcc:
$ gcc main.c func.c -o contador /tmp/cc8MZpoP.o: In function `main':main.c:(.text+0x5a): undefined reference to `sin' collect2: ld returned 1 exit status
Parece que o linker não conseguiu encontrar a biblioteca relativa à função sin. Sabemos (não sabemos?) que a biblioteca matemática se encontra em /usr/lib. Precisamos usar o parâmetro -l do gcc para indicar a biblioteca a ser usada pelo linker.
$ gcc main.c conta.c -o contador -lm
Explicando, -lm, o -l indica library (biblioteca) e m é o "nome" da biblioteca matemática libm.so.
Make
Como estamos vendo, compilar um programa pode se tornar complicado, usando bibliotecas e sendo composto por vários arquivos. Mas nem tudo está perdido, pois podemos usar o Make para automatiza o processo de construção do software, respeitando a ordem de dependências entre os arquivos.
Makefile
Quando invocado, o comando make procura no mesmo diretório o arquivo Makefile que contém as regras de construção para o código fonte.
# Comentários
VARIÁVEL=valor
target: dependencies
command 1
command n
Podemos atribuir valores a variáveis para uso posterior dentro do Makefile "targets" nomeiam seções do Makefile e também podem ser referidos como dependências de outras seções. "dependencies" são targets que precisam ser processados antes do que se refere à estas dependencies. É necessário que linhas com os comandos depois de target sejam identadas com o caractere TAB.
O Makefile a seguir deve ser colocado no mesmo diretório que nossa aplicação de contagem de caracteres para o correto funcionamento.
Makefile:
LIBS=-lm
PROG=contador
contador: main.o func.o
gcc main.o func.o ${LIBS} -o ${PROG}
main.o: main.c
gcc main.c -c
func.o: func.h func.c
gcc func.c -c
clean:
rm *.o ${PROG}
Por padrão o make chama o primeiro target do Makefile, no nosso caso contador. O target "contador" depende dos targets main.o e func.o, estes dependem dos arquivos de código fonte, quando "contador" é chamdo, os comandos das dependências são executados, os arquivos .o são gerados, o comando em "contador" gera o executável ${PROG}, que é uma variável que o make substitui pelos valores definidos. Variáveis ajudam muito a organizar o código.
$ make
Para apagar o programa e os arquivos .o gerados pelo processo de compilação, podemos usar
$ make clean
que nada mais faz do que dizer para o make ir direto para o alvo "clean".
Erros
Coisas Ruins(TM) podem acontecer a qualquer momento. Podemos fazer acontecer agora mesmo! Executemos nosso programa sem parâmetros
$ ./contador Segmentation fault
Segmentation fault é uma das mensagens de erro mais comuns e não dá muita informação além de que houve um erro em tempo de execução. O que poderia ter causado o erro? Para o compilador não havia nada de errado com o programa, já que a compilação foi concluida com sucesso e quando usamos o programa com o parâmetro "sbrubbles" nada estranho acontece. Teria sido falta de "sbrubbles"?!
GNU Debugger
Muitas programadores procuram erros em seus programas espalhando comandos print nas partes suspeitas do código, por mais popular que seja, este é um método bem improdutivo e limitado. O ideal seria poder examinar a memória, conteúdo das variáveis, seqüência de chamada de funções. Podemos fazer tudo isso (e mais uma quantidade exorbitante de coisas) com o GNU Debugger, o popular gdb.
Informações de Depuração
Para aproveitar ao máximo as funções do gdb, precisamos compilar o programa com informações de debugging. Isso significa que serão incluídas informações extras no executável gerado, com a opção -g do gcc, podemos adicionar informação de debugging (depuração) no executável resultante. Esta informação pode vir em vários formatos, os dois mais comuns são stabs, mais antigo, e DWARF 2, mais expressivo, e existem outros. Usaremos então a opção -ggdb, que dirá ao gcc escolher o formato mais expressivo disponível no ambiente
$ gcc main.c func.c -o contador -lm -ggdb
Sessão de Debugging
$ gdb contador
Dentro do gdb, run executa o programa:
(gdb) run
Starting program: /home/user/project/contador
Program received signal SIGSEGV, Segmentation fault.
0x08048453 in conta_caracteres (palavra=0x0) at func.c:5
5 while (palavra[contador] != '\0') {
De cara a saída do gdb para o erro já trás algumas informações úteis. Na segunda linha da saída do gdb temos a identificação do erro, na terceira temos, respectivamente, a posição do código na memória, em nosso caso 0x08048453, nome da função onde ocorreu o erro, valor da variável palavra passada como parâmetro, e indicação de qual linha de qual arquivo fonte está definida a função. Olhando o valor passado para função conta_caracteres, que recebe um ponteiro para char, podemos dizer que foi passado um valor NULL (0x0), e quando, na linha 5, o programa tenta acessar a posição indicada por contador desse array nulo, acontece a falha de segmentação. Simples e sem prints. Mas se você sentir muita falta, um dos vários comandos do gdb é o print, com o qual você pode observar valores como, por exemplo, variáveis do programa em execução.
(gdb) print contador $1 = 0 (gdb) print palavra $2 = 0x0 (gdb)
Backtrace
Mostra um "rastro" das chamadas de funções: funçãoA chamou funçãoB, que chamou funçãoC, que deu pau na linha x.
(gdb) backtrace #0 0x08048453 in conta_caracteres (palavra=0x0) at func.c:5 #1 0x080483f1 in main (argc=1, argv=0xbf849b64) at main.c:15
Na seqüência conta_caracteres foi chamada pelo main. Aqui está bem simples, mas situações normais envolvem chamadas de funções mais "profundas".
Breakpoints
Podemos marcar pontos no código onde desejamos que o gdb pare a execução, por exemplo, numa linha antes de onde aconte o problema, para podermos inspecionar o estado das variáveis antes de continuamos.
(gdb) break main.c:7 Breakpoint 1 at 0x80483d8: file main.c, line 7. (gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/user/project/ Breakpoint 1, main (argc=1, argv=0xbfc92764) at main.c:7 7 double valor = 37.0; (gdb) print valor $1 = -3.8196929852197302e-39 (gdb) print argv[1] $2 = 0x0 (gdb) print argv[0] $3 = 0xbfa4b9e6 "/home/c6c12/mls/src/a.out"
O penúltimo comando mostra o que já sabíamos: o usuário esqueceu de passar (via linha de comando, argv[1]) a palavra a ser contada.
(gdb) cont
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x08048453 in conta_caracteres (palavra=0x0) at func.c:5
5 while (palavra[contador] != '\0') {
O comando cont continua a execução de onde havia parado, voltando para o erro. Já sabemos qual é o erro, mas qual a solução? A função de contagem pode retornar 0 em caso de entrada NULL, ou podemos verificar os parâmetros no main e emitir uma mensagem com instruções de uso.
Bibliotecas de Desenvolvimento
No Debian/Ubuntu nomes de bibliotecas começam com o prefixo lib*, se você quer executar programas com recursos de sbrubbles, você deve instalar o pacote libsbrubbles. Algo que pode causar confusão com quem está começando é obter mensagens de erro de falta de biblioteca ao compilar um programa que usa sbrubbles, sabendo que libsbrubbles está instalada. Em sistemas Debian as bibliotecas foram separadas em "libalgumacoisa" e "libalgumacoisa-dev". A primeira é necessária para executar aplicações que precisem da biblioteca, e a última para compilar aplicações que usem a biblioteca, e inclui os arquivos de cabeçalho e outros itens necessários à compilação.
Exemplo de Biblioteca - GTKmm
Uma biblioteca muito usada para desenvolver aplicações com interface gráfica no Linux (e FreeBSD, Windows, MacOS X) é a GTK+[4]. Ela escrita em C e orientada a objetos. Sim, em C e orientada a objetos! Parece bizarro, mas não só é viável, como trás muitas vantagens, uma delas é poder fazer ligações (bindings) com qualquer linguagem orientada a objetos. Como você pode imaginar, programação OO em C não é das coisas mais naturais, então usaremos como exemplo o binding de GTK+ com C++: GTKmm[5].
Exemplo de Aplicação Gráfica em C++
A aplicação a seguir cria uma janela com um botão Hello World, que ao ser clicado imprime a mensagem "Hello World". A classe HelloWorld extende a classe Gtk::Window, adquirindo suas características. A função main instancia a janela e executa o programa.
hello.h:
#ifndef HELLO_H #define HELLO_H #include <gtkmm/button.h> #include <gtkmm/window.h> class HelloWorld : public Gtk::Window { public: HelloWorld();redhatGCC4.0 virtual ~HelloWorld(); protected: //Signal handlers virtual void on_button_clicked(); //Member widgets Gtk::Button m_button; }; #endif
hello.cpp:
#include <iostream> #include "hello.h" HelloWorld::HelloWorld() : m_button("Hello World") { set_border_width(10); m_button.signal_clicked().connect( sigc::mem_fun(*this, &HelloWorld::on_button_clicked)); add(m_button); m_button.show(); } HelloWorld::~HelloWorld() { } void HelloWorld::on_button_clicked() { std::cout << "Hello World" << std::endl; }
main.cpp:
#include <gtkmm/main.h> #include "hello.h" int main (int argc, char *argv[]) { Gtk::Main kit(argc, argv); HelloWorld helloworld; Gtk::Main::run(helloworld); return 0; }
Compilação Passo a Passo
O compilador de C++ do GCC é o g++.
$ g++ hello.cpp -c `pkg-config gtkmm-2.4 --cflags` $ g++ main.cpp -c `pkg-config gtkmm-2.4 --cflags` $ g++ main.o hello.o -o hello `pkg-config gtkmm-2.4 --libs` $ ./hello
Caso você não saiba, o efeito da crase em trechos como `pkg-config gtkmm-2.4 --cflags`, é executar um outro programa e "colar" seu resultado na linha de comando, e só então executar o programa chamado no início da linha.
Aperte o botão "Hello World" algumas vezes e veja o resultado.
pkg-config
Retorna informações sobre bibliotecas instaladas no sistema, como parâmetros para linha de comando do gcc, estas informações vêm de arquivos .pc, instalados pelos pacotes lib*-dev. Arquivos .pc costumam ficar em /usr/lib/pkgconfig
Faça Você Mesmo: observe a saída de pkg-config e o conteúdo do arquivo .pc da biblioteca gtkmm.
$ pkg-config gtkmm-2.4 --cflags --libs $ cat /usr/lib/pkgconfig/gtkmm-2.4.pc
Conclusão
O que vimos foi um "grande passeio", cada tópico abordado pode ser muito aprofundado, e a intenção aqui foi ter uma visão geral e transportar habilidades de programação no Windows para o ambiente Linux. Muito pode ser acrescentado, como o uso de IDEs como o Eclipse para desenvolvimento no Linux.
Referências
- GCC
- http://gcc.gnu.org
- The GNU Library Reference Manual
- http://www.gnu.org/software/libc/manual/html_node/index.html:
- GNU Make
- http://www.gnu.org/software/make/manual/html_node/index.html
- GDB
- http://www.gnu.org/software/gdb
- Tutorial GDB
- http://www.cs.princeton.edu/~benjasik/gdb/gdbtut.html
- GTKmm
- http://www.gtkmm.org/docs/gtkmm-2.4/docs/tutorial/html/index.html
[1] http://www.linuxjournal.com/article/7413
[2] http://www.redhat.com/magazine/002dec04/features/gcc
[3] http://fabrice.bellard.free.fr/qemu
[4] http://www.gtk.org
[5] http://www.gtkmm.org
| Autor: Marcelo Lira |















