Sistema Operatiu Linux
Unitat: Linux com a entorn de programació
Tema: compilació de programes en 'C'
Introducció: el compilador gcc, conceptes de compilador, preprocessador
i enllaçador.
-
Compilar un programa font significa realitzar una sèrie de
transformacions sobre el text original per a obtenir com a resultat un
fitxer executable
-
El compilador estàndard de C en Unix s'anomena cc. En Linux
tenim a més a més una versió creada per GNU anomenada
gcc.
-
La forma més simple de compilar un fitxer que conté un programa
font en C és:
gcc programa.c
-
Aquest comandament generarà un fitxer executable anomenat a.out,
el qual podrem executar directament.
-
Exemple: seguir les següents passes
-
$ cat > programa.c
-
#include <stdio.h>
-
#define MAX 10
-
main()
-
{
-
int i;
-
for (i = 0; i<MAX; i++) printf("proves\n");
-
}
-
<CTRL + D>
-
$ gcc programa.c
-
$ ./a.out
-
En l'últim pas executem el programa a.out.
-
Per generar un programa executable amb un nom diferent de a.out
utilitzem el modificador -o de gcc:
gcc programa.c -o programa.exe
-
Ara el resultat serà un programa anomenat programa.exe, que
podrem executar:
./programa.exe
-
De fet el procés de compilació comprén diverses fases:
-
fase 1, preprocés: s'inserten en el programa totes les referències
del tipus #include i es sustitueixen les definicions de constants i macros
del tipus #define en el text. El programa que preprocesa el fitxer original
s'anomena cpp i es cridat per el gcc. El resultat del preprocés
és un fitxer amb extensió .i
-
Exemple de preprocés: el modificador -E de gcc atura el procés
de compilació en la primera fase i produeix la sortida per pantalla
del fitxer. Si fem:
gcc -E programa.c | more
veurem el nostre programa amb el fitxer <stdio.h>
inclos.
-
fase 2, compilació: la compilació propiament dita
només produeix un nou text del programa traduït a llenguatge
ensamblador. El programa que efectúa aquesta fase s'anomena cc1
i produeix un fitxer amb extensió .s
-
Exemple de generació del programa en ensamblador: el modificador
-S
de gcc atura el procés en la segona fase per produïr
un fitxer en ensamblador. Si fem:
gcc -S programa.c; cat programa.s
veurem el nostre programa en ensamblador.
-
fase 3, generació del programa objecte: un programa objecte
és aquell que conté instruccions en codi màquina específiques
del processador amb el que treballem (Intel, Alpha, etc.). El programa
as
pren com a entrada el programa en ensamblador i genera com a sortida un
fitxer amb extensió .o que és un programa objecte.
El modificador -c de gcc permet aturar el procés en
aquesta fase per obtenir un fitxer objecte:
gcc -o prograa.o -c programa.c
-
fase 4, enllaç de l'objecte amb les llibreríes: el
programa objecte fa crides a funcions que estan definides en fitxers
independents del nostre programa anomenats llibreríes de funcions
o simplement llibreríes. Per
aquesta raó no podem executar directament un fitxer objecte i cal
aquest últim pas d'enllaç en el qual relacionem el nostre
fitxer objecte amb les llibreríes necesaries per tal d'obtenir el
fitxer executable. El programa ld és l'encarregat d'efectuar l'enllaç.
-
Altres opcions de compilació:
-
-I<directori> especifica un directori diferent del configurat
per defecte on hi han fitxers "include". Per exemple:
gcc programa.c -I/treball/jcuesta/llibreries -o programa.exe
Compilarà el fitxer programa.c per produïr programa.exe,
i en la fase de preprocés buscarà els fitxers include
en els directoris per defecte i també en el directori /treball/jcuesta/llibreries.
-
-O optimitza el codi per tal de que s'executi amb la
màximia velocitat. La compilació serà més lenta
i el fitxer executable ocuparà més espai.
-
-w elimina el missatges de "warning": avisos que efectúa
el compilador quan es troba en alguna operació perillosa o dubtosa.
A diferència dels errors de compilació els warnings no aturan
la compilació i el fitxer executable es genera.
-
-Werror atura el procés de compilació si es detecta
algún warning, tractant-lo com si fos un error.
-
-Wall realitza el màxim número de comprobacions per
assegurar que no fem cap operació perillosa.
-
EXERCICI: _Introduïr en el fitxer prova.c el següent
programa que escriu 10 cops per pantalla una frase:
#include <stdio.h>
main( )
{ int i;
for (i = 0; i < 10; i++) puts("proves
de gcc\n");
}
A continuació realitzeu les següents proves amb
gcc:
1. Obtenir el fitxer prova.i, i llisteu-lo per verificar que
ara inclou el fitxer stdio.h
2. Obtenir el fitxer prova.s, i llisteu-lo per verificar que
conté el programa en ensamblador
3. Obtenir el fitxer prova.o . ¿És pot executar
aquest fitxer? ¿Perquè?
4. Obtenir el fitxer prova.exe i provar-lo
Llibreríes de funcions
-
Els fitxers externs necesaris per crear un executable són de dos
tipus: fitxers inclosos (#include fitxer) i llibreríes.
-
Els fitxers inclosos per norma tenen l'extensió .h (h de
"header", fitxer de capçalera) i són fitxers de text que
contenen defincions de constants, tipus de dades i macros. Estan en els
directoris /usr/include i /usr/src/linux/include. En aquest
últim cas es tracta de fitxers necesaris per realitzar la compilació
del nucli de Linux.
-
A banda dels fitxers inclosos que proporciona el sistema també podem
crear els nostre propis fitxers i utilitzar-los en la compilació,
tal com s'ha explicat en l'opció -I del compilador gcc.
-
En canvi les llibreríes són fitxers que contenen funcions
que ja estan compilades, i per tant en format de codi máquina (fitxers
objectes). El sistema proporciona les llibreríes estàndard
i a més a més podem crear-ne les nostres.
-
El comandament ldd mostra les llibreríes usades en un programa
executable. Per exemple:
[jcuesta@famara jcuesta]$ ldd prova.exe
libc.so.6 => /lib/libc.so.6
(0x40020000)
/lib/ld-linux.so.2 =>
/lib/ld-linux.so.2 (0x40000000)
Veiem que el programa prova.exe utilitza les llibreríes
/lib/libc.so.6 i /lib/ld-linux.so.2 que són las llibreríes
estàndard de C.
-
Hi han dos tipus de llibreríes: estàtiques i dinàmiques.
-
Les llibreríes estàtiques carreguen en memòria central
totes les funcions en el moment que iniciem el programa executable, tant
si les anem a utilitzar com si no. Les funcions de la llibrería
queden incorporades a l'executable. Per tant si copiem el programa en un
altre màquina Linux, s'executarà correctament sense necessitat
de recompilar.
-
Les llibreríes dinàmiques només carreguen en memòria
funcions sota demanda, és a dir a mida que l'executable necessita
les funcions aquesten es carreguen en memòria. Per tant estalviem
memòria central respecte a utilitzar llibreríes estàtiques.
Les funcions de la llibrería no queden incloses en l'executable,
el que s'inclou són referències a les llibreríes.
Per tant si copiem l'executable en un altre màquina haurem de recompilar
amb les llibreríes de l'altre màquina.
PRÀCTICA: Creació d'una llibrería estàtica.
PRÀCTICA: Creació d'una llibrería dinàmica
Seguirem les següents passes:
1.Crear el fitxer objecte amb l'opció fPIC que genera
codi independent de la posició, necessari per usar-lo en llibreríes
compartides. Una llibrería dinàmica compartida (dynamic
shared library) és aquella que permet que diversos programes
en execució puguin enllaçar-se amb ella de forma simultània.
Escriurem:.
gcc -c -fPIC utils.c -o utils.o
2. Incorporar a la llibrería dinàmica l'objecte. Utilitzem
l'opció shared per indicar que la llibrería és
compartida entre diversos programes que la poden utilitzar simultàniament:
gcc -shared utils.o -o biblio.so.1.0
Les llibreríes dinàmiques tenen la següent nomenclatura:
nom.so.versió.revisió . Per tant em creat la llibrería
dinàmica biblio.so en versió 1.0
3. Creem un enllaç a la llibrería dinàmica:
ln -s biblio.so.1.0 biblio.so
4. Incluim el directori on està la nova llibrería
en la llista de directoris de llibreríes del sistema, continguda
en la variable d'entorn LD_LIBRARY_PATH:
LD_LIBRARY_PATH=`pwd`; export LD_LIBRARY_PATH
5. La llibrería dinàmica està llesta. Per
provar-la:
gcc prova.c biblio.so -o prova2.exe
Exercici: Si ara movem o renombrem la llibrería biblio.so.1.0,
continuarà funcionant el programa prova2.exe? Si fem el mateix
amb la llibrería estàtica mibilio.a, funcionarà
el programa prova.exe?
Depuració dels programes amb gdb
-
El programa gdb permet depurar un programa C segons diverses opcions:
execució pas a pas, punts d'interrupció, inspecció
de variables, etc.
-
Pràctica: depurarem el programa programa.c que hem escrit
al principi d'aquest tema. Seguim les següents passes:
1. Recompilem el programa amb l'opció -g per tal de
que el compilador afegeixi l'informació necessaria per el programa
gdb:
gcc -g prova.c biblio.so
-o prova.exe
2. Iniciem gdb:
gdb prova.exe
3. Es mostra el símbol del depurador:
(gdb)
4. Provarem a continuació els diferents comandaments
de gdb:
| run |
executar el programa |
| break [línia/funció] |
afegir un punt d'interrupció en la línia o en la funció |
| step |
executar la següent instrucció |
| kill |
aturar l'execució del programa |
| delete |
eliminar els punts d'interrupció |
| print [variable/funció] |
mostrar el contingut de la variable o el resultat de la funció |
| set variable=valor |
modificar el valor de la variable |
| continue |
continuar executant fins el següent punt d'interrupció
o fins el final |
Taula: comandaments de depuració en gdb
Per exemple:
(gdb) break 4
(gdb) run
(gdb) step
(gdb) print i
(gdb) set i=3
(gdb) continue
(gdb) quit
Exercicis
1. Afegir a la biblioteca mibiblio.a una funció void
linea(int llarg) per escriure una línia d'asteriscs, tants com
indiqui la variable llarg. Recompilar i provar la nova funció
amb un programa de prova.
2. Eliminar la funció utils.o de la llibrería
mibiblio.a Comprobar que ha estat eliminada.
3. Tornar a incloure la funció utils.o en la llibrería
mibiblio.a i comprobar-ho