www.delorie.com/djgpp/doc/ug/larger/multisrc.html   search  
Guía: Múltiples Ficheros Fuente

En cuanto se adquiere algo de experiencia escribiendo software se descubre que hacer todo en un único fichero fuente no va a funcionar. Es como guardar toda la ropa en una única caja grande - al final los calcetines acaban perdidos en el fondo. La mayoría de programas están compuestos de más de un fichero fuente, y cada fuente contiene un "módulo" de funcionalidad, normalmente independientes independiente de modo que se pueda trabajar con el? por separado y (con suerte) probarlo también por separado.

Otra razón para usar múltiples fuentes es para poder escribir diferentes partes del programa en distintos lenguajes. Por ejemplo, gcc utiliza C para la mayoría de los módulos, pero usa bison para compilar la gramática. Cuando se usan varios fuentes tambien se reduce el ciclo de depuración, porque la mayoría de los fuentes ya estarán compilados cuando tengas que recompilar tu programa tras arreglar ese último bug :-)

En cualquier caso, el propósito de esta parte de la Guía es ayudarte a entender cómo crear un programa que está constituido por más de un fichero fuente.

La primera cosa que debes entender es el concepto de "símbolo global". Los Símbolos son cosas tales como funciones y variables. Los símbolos globales son aquellos que son accesibles para todo el programa. Por ejemplo:

int c;

int foo(int x)
{
  int y = x * 2 + 1 + c;
  printf("foo(%d)=%d\n", x, y);
}

En este fragmento de código, los símbolos c y foo son globales; c porque es una variable declarada fuera de cualquier función, y foo porque es una función que otras partes del programa serán capaces de usar. Hay también otro símbolo global - printf. Aunque este fragmento no define printf, si usa este simbolo. La diferencia está en que foo está definida por nosotros y printf es exterior para nosotros.

Considera estas dos funciones, cada una en su propio fichero fuente:

int foo(int x)                  int bar(int j)
{                                 {
  return bar(x) + 47;           printf("j is %d\n", j);
}                                     return j*j;
                                   }

En este ejemplo, el fichero de la izquierda define el símbolo global foo y hace referencia al símbolo externo bar. El fichero de la derecha, en cambio, define el símbolo globar bar y hace referencia al símbolo externo printf.

Cuando compilas estos ficheros para obtener los ficheros objeto, usando "gcc -c foo.c" y "gcc -c bar.c" (adelante prúebalo), el compilador no sabe cómo resolver esas referencias a los símbolos que no conoce. ¿Qué es lo que hace? Prácticamente nada. Tan sólo escribe la información que le falta en el fichero objeto, y aplaza el problema para después. ¿Cuando es después? Una vez que están todos los ficheros compilados, hay que enlazarlos. Unix llama a esto "loading" (carga), algo como "cárgalos todos en un programa", por lo tanto, el programa que hace esto se llama ld (de "loading"), aunque es gcc el que se encarga de llamar a ld por tí.

Cuando se combinan estos ficheros objeto (junto con la biblioteca estándar de C, lo que sucede automáticamente), el enlazador (ld) crea una gran lista con todos los símbolos globales, definidos y externos, que se encuentran en todos los objetos. Despues asigna las referencias a los símbolos con sus definiciones. Si aún así hay algun símbolo sin definir, se produce un error - "unresolved external". Si no falta ninguno , ya tienes tu programa!

Otra cosa que hace el enlazador (linker) es asegurarse de que no haya símbolos definidos en dos ficheros distintos (o más). Por ejemplo, ningún programa puede tener dos funciones llamadas foo - ¿qué sentido tendría? Esto se aplica también a las variables, que es donde tropieza mucha gente. Observa estos dos ficheros:

int size = 42;			extern int size;

En este ejemplo, el fichero de la izquierda define la variable size y el derecho hace referencia a la variable externa size. Cuando los enlazas juntos, todo es perfecto. Ahora considera esto:

int size = 42;			int size;

En este caso, ambos ficheros definen la variable size, así que cuando los enlazas hay un problema. Aunque esto parece obvio, mucha gente pone la definición de las variables en un fichero de encabezamiento (.h "header file"), que se incluye en múltiples ficheros fuente, y por tanto (sin darse cuenta definen las variales) se define una vez en cada fichero. ¡Usa siempre extern en los ficheros de encabezamiento! Recuerda también que extern solo no definirá el símbolo. La definición de la variable debe estar (tambien, por lo menos, en uno) en alguno de los fuentes.

Muy bien, ya vale de símbolos. ¿Cómo se compilan y enlazan estos ficheros? Empecemos con estos dos ficheros fuente, main.c y foo.c:

int main(void)                            int foo(int x)
{                                             {
  printf("foo(4)=%d\n", foo(4));      return 2*x+1;
  exit(0);                                   }
}

Lo primero que debemos hacer es compilar estos ficheros para obtener ficheros objeto. Usa un comando como este:

gcc -c main.c
gcc -c foo.c

Podrías haber usado también "gcc -c main.c foo.c", los resultados son los mismos. Sin embargo, el método de los dos comandos es mejor una vez que aprendes a usar make para automatizar las compilaciones. El resultado son dos ficheros nuevos en tu directorio, main.o y foo.o. Estos son los ficheros objeto. Cada uno contiene las versiones compiladas de los fuentes, junto con las tablas de símbolos y otras informaciones.

Para ver las tablas de símbolos, prueba este comando:

nm main.o foo.o

Para generar el programa final, usa gcc de nuevo:

gcc main.o foo.o -o main.exe

Cuando ejecutes main.exe, verás que ¡muestra el valor correcto! Si no, puedes editar tan sólo uno de los ficheros, recompilar sólo ese, y simplemente enlazar los dos ficheros objetos al final. Aunque esto no parece que ahorre mucho, imagínate tener que construir gcc. Tiene cientos de ficheros fuente y tarda unos 20 minutos para construirlo desde cero. Si se edita y compila tan sólo uno de los ficheros fuente se tarda menos de un minuto, incluida la creación del programa. Un buen ahorro ¿no?

Traducido por: [Gorka's web] (12 Enero 2000)
Revisado por: [www.cevallos.de] (1/5/2000)

  Copyright © 1997   by DJ Delorie     Updated Jan 1997