HeadersA pedido de elgarbe pongo un par de headers de ejemplo y para que lo usariamos, tambien como toda persona tengo dudas y probando traigo algunas "curiosidades". Asi que no lo tomen como si fuera la biblia por que solamente
es mi opinion. Pero no creo que difiera de varias personas.
¿ Que pueden ir en los headers ?Realmente en los headers puede ir de todo. Incluso declaraciones de variables. Es decir:
Pero ¿ es lo correcto ?. Pensemos por un momento para que sirve un header. En nuestros archivos fuentes podemos:
- Crear funciones
- Declarar los prototipos a las funciones creadas
- Utilizar #define para algunas macros / constantes
- Poner un enum, struct, typedef
- Declarar variables
¿ para que sirve el .h si todo lo podemos hacer desde el los archivos .c ?Hay una sola cosa que en la lista parece no estar. Y es que si yo quisiera tener esos #define en varios archivos .c deberia copiarlos en todos, y peor aun, si necesitara cambiar 1 solo parametro deberia recorrerme todos los archivos para cambiarlo ( que seguro me olvidaria de alguno ). Entonces seria mejor crear un .h e incluirlos en todos.
Nuestros .h son archivos compartidos. Uno podria pensar equivocamente que el .h sirve como "nexo" a un archivo fuente. El mejor ejemplo es cuando creamos nuestras librerias. En el que ponemos todos los prototipos y defines que necesite nuestra libreria.c y para usar las funciones incorporamos ese .h a el main.c . Pero es posible crear un nexo entre las librerias sin usar un header.. Este simple ejemplo lo demuestra:
auxiliar.cvoid funcion1(int x){
int i,j;
for (i=0;i<10;i++)
{
j+=i;
}
}
main.cextern void funcion1(int x);
void main(void) {
while(1)
{
funcion1(10);
}
}
Incluso sin el extern funcionaria, esto por que los protipos a funciones vienen explicito el "extern". Y que deberia agregarse para que la persona que lo lea sepa que corresponde a otro archivo. Si es que ocurre el caso anterior que di el ejemplo.
Entonces no sirve de "nexo",ya que podemos crear un nexo sin un archivo header. Pero si para compartir datos que usemos en ambos lados. Supongamos ahora el mismo codigo pasa un puntero a una struct. Pero obviamente tengo que definir la estructura en ambos lados. Y por que soy bastante vago y no quiero escribir muchos "struct" tambien creo un tipo con el typedef. Tengo #defines y enums para usar en ambos archivos. Para este ejemplo cree 3 archivos:
auxheader.h#ifndef AUXHEADER_H
#define AUXHEADER_H
#define START 0
#define END 10
enum { ESTADO_INICIAL = 0,
ESTADO_NUM2,
ESTADO_NUM3 };
struct myStruct {
char *ptr;
int large;
int end;
};
typedef struct myStruct myS;
void funcion1(myS *segundaStruct, int valor1 , int valor2);
#endif /* AUXHEADER_H */
En el header tengo varias cosas que voy a usar, entre ellas estan unos defines, un par de enum, la estructura, como dije el typedef y finalmente el prototipo. Como voy a usar TODO en ambos archivos entonces lo incluyo en ambos y asi sin necesidad de repetirlo en los .c puedo usarlos.
main.c#include "auxheader.h"
void main(void) {
myS nuevaStruct;
nuevaStruct.end = ESTADO_INICIAL;
nuevaStruct.large = ESTADO_NUM3;
while(1)
{
funcion1(&nuevaStruct, START, END);
}
}
auxiliar.c#include "auxheader.h"
void funcion1(myS *segundaStruct, int valor1 , int valor2){
int i,j;
segundaStruct->end = ESTADO_NUM2;
for (i=valor1;i<valor2;i++)
{
j+=i;
}
}
A veces no es necesario compartir todo, podemos tener 10 funciones en nuestro auxiliar.c y tal ves solo queremos que los demas archivo tengan acceso a 2 funciones nomas. Entonces para eso podemos crear 2 headers. Uno para uso externo ( conteniendo esas 2 funciones y las definiciones necesarias ) y uno para uso interno ( conteniendo los prototipos /definiciones que se usan en ese archivo ).
¿Que ocurre si creamos una variable en nuestro header? ( yo tambien me lo preguntaba )
Como esto es nuevo para mi tambien, me refiero al jugar con los headers al punto de hacer algo "ilogico". Curiosamente esperaba un error. Ya que si incorporamos el header a 2 archivos es como si cada archivo tuviera su propia variable, o mejor dicho es como si declaramos 2 veces la variable con el mismo nombre, y pensaba que el linker iba a acusarme. Pero en XC8 funciono como si en un archivo hubiera dicho que estaba definido como externo y en el otro global, es decir ambas variables eran las mismas, en main.c cambiaba el valor y en auxiliar.c podia cambiarlo tambien. Interesante para probar y ver si se cumple en algun otro compilador.
Y como siempre ocurre, aparecen mas preguntas..
¿Debo poner una variable en el header?Personalmente pienso que no. Es mucho mas comodo para una persona ver la variable en cuestion en el .c que estar buscando en los .h cada una de las variables. Y si, no es algo raro de ver. Vi varias personas como se manejan unicamente con un solo archivo ( main.c ) en el que se vuelven super extensos, crean sus headers con todas las declaraciones de variables posibles para tratar de "ordenar" un poco las cosas. Entonces hay que tener 2 archivos abiertos para ir viendo cual variable hay para poder usarlas. Creo que pasa por una decision personal pero si vamos a legibilidad mejor seria en el .c . ( Aqui hablo de incluir el header solo en un solo archivo y no en varios )
Y para finalizar y dar un resumen sobre que poner y que NO poner en las cabeceras ( a mi recomendacion ) seria:
En un .h iria: Todo aquello que se desea
compartir entre varios archivos para que los demas lo usen y que
no ocupe espacio fisico.
- typedef
- Definiciones de estructuras, ojo definiciones nomas, decir cuales son sus elementos, nada de crear una estructura
- Defines ya sea constantes o macros
- enum
- prototipos de funciones
Y recordar que se puede hacer 2 headers o mas (me refiero a uno privado y otro publico ). Para un mismo archivo.
------------------------------------------------------------------------
ExternRespecto a cuando usar extern. Preguntado por elgarbe tambien.
Pienso que el extern es bueno usarlo para no tener que modificar archivos a cada rato. Y espero explicarme con un codigo ejemplo.
Supongamos una liberia LCD 2x16. Si la libreria esta bien realizada tendriamos un port.c el cual contendria unas funciones tales como:
inline void LCDCsOn(void){
PORTBbits.RB0=1;
};
inline void LCDRsOn(void){
PORTBbits.RB1=1;
};
inline void LCDCsOff(void){
PORTBbits.RB0=0;
};
De esta forma tendriamos que, si por alguna razon, quisieramos cambiar el microcontrolador y tal ves usar la API de un integrado de TI ( es lo unico que tengo a mano ) cambiariamos estas funciones
inline void LCDCsOff(void){
GPIOPinWrite(TFTPORT_CONT, CS , 0x00);
};
Perfecto.. Nuestro modulo de LCD es independiente de nuestro microcontrolador. Solo debemos cambiar el port. Pero es fastidioso estar modificando el port.c a cada rato. Entonces creo un archivo para cada micro, portPIC.c y portTI.c . En el caso de incluirlo en el proyecto solo lo hago con el port que necesito.
Genial ahora tendria que crear un header con todas las definiciones e incluirlo en mi moduloLCD.c o
¿ mejor no ?Si se ponen a pensar ese header tiene definiciones que solo le importarian al portTI o portPIC ( tal cual ven ahi que tengo TFTPORT_CONT y CS ), lo cual a mi moduloLCD.c no deberia importarle nada de eso. Tampoco podria crear un portPIC.h y portTI.h para incluirlo en mi moduloLCD.c, sino deberia estar modificandolo a cada rato si cambio entre un proyecto de PIC a uno de TI y viceversa, y por ser un modulo deberia servir para todos.
Existen 2 soluciones que se me ocurren ahora mismo:
- La primera seria crear un header con solo los prototipos ( supongamos LCDport.h ), entonces los portxxx.c deberian incorporar ambos headers ( el comun para el moduloLCD.c y el propio del port ).
- La segunda es la mejor, el portxx.c posee su propio .h ( con las definiciones que solo le importan a portxx.c ) , y luego en moduloLCD.c podria definirlo asi:
extern void LCDCsOn(void);
extern void LCDCsOff(void);
extern void LCDRsOn(void);
Y listo, ya no necesito usar algun include portxx.h, no me interesa y hace mas facil incluso la programacion de portxx.c. El archivo port sea cual sea que va a ser incorporado al proyecto deberia tener esas funciones, si no las tiene saltaria un error. Y ya con eso me libro de estar modificando moduloLCD.c, y solo deberia incluir un port o el otro segun este trabajando con PIC o el de TI. Todo lo demas se encargaria el linker.
Como resumen, el extern en una funcion lo usaria para no tener que modificando los archivos por unos pequeños cambios.
Otro ejemplo en el que vi usado extern en las funciones es el de cuando tenemos los vectores de interrupcion ( PIC16/18 no tiene

). Supongamos esto:
(obviamente esta incompleto el codigo, pero es para que se entienda)
void (*Vectores[6])(void) = { // Vectores es un array de 6 punteros a funciones
Interrupt_Default, // UART
Interrupt_Default, // SPI
Interrupt_Default, // I2C
Interrupt_Default, // Timer0
Interrupt_Default, // Timer1
Interrupt_Default} // Timer2
void Interrupt_Default(void){
while(1);
}
Este es nuestro archivo de vectores de interrupcion. Si nosotros quisieramos crear una funcion de interrupcion en otro archivo, podriamos crear un .h e incluirlo en ese archivo de vectores, entonces llega el momento en que uno no posee 1 solo archivo con funciones a agregar en los vectores, y llenamos de includes. Otra solucion es que podriamos simplemente usar extern. Si por ejemplo en interrupciones.c tengo la funcion UART_RXInt_Handler(), entonces me quedaria:
extern UART_RXInt_Handler(void);
void (*Vectores[6])(void) = { // Vectores es un array de 6 punteros a funciones
UART_RXInt_Handler, // UART
Interrupt_Default, // SPI
Interrupt_Default, // I2C
Interrupt_Default, // Timer0
Interrupt_Default, // Timer1
Interrupt_Default} // Timer2
void Interrupt_Default(void){
while(1);
}
Y no deberia preocuparme para nada de donde viene esa funcion, siempre y cuando corresponda el nombre/tipo/argumento.
Eso con respecto a las funciones. Con respecto a las variables ocurre algo parecido. Supongamos que tenemos una variable "global" definido en interrupcion.c y que queremos acceder a la misma desde nuestro main.c, esto podemos hacerlo como probamos antes, es decir creamos un header SOLO para esa definicion de la variable y la compartimos. 1 archivo enteramente dedicado a eso.. Algo bastante loco y que si uno se pone a pensar que puede tener mas de 1 variable global entre distintos archivos, lo cual llevaria a tener muchos .h para solamente este fin. Pero nuevamente podriamos hacer esto:
interrupcion.cmain.cextern volatile int variable;
De esa forma le decimos al compilador que esa variable existe en otro lugar. Y que el linker luego va a unirlas.
Son los unicos casos en el que vi, y pienso que debe usarse el extern.