Herramientas de usuario

Herramientas del sitio


compilacion

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Próxima revisión
Revisión previa
compilacion [2012/09/04 13:15] – creado lmateucompilacion [2014/09/04 13:42] (actual) – [Preproceso] lmateu
Línea 1: Línea 1:
-===== Fases de la Compilación =====+===== Etapas de la Compilación =====
  
 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, de otro Recuerde que cada identificador que se usa en C debe haber sido declarado previamente, de otro
 modo se considera un error.  El peligro que se corre es declarar una variable o función modo se considera un error.  El peligro que se corre es declarar una variable o función
-de un tipo en un archivo, pero de otro en otro archivo.  El compilador no reclama en+de un tipo en un archivo, pero de otro tipo en otro archivo.  El compilador no reclama en
 este caso porque simplemente no tiene la información para detectar el error.  Por eso este caso porque simplemente no tiene la información para detectar el error.  Por eso
 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 estandar mostrará:+La salida estándar mostrará:
  
   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.  Este 
 +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.  Normalmente no se usa esta opción en conjunto con -O. 
 +  * -S detiene gcc justo después de la compilación para poder estudiar el código en assembler (un archivo con la extensión '.s'). 
 + 
 +==== El ensamblaje ==== 
 + 
 +Esta etapa recibe el archivo en assembler (extensión '.s') y produce instrucciones ejecutables directamente 
 +por la máquina (archivo con extensión '.o').  Sin embargo el archivo todavía no es ejecutable porque contiene 
 +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 '.o' y generar un solo gran archivo 
 +correspondiente al binario ejecutable.  Si no se especifica un nombre, por omisión se usa 'a.out'
 +Esta etapa la realiza el comando ln (que viene de link).  En ella se resuelven las referencias 
 +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 <stdio.h>
 +  extern int a;
 +  float b;
 +  
 +  int main() {
 +    int x= f();
 +    printf("%d %d %f\n", x, a, b);
 +    return x;
 +  }
 +  
 +  int g(int x) {
 +    a= 2;
 +    return x;
 +  }
 +
 +Aquí las referencias pendientes son a y f.  Observe que a fue declarada como 'extern' lo que le dice
 +al ensablador que no reserve espacio para ella porque se trata de una promesa de que otro archivo
 +la va a declarar.  Por otra parte b no es una referencia pendiente porque no lleva el atributo
 +'extern'.
 +
 +La fase de link en Unix la realiza el comando ''ld'', que viene de //loader// Sin embargo este es
 +un error histórico, porque su tarea no es la de un //loader// (cargardor).  Normalmente el cargador
 +es la componente del núcleo del sistema operativo que carga un archivo ejecutable en la memoria del
 +computador para que sea ejecutado.  De todas formas, el nombre
 +se conserva quizás por compatibilidad.  Gcc invoca el linker de esta forma:
 +
 +  % ld a.o b.o ... otros argumentos ...
 +
 +
 +¿Pero que pasa con b que aparece declarada en dos archivos?  El linker resuelve el problema
 +eliminando una de las declaraciones.  La variable aparecerá una sola vez.  Pero esto no
 +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.  Por ejemplo consideremos
 +que el archivo a.c contiene:
 +
 +  int a= 1;
 +  
 +Y el archivo b.c contiene:
 +
 +  #include <stdio.h>
 +  extern float a;
 +  
 +  int main() {
 +    printf("%f\n", a);
 +  }
 +
 +El resultado es impredescible y no hay ninguna advertencia del cambio de tipo en la variable.  Por eso
 +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 '.a' que empaquetan un sin número de archivos '.o' con el objeto
 +de manejarlos como una unidad.  Por ejemplo supongamos que tenemos los siguientes archivos:
 +
 +  /* 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.  Creamos la bibliloteca bib.a con:
 +
 +  % 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.  Pero ese a su vez tiene una
 +referencia pendiente a g y la encuentra en bib2.o al binario.  Como h no se referencia en ningún
 +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.  El linker no cargó la función de la biblioteca
 +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.  Cuando se invoca g, se redirige
 +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