Un programa en C consiste de uno o más ficheros fuente. Los ficheros fuente contienen prototipado de funciones, definiciones de tipos, definiciones de variables, definiciones de funciones, etc. En uno de estos ficheros y sólo en uno debe existir una función denominada main que es la encargada de iniciar el programa.
Desde el punto de vista modular, en C, cada módulo coincide con un fichero fuente (con extensión ".c"). Este fichero fuente define las variables y funciones del módulo. A cada fichero fuente se le asocia un fichero que tiene el papel de declarar la interfaz del módulo. A estos ficheros se les denomina ficheros cabecera (con extensión ".h"). Los ficheros cabecera, típicamente, contienen las declaraciones de variables por medio de la palabra reservada extern, el prototipado de las funciones accesibles del módulo, la definición de tipos y macros.
Puede que indirectamente o por error en la compilación de un fichero fuente (con extensión ".c") se incluya varias veces el mismo fichero cabecera en cuyo caso si aparece una definición en el ".h" se producirá un error de compilación. Para evitar este posible error en un fichero cabecera si se hacen definiciones de variables globales, de tipos, con typedef o struct, es necesario usar la estructura:
La declaración prototipo de funciones o la declaración mediante extern de variables globales no plantea problemas.
Cada fichero fuente se debe poder compilar por separado. La compilación separada implica que cada fichero fuente debe contener toda la información necesaria para que el compilador pueda compilarlo y generar un fichero objeto. Es de destacar que los ficheros objetos contienen código casi ejecutable a falta de resolver direcciones no conocidas por el compilador en tiempo de compilación. El enlazador es el encargado de reunir los ficheros objetos y los elementos necesarios de la biblioteca de funciones para resolver esas referencias desconocidas y formar un programa ejecutable.
En los entornos de desarrollo actuales, para desarrollar programas, se crea un project en el cual se especifica qué ficheros lo componen. El entorno se encarga de detectar dependencias y compilar y enlazar de forma inteligente sólo los ficheros que han sido modificados desde la última compilación.
En la figura siguiente se muestran los pasos a seguir para construir un ejecutable de un programa multifichero en C, partiendo de los ficheros fuente que lo componen.
Cada fichero fuente se compila por separado obteniendo su correspondiente fichero objeto. Una vez que se tienen todos los ficheros objetos necesarios se enlazan junto a las funciones de la biblioteca para obtener el programa ejecutable.
En la figura anterior se muestran los detalles de la fase de compilación. La compilación de cada fichero fuente requiere de la inclusión de los ficheros cabecera de la biblioteca de funciones necesarios y del resto de los módulos usados. Esta información es necesaria para que el compilador sea capaz de hacer correctamente su trabajo.
El módulo vecutil suministra como servicios tres funciones ordena, maximo y minimo que ordenan un vector de enteros, devuelve el valor máximo y el valor mínimo, respectivamente. Además, informa por medio de una variable global, si se ha producido un error o no, en la ordenación del vector. También define una macro con el tamaño máximo de vector que admite. En la implementación se ocultan dos elementos: la función selOrdena, función interna de la ordenación, y la variable contador, también variable interna que contabiliza el número de llamadas a selOrdena. Estos dos elementos no pueden ser accedidos desde otro módulo.
#include "vecutil.h" #include <limits.h> #include <stdlib.h> static int contador=0; static void selOrdena(int ve[], int vs[], int n){ //Método de ordenación por selección lineal int i, menor, pos, j; for(i=0;i<n;i++){ menor=ve[0]; pos=0; for(j=1;j<n;j++) if(menor > ve[j]){ menor=ve[j]; pos=j; } vs[i]=menor; ve[pos]=INT_MAX; } } int vecerror=0; void ordena(int v[], int n){ static int aux[MAXVEC]; int i; if(v==NULL || n==0){ //Control de errores vecerror=1; return; } contador++; //Contabiliza el número de llamadas a selOrdena selOrdena(v,aux,n); for(i=0;i<n;i++) v[i]=aux[i]; } int maximo(int v[], int n){ //Calcula el máximo elemento de un vector int i, maximo=v[0]; for(i=1;i<n;i++) if(maximo < v[i]) maximo=v[i]; return maximo; } int minimo(int v[], int n) { //Calcula el mínimo elemento de un vector int i, minimo=v[0]; for(i=1;i<n;i++) if(minimo > v[i]) minimo=v[i]; return minimo; }
#ifndef INC_VECUTIL #define INC_VECUTIL #define MAXVEC 10000 extern int vecerror; void ordena(int v[], int n); int maximo(int v[], int n); int minimo(int v[], int n); #endif
El módulo principal contiene la función main, la cual llama a las funciones del módulo vectutil y accede a la variable vecerror. La función main, lee en un vector un número de enteros, muestra el mínimo, el máximo y ordena el vector.
#include <stdio.h> #include "vecutil.h" int main() { int n=MAXVEC, i; int vec[MAXVEC]; for (i=0; i<n; i++) { //Lectura de los elementos del vector printf("leer el %d-ésimo elemento ", i);scanf("%d",&vec[i]); } printf("el elemento máximo del vector es %d", maximo(vec,n)); //Muestra el máximo printf("el elemento mínimo del vector es %d", minimo(vec,n)); //Muestra el mínimo ordena(vec,n); //Ordena el vector if(vecerror) printf("Error en la ordenación\n"); return 0; }