compilacion
Diferencias
Muestra las diferencias entre dos versiones de la página.
Próxima revisión | Revisión previa | ||
compilacion [2012/09/04 13:15] – creado lmateu | compilacion [2014/09/04 13:42] (actual) – [Preproceso] lmateu | ||
---|---|---|---|
Línea 1: | Línea 1: | ||
- | ===== Fases de la Compilación ===== | + | ===== Etapas |
La compilación de un programa en C pasa por varias etapas desde | La compilación de un programa en C pasa por varias etapas desde | ||
Línea 27: | Línea 27: | ||
este archivo puede ser muy grande cuando Ud. incluye archivos | este archivo puede ser muy grande cuando Ud. incluye archivos | ||
de encabezado como stdio.h. | de encabezado como stdio.h. | ||
+ | |||
+ | Esta etapa la lleva a cabo un proceso independiente denominado cpp | ||
+ | (que viene de //C preprocessor// | ||
A continuación se explican las directivas más usadas. | A continuación se explican las directivas más usadas. | ||
Línea 46: | Línea 49: | ||
Recuerde que cada identificador que se usa en C debe haber sido declarado previamente, | Recuerde que cada identificador que se usa en C debe haber sido declarado previamente, | ||
modo se considera un error. | modo se considera un error. | ||
- | de un tipo en un archivo, pero de otro en otro archivo. | + | de un tipo en un archivo, pero de otro tipo en otro archivo. |
este caso porque simplemente no tiene la información para detectar el error. | este caso porque simplemente no tiene la información para detectar el error. | ||
se denomina compilación **separada**. | se denomina compilación **separada**. | ||
Línea 74: | Línea 77: | ||
% gcc -E prog.c | % gcc -E prog.c | ||
| | ||
- | La salida | + | La salida |
int main(int argc, char **argv) { | int main(int argc, char **argv) { | ||
Línea 112: | Línea 115: | ||
#endif | #endif | ||
+ | ==== La compilación ==== | ||
- | | + | Esta etapa recibe el resultado del preproceso que consisten en un gran archivo sin directivas #. |
+ | El archivo es grande porque se han incluido textualmente todos los archivos de encabezados con | ||
+ | las declaraciones de funciones de biblioteca especificadas por los #include. | ||
+ | |||
+ | La etapa compila las funciones y produce código en el assembler específico de la plataforma. | ||
+ | código es de muy bajo nivel pero todavía legible porque cada instrucción usa un nombre para identificarla. | ||
+ | Este código no es directamente ejecutable por la máquina. | ||
+ | |||
+ | La opciónes más importantes de esta etapa son: | ||
+ | |||
+ | * -O instruye al compilador para generar un código más eficiente en tiempo de ejecución. | ||
+ | * -g genera tablas que permiten depurar el programa con un depurador como gdb. Esto es lo que permite por ejemplo que el depurador pueda determinar la posición de las variables locales dentro del registro de activación de una función. | ||
+ | * -S detiene gcc justo después de la compilación para poder estudiar el código en assembler (un archivo con la extensión ' | ||
+ | |||
+ | ==== El ensamblaje ==== | ||
+ | |||
+ | Esta etapa recibe el archivo en assembler (extensión ' | ||
+ | por la máquina (archivo con extensión ' | ||
+ | referencias pendientes a funciones y variables definidas en otros archivos. | ||
+ | |||
+ | Gcc admite la opción -c para para detener el proceso justo después del ensamblaje. | ||
+ | |||
+ | Esta etapa la lleva a cabo un proceso independiente denominado as (que viene de assembler). | ||
+ | |||
+ | ==== Link ==== | ||
+ | |||
+ | Esta etapa es la que se encarga de juntar todos los archivos ' | ||
+ | correspondiente al binario ejecutable. | ||
+ | Esta etapa la realiza el comando ln (que viene de link). | ||
+ | pendientes, es decir las llamadas a funciones definidas en otros archivos. | ||
+ | |||
+ | Por ejemplo supongamos que tenemos 2 archivos a.c y b.c: | ||
+ | |||
+ | /* Este es a.c */ | ||
+ | int g(int x); | ||
+ | int a= 1; | ||
+ | float b; | ||
| | ||
+ | int f() { | ||
+ | b= 1.0; | ||
+ | return g(a); | ||
+ | } | ||
+ | |||
+ | En a.c la referencia pendiente es g porque el ensamblador no puede determinar cual es la dirección de | ||
+ | la función g. | ||
+ | |||
+ | /* Este es b.c */ | ||
+ | #include < | ||
+ | extern int a; | ||
+ | float b; | ||
+ | | ||
+ | int main() { | ||
+ | int x= f(); | ||
+ | printf(" | ||
+ | return x; | ||
+ | } | ||
+ | | ||
+ | int g(int x) { | ||
+ | a= 2; | ||
+ | return x; | ||
+ | } | ||
+ | |||
+ | Aquí las referencias pendientes son a y f. Observe que a fue declarada como ' | ||
+ | al ensablador que no reserve espacio para ella porque se trata de una promesa de que otro archivo | ||
+ | la va a declarar. | ||
+ | ' | ||
+ | |||
+ | La fase de link en Unix la realiza el comando '' | ||
+ | un error histórico, porque su tarea no es la de un //loader// (cargardor). | ||
+ | es la componente del núcleo del sistema operativo que carga un archivo ejecutable en la memoria del | ||
+ | computador para que sea ejecutado. | ||
+ | se conserva quizás por compatibilidad. | ||
+ | |||
+ | % ld a.o b.o ... otros argumentos ... | ||
+ | |||
+ | |||
+ | ¿Pero que pasa con b que aparece declarada en dos archivos? | ||
+ | eliminando una de las declaraciones. | ||
+ | se puede hacer si la variable se inicializa en los 2 archivos (aunque sea el mismo valor). | ||
+ | En ese caso el linker reporta la variable como una definición múltiple. | ||
+ | |||
+ | === Inconsistencia de tipos === | ||
+ | |||
+ | Cuidado, el linker no realiza ninguna verificación de tipos. | ||
+ | que el archivo a.c contiene: | ||
+ | |||
+ | int a= 1; | ||
+ | | ||
+ | Y el archivo b.c contiene: | ||
+ | |||
+ | #include < | ||
+ | extern float a; | ||
+ | | ||
+ | int main() { | ||
+ | printf(" | ||
+ | } | ||
+ | |||
+ | El resultado es impredescible y no hay ninguna advertencia del cambio de tipo en la variable. | ||
+ | siempre se recomienda declarar funciones y variables compartidas en archivos de encabezados para | ||
+ | evitar este tipo de errores. | ||
+ | |||
+ | === Static === | ||
+ | |||
+ | Un error típico en grandes proyectos ocurre cuando 2 archivos implementan funciones con el mismo nombre. | ||
+ | El linker no puede determinar cual usar, y reporta la función como una definición múltiple. | ||
+ | |||
+ | Para disminuir este tipo de errores se puede limitar la visibilidad de una funciona o variable global | ||
+ | a solo el archivo en donde declara: | ||
+ | |||
+ | static int f() { | ||
+ | ... | ||
+ | } | ||
+ | |||
+ | De esta forma si otra función f se declara en otro archivo, no habrá colisión de nombres. | ||
+ | Observe que uso de static acá no tiene nada que ver con el atributo static de Java. | ||
+ | |||
+ | ==== Las bibliotecas ==== | ||
+ | |||
+ | Las bibliotecas son archivos con la extensión ' | ||
+ | de manejarlos como una unidad. | ||
+ | |||
+ | /* bib1.c */ | ||
+ | void g(); | ||
+ | | ||
+ | void f() { | ||
+ | g(); | ||
+ | } | ||
+ | |||
+ | /* bib2.c */ | ||
+ | void g() { | ||
+ | } | ||
+ | |||
+ | /* bib3.c */ | ||
+ | void h() { | ||
+ | } | ||
+ | |||
+ | Compilamos ambos archivos con: | ||
+ | |||
+ | % gcc -c bib1.c bib2.c bib3.c | ||
+ | |||
+ | Lo que genera los archivos bib1.o y bib2.o. | ||
+ | |||
+ | % ar r bib.a bib1.o bib2.o bib3.o | ||
+ | |||
+ | Ahora podemos usar f o g desde otro programa: | ||
+ | |||
+ | /* a.c */ | ||
+ | int main() { | ||
+ | f(); | ||
+ | } | ||
+ | |||
+ | Compilamos incluyendo la biblioteca con: | ||
+ | |||
+ | % gcc a.c bib.a | ||
+ | |||
+ | Como a.o incluye una referencia pendiente a f, el linker la busca en las bibliotecas y la encuentra | ||
+ | en bib1.o, por lo tanto agrega bib1.o completo al binario. | ||
+ | referencia pendiente a g y la encuentra en bib2.o al binario. | ||
+ | lado, el archivo bib3.o no se agrega al binario ejecutable. | ||
+ | |||
+ | Uno de los bugs más difíciles de diagnosticar que me ha tocado presenciar ocurrió cuando | ||
+ | un función de biblioteca llamaba a otra función dentro de la misma biblioteca pero que casualmente | ||
+ | se llamaba igual que una variable global de los fuentes. | ||
+ | si no que rellenó la dirección con la de la variable global, que ni siquiera erá código ejecutable. | ||
+ | ¡El programa se caía con un segmentation fault inexplicable! | ||
+ | |||
+ | Esto se puede reproducir cambiando a.c por: | ||
+ | |||
+ | /* a.c */ | ||
+ | float g= 3.14; | ||
+ | int main() { | ||
+ | f(); | ||
+ | } | ||
+ | |||
+ | Ejercicio: Compile y ejecute para observar el segmentation fault. | ||
+ | la ejecución hacia la variable en punto flotante g, como si ahí hubiesen instrucciones. |
compilacion.1346764526.txt.gz · Última modificación: 2012/09/04 13:15 por lmateu