- Nunca asumas que el tamaño de una estructura en C es igual a la suma de sus componentes.Puede ser un error o tal ves no. Una de las ventajas de C es no tener que preocuparse por como estan guardados los datos en memoria, ni como es que son accedidos a la misma. A pesar que C garantiza que los datos van a estar seguidos, no significa que en algun momento ingrese un "padding" en la misma. O tampoco podemos saber si es que justo cambio de banco ( en el caso de los PIC16 mas viejitos ). Entonces ocurren cosas que parecen ilogicas.
Hace poco (desde escrito esto) ocurrieron 2 casos que involucraban estructuras. El primero fue la falta de optimizacion, y la segunda por que la estructura ocupaba mas espacio.
En este caso presento una version resumida de la estructura, por supuesto hay mas componentes
typedef struct {
uint8 start;
uint8 end;
} buffer_circular
Ocurria que al hacer la siguiente linea:
dato = buffer->data[buffer->start++];
C calculaba el valor como la Base + offset, lo cual generaba mas instrucciones que la que el usuario queria. Entonces llego a la conclusion de crear un MACRO ( ya vamos a ver esto ) que haga lo siguiente:
(*((uint8*)dato)) = *(struct_pointer + DATA_OFFSET + (2* *(struct_pointer+START_OFFSET)));
(*((uint8*)dato + 1)) = *(struct_pointer + DATA_OFFSET + (2* *(struct_pointer+START_OFFSET) + 1));
Lo interesante del macro era que creaba un codigo exactamente para esa situacion. Lo cual funciona, pero ahora pensemos un poco, esto es un codigo de una libreria para un buffer circular que por ser en C puedo aplicarlo en cualquier microcontrolador, GENIAL!
Pero este codigo puede fallar por multiples razones. Pero antes presento el segundo caso y tendremos la "moraleja" servida.
El segundo caso ocurrio algo similar. Un usuario que poseia una estructura y que le funcionaba en los micros de 8 bits, pero se fue a un micro de 16 bits y comenzaron los problemas. Su estructura estaba ocupando 2 bytes mas.
struct Struct1{
int8 var1;
int8 var2;
int16 Var3;
struct Struct2 mStr;
struct Struct2 mStr2;
int16 var4;
};
struct Struct2{
int8 var10;
int8 var11;
int8 var12;
};
Entonces que es lo que cambio ? Lo unico que cambio es los bits de sistema. Y C que decidio hacer la estructura de esta forma en memoria:
+-------+-------+
| var1 | var2 |
+-------+-------+
| var3 |
+-------+-------+
| var10 | var11 | mStr
+-------+-------+
| var12 | |
+-------+-------+
| var10 | var11 | mStr2
+-------+-------+
| var12 | |
+-------+-------+
| nrec |
+-------+-------+Si ahora volvemos al primer caso, esto hubiera sido un desastre, por que la persona asumio que todos los bytes iban a estar seguidos. Que como dije para los 8 bits puede (no se a ciencia cierta) no llegar a ser problema, pero cuando se tiene un codigo que es una libreria se desea que sirva para todos.
- Usar funciones "inline" en ves de macros, o en caso de usar macros usar parentesis!.Hay varias razones para hacer esto. Normalmente cuando uno posee una funcion muy pequeña y que se usa varias veces es preferible hacerlo asi para incrementar la velocidad del sistema. El por que no Macros y si la funcion es por que muchas veces los Macros si no se escriben correctamente pueden terminar ocasionando problemas.
Muchas veces creamos funciones en las Macros. Veamos una muy simple:
#define Cubo(X) (X * X * X)
Bien.. es nuestra funcion de un numero al cubo. Pero esta se comportara como realmente una funcion?
Si tenemos la siguiente linea:
Y uno pensaria que este codigo daria 81/27 = 3 , donde 27 es el cubo de 3. Pero esto es un error. Esto se traduce a :
a= 81 / (2+1 * 2+1 * 2+1);
Por la precedencia de la multiplicacion sobre la suma, obtenemos 81 / 7 = 11
No es lo que pretendiamos. La solucion a esto es:
#define Cubo(X) ((X) * (X) * (X))
De misma forma podriamos pensar que al tener los parentesis dentro podriamos quitar los de afuera. Y es otro error
#define Cubo(X) (X) * (X) * (X)
a = 81 / Cubo(2+1);
Uno esperaria nuevamente que esto funcion, ahora ya sabemos que 2+1 se van a sumar antes, pero NO. No funciona por que termina evaluandose asi:
((81 / 3) * 3 * 3)Al tener el mismo orden de precedencia se divide y luego se multiplica, quedando en 243, Por eso mismo uno debe poner muchos parentesis ahi. Pero esto es la solucion ? NO.
Supongamos ahora este caso:
#define Cubo(X) ((X) * (X) * (X))
i = 2;
a= 81 / Cubo(++i);
Lo que uno normalmente en las funciones conoce es que primero se va a aumentar i y luego se pasa ese valor. Bueno en los Macros eso no ocurre. y terminarian con algo asi:
Lo cual es totalmente indefinido el resultado que va a salir de ahi. tal ves no es lo mas simple de ver. Pero ocurre algo como esto:
b[2]={5,8};
i=0;
a = i + b[++i];
Uno podria penasr que si.. primero aumentaria i, y luego haria la suma, PERO tal ves el compilador comenzo a sumar y todavia no evaluo el array, entonces i no se incremento. Y de cosas asi no se pueden depender. Ya que no se sabe a ciencia cierta el resultado, ¿5 o 9?.
Otra de macros... Es posile pasarle cualquier cosa. Sin que el compilador nos diga que existe un error, por que son resueltas por el pre-procesador (antes que el compilador).
#define Cubo(X) ((X) * (X) * (X))
char i = 'a';
a= 81 / Cubo(i);
Para todo lo demas existen las funciones inline
inline int Cubo(int x){
return (x * x * x)
}
Y con esto resolvimos nuestros problemas. Otro caso es de las macros con vaaaaarias instrucciones, es mejor crear directamente una funcion de estas. Esto no quiere decir que los macros sean malas, sino que uno debe saber elegir cual usar.
Ademas las funciones inline van a hacer que el compilador las introduzca en el codigo sin la sobrecarga de pasar las variables al stack como una funcion normal haria. Y lo que se busca desde una macro.
- Mas sobre MACROSAunque esta recomendacion, puede parecer absurda. En el sentido de que se de un error de esta naturaleza, pero lo eh visto por ahi.
Supongamos este codigo:
#define FIN_DE_DATOS -1
...
if ( getchUSART() FIN_DE_DATOS)
{
//xxxx..
}
Si por alguna razon alguien se olvidara de poner el condicional ( == , != , etc ) tal ves un error que no suele ocurrir normalmente, el compilador no levantaria ningun error ya que la expresion se evaluaria como:
getchUSART() - 1Es un error y queremos que el compilador nos avise de esto, entonces tendriamos 2 soluciones:
#define FIN_DE_DATOS (-1)
enum { FIN_DE_DATOS = -1 };
Aunque nuevametne el caso del #define puede no ser el correcto. Hay funciones realizadas con macro de la forma:
*Codigo sacado de internet:
#define GET_MACRO(_1,_2,_3,NAME,...) NAME
#define FOO(...) GET_MACRO(__VA_ARGS__, FOO3, FOO2)(__VA_ARGS__)
En el cual se podria confundir el getchUSART() como una de estas al estar juntas y no poseer una separacion:
Un poco cuestion de gustos y tener en cuenta esto.
- Poner siempre las llaves por mas que sea una linea , y poner las condiciones en los if.Algo que yo hago muy a menudo ( soy sincero ) es lo siguiente ( el codigo para ambos casos):
char a[10];
for ( i=0 ; i<10 ; i++ )
a[i] = i;
//....
if (!BusyUSART())
{
// .....
}
Es por ahi algo comun de ver. Como se ve esta identado, pero aun asi si en el afan o apresuro uno quisiera agregar una linea en el for podria llegar a cometer un error, Es una sola linea!. Pero aun asi seria una buena costumbre tener las llaves.
Por otra parte tenemos el if, en el cual para alguien que conoce el lenguaje sabra cuando es que va a entrar al if, pero si apuntamos a la legibilidad del codigo para que incluso alguien ajeno sepa rapidamente que estamos haciendo,entonces deberia ser asi:
char a[10];
for ( i=0 ; i<10 ; i++ )
{
a[i] = i;
}
//....
if (BusyUSART() == 0)
{
// .....
}
Nuevamente por ahi con el afan de ganar un poco de tiempo hacemos las cosas mas rapido para ahorranos esos 3 caracteres de mas.
- Documentacion / Comentarios.Esta creo que es la mas importante. Cuando es un codigo pequeño y siguiendo las consejos para que sea legible el codigo no necesita demasiados comentarios. Pero a medida que el codigo aumenta. Se vuelve imposible recordar cada una de las cosas que hicimos. Y tampoco es demasiado problema si es que el codigo lo toca una sola persona. Pero cuando ya varias estan involucradas y no conocen por dentro lo que hay en esas funciones se vuelve totalmente necesario.
De esta forma podemos ubicar comentarios en distintos lugares:
Al comienzo del archivo, indicando el archivo, al modulo que pertenece, fecha de creacion, explicacion, copyrigth y muchos etc mas.
Antes de cada funcion, explicando la funcion en si, cada parametro que es (entrada y retorno). Un ejemplo de uso de la funcion si es que se quiere. Que es lo que va a servir de apoyo a la persona que necesite usar la funcion sin entrar en detalles sobre esta (Como referencia a otras funciones). Tomando de codigo de ejemplo:
//*****************************************************************************
//
//! Registra un Handler para la interrupcion del ADC.
//!
//! \param ui32Base Es la base del modulo ADC.
//! \param ui32SequenceNum Es el numero de secuencia de muestreo.
//! \param pfnHandler Es un puntero a la funcion a ser llamada cuando ocurra la interrupcion
//!
//! This function sets the handler to be called when a sample sequence
//! interrupt occurs. This function enables the global interrupt in the
//! interrupt controller; the sequence interrupt must be enabled with
//! ADCIntEnable(). It is the interrupt handler's responsibility to clear the
//! interrupt source via ADCIntClear().
//!
//! \sa IntRegister() for important information about registering interrupt
//! handlers.
//!
//! \return None.
//
//*****************************************************************************
void
ADCIntRegister(uint32_t ui32Base, uint32_t ui32SequenceNum,void (*pfnHandler)(void))
Esto es ayuda tanto para la persona que lo va a usar, como la persona que esta trabajando con este codigo. Aun asi tambien deberiamos comentar dentro del codigo. Creo que la mas sana de las constumbres es comentar no es la misma linea, sino en bloques.
//
// Checkeo los argumentos que cumplan los requisitos
//
xxxxxxxxx;
//
// Determino xxxx cosa.
//
xxxxxxx;
//
// Registro la interrupcion
//
IntRegister(x, Y);
//
// Habilito la interrupcion.
//
IntEnable(x);
Aunque hay casos por ejemplo en la inicializacion de variables. Es complejo ver esto:
int i=0,j=0,NumeroTarde=30,PleotaDesinflada=50;
Para los casos de variables inicializadas como estas, es mejor que cada una tenga su propia linea y su comentario
int i=0; /**< Variable auxiliar para el uso en loops */
int j=0; /**< Variable auxiliar para el uso en loops */
int NumeroTarde=30; /**< Variable que mantiene el numero de tardes que pasaron */
int PelotaDesinflada=50; /**< Variable para saber cuantas pelotas desinfladas hubo hasta el momento */
Esto es bueno para el caso de variables inicializadas , cuando no lo son asi. En el momento que lo usemos en el codigo ahi lo comentamos. Ej:
int i,j,NumeroTarde,PleotaDesinflada;
void xxxx(void){
i=0; /**< Variable auxiliar para el uso en loops */
j=0; /**< Variable auxiliar para el uso en loops */
NumeroTarde=30; /**< Variable que mantiene el numero de tardes que pasaron */
PelotaDesinflada=50; /**< Variable para saber cuantas pelotas desinfladas hubo hasta el momento */
..... Mas codigo ....
}
- Headers Guard, Para los archivos .hCuando creamos un .h por ahi solemos incorporarlo en varios lados. Esto puede traer problemas ya que estariamos definiendo en varios lados lo mismo. Es por eso que se suele usar una guarda para evitar que esto suceda.
#ifndef HEADER_H
#define HEADER_H
/* ... Contenido de <header.h> ... */
#endif /* HEADER_H */
Una cosa a observar es el nombre que posee esta guarda. casi siempre usamos el mismo nombre que el archivo. Pero algunos hace uso de este nombre _HEADER_H ( con un guion bajo adelante ) eso SI que es una mala implementacion, por que C se reserva el uso de todos los macros/definiciones/etc con un guion bajo adelante ( en realidad es mas detallado referirse a C Standard, 7.1.3 )
El principal problema seria que nosotros incluimos una libreria de C y esta posee tal ves una cabecera con el mismo nombre que nuestro archivo por culpa de ese guien bajo. resultando que uno de los dos no se incluya y tengamos problemas. Por eso mismo no deberiamos crear estas definiciones/variables como reservadas ( con el guion bajo ).
- No acceder a una variable por un puntero que no es de su tipoY aca me incluyo nuevamente. Y recuerdo bien el tema que se dio aca en el foro al enviar un valor de un float ( 32 bits ) por UART. Entonces se podia hacer de 2 formas.
Creamos un puntero a float pero con el tipo char de esa forma enviabamos los 4 valores. por UART y luego lo juntabamos de la misma manera.. Hace poco encontre lo que en GCC le llaman strict-aliasing rule (por lo que tengo entendido es por la optimizacion) y muestro un ejemplo
void funcion(void) {
short a[2];
a[0]=0x11;
a[1]=0x11;
*(int *)a = 0x2222; // Creo un puntero de tamaño int ( 16 bits ) con la direccion de a. Y accedo a su contenido
printf("%x %x\n", a
[0], a
[1]); }
Dependiendo de la optimizacion de GCC o de la arquitectura es posible que ese codigo no funcione como nosotros quisieramos. Segun si estaba activo ese strict-alising o no es posible obtener del printf 11 11 o 22 22 en la salida. Como esto depende del compilador/arquitectura/etc no se recomienda, tal ves era una verson vieja pero encontre que puede ocurrir, y lo correcto es usar una union:
void func(void) {
union {
short a[2];
int i;
} u;
u.a[0]=0x11;
u.a[1]=0x11;
u.i = 0x2222;
printf("%x %x\n", u.
a[0], u.
a[1]); }
El cual ahora si imprimiria el valor correcto. Sin posibilidad de problemas.
EDIT para expandir mas sobre esto:
Supuestamente existe una regla llamada "strict-aliasing rule" en el cual se "asume" que 2 punteros de distinto tipo no deben apuntar al mismo espacio de memoria. De esta forma el compilador (con optimizacion) puede llegar a pensar en esto. Y acomodar las instrucciones que le dimos de otra forma. Como en el ejemplo anterior tal ves ejecutar el printf primero antes de asignar el valor al otro puntero, ya que tenia esos valores (los del char) en los registros de trabajo, y no tenia sentido sacarlos y volverlos a pasar por ahi. Y esto es lo que causa este comportamiento. Esa opcion creo que actualmente viene activada y no deberia ocurrir, pero es bueno tenerlo en cuenta.