La parte que más liosa me resulta del lenguaje C es la que corresponde a la programación modular.
Como a veces estoy largas temporadas sin programar en este lenguaje, cada vez que tengo que retomar algún proyecto me cuesta bastante volver a asimilar estos conceptos.
Es por ello que he decidido escribir este mini-tutorial, y también por si de paso, pudiera interesarle a alguien más :-)
Introducción
Si estamos desarrollando un programa en C y
vemos que nuestro único fichero fuente empieza a crecer y crecer, es
hora de replantearse realizar un diseño modular y dividir el programa
en varios ficheros fuente o módulos.
Cada módulo se encargará de
una función concreta: dibujar en pantalla, capturar el teclado, generar
sonidos, etc... y se compilará por separado para enlazarse
posteriormente y generar el ejecutable.
Aunque la distribución
del código en varios ficheros fuentes no es muy complicada puede
generar problemas al principio, ya que introduce nuevos conceptos que
antes se pasaban por alto, como interfaz, ficheros de cabecera,
variables globales, etc...
Interfaz: Un poco de Teoría
En
términos generales de programación, la interfaz define los métodos de
comunicación que un módulo hace públicos para que los demás se
comuniquen con él.
Por norma general la interfaz se debe
mantener aparte de la implementación del módulo para separar la
comunicación interna con la externa. De esta forma, si cambiamos la
implementación pero dejamos intacta la interfaz, los demás módulos
podrán seguir comunicándose de la misma manera con nuestro módulo.
Veamos
la utilidad de esto: imaginemos que tenemos un programa en C con varios
módulos y uno de ellos está poco optimizado o presenta fallos. Si somos
capaces de mejorar o arreglar el código de ese módulo sin modificar su
interfaz, no tendremos que realizar modificaciones en el resto de los
módulos que se comunican con él.
Archivos de cabecera
En C, la interfaz de un módulo son las funciones, variables,
tipos de datos y constantes que se hacen públicas al resto de los
módulos.
Por lo tanto, por cada módulo modulo.c existirá un fichero de cabecera modulo.h, que contendrá la declaración de la interfaz de ese módulo.
Los elementos que son de uso interno del módulo no se deben declarar en el fichero cabecera.
Los módulos que vayan a utilizar recursos de un módulo llamado modulo.c tendrían que incluir su fichero de cabecera con #include "modulo.h" con lo que se incluyen las declaraciones públicas de ese módulo.
Normalmente
es el programador que ha creado la librería quien proporciona el
fichero de cabecera. Para declarar las funciones y variables externas
de la librería, el usuario que quiera usar la librería simplemente
deberá incluir el fichero de cabecera.
Variables globales
Imaginemos que queremos compartir una variable entre varios módulos. Las opciones que tenemos son dos:
- Pasar esa variable como parámetro.
- Utilizar variables globales.
Si
tenemos muchas variables que queremos compartir entre módulos, la
primera opción redundaría en largas listas de parámetros para cada
función. Por ello, la opción más cómoda y eficiente suele ser usar
variables globales.
Para que una variable sea global se debe
definir (sólo una vez) fuera de cualquier función. A su vez, debe ser
declarada en cada módulo que quiera acceder a la variable.
Para
evitar problemas de ambigüedad entre definición y declaración de una
variable global es conveniente que a la hora de declarar la variable
global usemos el calificador extern. Con ello le estamos
diciendo al compilador que estamos solamente declarando la variable y
que la definición estará en algún otro lado (en otro fichero o después
en el mismo fichero).
Nota sobre variables static:
El significado del calificador static variará dependiendo de si se aplica a una variable global o interna:
- Variable global: Limita la visibilidad de la variable al fichero fuente que estamos compilando
- Variable
interna: funciona igual que una variable automática con la única
diferencia de que el valor de la variable permanece aunque la variable
pierda su visibilidad.
Ejemplo de Archivo de Cabecera
Imaginemos un módulo movimiento.c encargado del movimiento de un juego.
Este módulo tiene 2 funciones MoverPulga() y MoverEnemigo() que queremos que sean accesibles desde otros módulos. Además tiene una función llamada AplicarGravedad() que es de uso interno, es decir, sólo la usarán las funciones de este módulo.
Imaginemos que además el módulo tiene dos variables: posicionPulga que queremos que sea accesible desde otros módulos y la variable rapidezPulga, que es de uso interno a la función MoverPulga().
Nuestro archivo de cabecera movimiento.h quedaría de la siguiente forma:
[movimiento.h]
void MoverPulga()
void MoverEnemigo()
extern int posicionPulga
Como vemos sólo hemos declarado los elementos que queremos hacer públicos al resto de los módulos.
Para
poder usar estas funciones y variables desde otro módulo deberemos
declararlas primero. Como ya están declaradas en movimiento.h es más
cómodo escribir simplemente
#include "movimiento.h" (es decir, estamos incluyendo el código del fichero movimiento.h a nuestro código) que andar declarándolas una a una.