Herramientas de usuario

Herramientas del sitio


compilacion

¡Esta es una revisión vieja del documento!


Fases de la Compilación

La compilación de un programa en C pasa por varias etapas desde que se tienen los fuentes escritos en C hasta generar el archivo binario directamente ejecutable por la máquina.

Todas estas etapas son gatilladas directamente por el compilador (por ejemplo gcc), pero algunas de ellas implican lanzar otros procesos como el preprocesador, el ensamblador o el linker.

Hay que considerar que C fue diseñado para favorecer la compilación separada, es decir cada archivo se compila en una forma absolutamente independiente de los demás archivos.

A continuación estudiaremos la secuencia de pasos.

Preproceso

La primera etapa de la compilación y consisten en expandir las directivas del preprocesador. Ud. siempre puede examinar como se va a expandir un fuente '.c' usando la opción -E del preprocesador:

% gcc -E prog.c

Arroja a la salida estándar el resultado del preproceso. Ojo: este archivo puede ser muy grande cuando Ud. incluye archivos de encabezado como stdio.h.

A continuación se explican las directivas más usadas.

#include

Por ejemplo:

#include <stdio.h>
#include "mis_definiciones.h"

En realidad se puede incluir cualquier archivo, no necesariamente tiene que tener la extensión '.h' pero por convención esa es la que usa. Aquí típicamente se encuentran:

  • Las definiciones de tipos y estructuras compartidas por todos los archivos.
  • Los encabezados de funciones compartidas por todos los archivos.
  • Definiciones de macros

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 de un tipo en un archivo, pero de otro en otro archivo. El compilador no reclama en este caso porque simplemente no tiene la información para detectar el error. Por eso se denomina compilación separada.

Por esta razón entonces se declaran todas la variables y funciones compartidas en los archivos de encabezado. Si se requiere cambiar un tipo se cambia en el encabezado y así cambia en todas partes. Si por error el tipo declarado en el encabezado no coincide con el tipo en la definición de una función, el compilador sí reclama porque tiene la información. Pero esto es solo una convención: nada impide que Ud. declare un encabezado en cada archivo que usa la función.

#define

Se usa para definir macros que se expanden literalmente. Por ejemplo supongamos que el archivo prog.c contiene:

#define N 100
#define MAX(a,b) a>b ? a : b
int main(int argc, char **argv) {
  char buf[N];
  int k= 1+MAX(argc,N);
  ...
}

Si Ud. usa:

% gcc -E prog.c

La salida estandar mostrará:

int main(int argc, char **argv) {
  char buf[100];
  int k= 1+argc>N ? argc : N;
  ...
}

¡Observe que la expresión resultante de la substitución de MAX no queda con la parentización intuitiva! 1+argc se compara con N.

Para evitar este error se recomienda siempre usar exceso de paréntesis en las macros. La forma más segura de definir MAX es la siguiente:

#define MAX(a,b) ((a)>(b)?(a):(b))

Aún así pueden ocurrir situaciones no esperadas como la siguiente:

int i,j;
...
i= MAX(i++,j);  /* Se expande a ((i++)>(j)?(i++):(j))

Es decir i se incrementa 2 veces. Por eso siempre recuerde: la expansión de las macros es literal. También considere que no existen las macros recursivas porque no habría forma de parar la recursión y el tamaño de lo expandido sería infinito.

#ifdef

Se usa para activar/desactivar código dependiendo de si alguna macro está definida. Por ejemplo:

#ifdef N
  char buf[N];
#else
  char buf[100];
#endif
compilacion.1346764526.txt.gz · Última modificación: 2012/09/04 13:15 por lmateu