una que programacion macro lenguaje funcion estructura ejemplo dev define declarar declaracion constantes c include c-preprocessor

programacion - ¿Cuál es una buena referencia que documenta los patrones de uso de X-Macros en C(o posiblemente en C++)?



macros en dev c++ (3)

Una definición básica y ejemplo y algunas referencias para " X-Macros " se dan en esta entrada de wikipedia en el pre-procesador C :

Un X-Macro es un archivo de encabezado (normalmente utiliza una extensión ".def" en lugar de la tradicional ".h") que contiene una lista de llamadas de macro similares (que pueden denominarse "macros de componentes").

¿Cuáles son algunas buenas fuentes de información sobre cómo usar esta poderosa técnica? ¿Existen bibliotecas de código abierto conocidas que usan este método?


Descubrí X-macros hace un par de años cuando comencé a hacer uso de punteros de función en mi código. Soy un programador incorporado y uso máquinas de estado con frecuencia. A menudo escribiría un código como este:

/* declare an enumeration of state codes */ enum{ STATE0, STATE1, STATE2, ... , STATEX, NUM_STATES}; /* declare a table of function pointers */ p_func_t jumptable[NUM_STATES] = {func0, func1, func2, ... , funcX};

El problema era que consideraba que era muy propenso a errores tener que mantener el orden de mi tabla de punteros de función de manera que coincidiera con el orden de mi enumeración de estados.

Un amigo mío me presentó a X-macros y fue como una bombilla encendida en mi cabeza. En serio, ¿dónde has estado toda mi vida x-macros!

Entonces ahora defino la siguiente tabla:

#define STATE_TABLE / ENTRY(STATE0, func0) / ENTRY(STATE1, func1) / ENTRY(STATE2, func2) / ... ENTRY(STATEX, funcX) /

Y puedo usarlo de la siguiente manera:

enum { #define ENTRY(a,b) a, STATE_TABLE #undef ENTRY NUM_STATES };

y

p_func_t jumptable[NUM_STATES] = { #define ENTRY(a,b) b, STATE_TABLE #undef ENTRY };

como extra, también puedo hacer que el pre-procesador construya mis prototipos de funciones de la siguiente manera:

#define ENTRY(a,b) static void b(void); STATE_TABLE #undef ENTRY

Otro uso es declarar e inicializar registros

#define IO_ADDRESS_OFFSET (0x8000) #define REGISTER_TABLE/ ENTRY(reg0, IO_ADDRESS_OFFSET + 0, 0x11)/ ENTRY(reg1, IO_ADDRESS_OFFSET + 1, 0x55)/ ENTRY(reg2, IO_ADDRESS_OFFSET + 2, 0x1b)/ ... ENTRY(regX, IO_ADDRESS_OFFSET + X, 0x33)/ /* declare the registers (where _at_ is a compiler specific directive) */ #define ENTRY(a, b, c) volatile uint8_t a _at_ b: REGISTER_TABLE #undef ENTRY /* initialize registers */ #def ENTRY(a, b, c) a = c; REGISTER_TABLE #undef ENTRY

Mi uso favorito sin embargo es cuando se trata de controladores de comunicación

Primero creo una tabla de comunicaciones, que contiene cada nombre y código de comando:

#define COMMAND_TABLE / ENTRY(RESERVED, reserved, 0x00) / ENTRY(COMMAND1, command1, 0x01) / ENTRY(COMMAND2, command2, 0x02) / ... ENTRY(COMMANDX, commandX, 0x0X) /

Tengo los nombres en mayúscula y minúscula en la tabla, porque la mayúscula se utilizará para las enumeraciones y la minúscula para los nombres de las funciones.

Luego también defino structs para cada comando para definir cómo se ve cada comando:

typedef struct {...}command1_cmd_t; typedef struct {...}command2_cmd_t; etc.

Asimismo, defino las estructuras para cada respuesta de comando:

typedef struct {...}response1_resp_t; typedef struct {...}response2_resp_t; etc.

Entonces puedo definir mi enumeración del código de comando:

enum { #define ENTRY(a,b,c) a##_CMD = c, COMMAND_TABLE #undef ENTRY };

Puedo definir mi enumeración de longitud de comando:

enum { #define ENTRY(a,b,c) a##_CMD_LENGTH = sizeof(b##_cmd_t); COMMAND_TABLE #undef ENTRY };

Puedo definir mi enumeración de longitud de respuesta:

enum { #define ENTRY(a,b,c) a##_RESP_LENGTH = sizeof(b##_resp_t); COMMAND_TABLE #undef ENTRY };

Puedo determinar cuántos comandos hay de la siguiente manera:

typedef struct { #define ENTRY(a,b,c) uint8_t b; COMMAND_TABLE #undef ENTRY } offset_struct_t; #define NUMBER_OF_COMMANDS sizeof(offset_struct_t)

NOTA: en realidad nunca instancia el offset_struct_t, solo lo uso como una forma para que el compilador genere mi número de comandos.

Tenga en cuenta que puedo generar mi tabla de punteros de función de la siguiente manera:

p_func_t jump_table[NUMBER_OF_COMMANDS] = { #define ENTRY(a,b,c) process_##b, COMMAND_TABLE #undef ENTRY }

Y mis prototipos de funciones:

#define ENTRY(a,b,c) void process_##b(void); COMMAND_TABLE #undef ENTRY

Ahora, por último, para el mejor uso de la historia, puedo hacer que el compilador calcule qué tan grande debe ser mi buffer de transmisión.

/* reminder the sizeof a union is the size of its largest member */ typedef union { #define ENTRY(a,b,c) uint8_t b##_buf[sizeof(b##_cmd_t)]; COMMAND_TABLE #undef ENTRY }tx_buf_t

De nuevo, esta unión es como mi estructura offset, no está instanciada, en su lugar puedo usar el operador sizeof para declarar mi tamaño de buffer de transmisión.

uint8_t tx_buf[sizeof(tx_buf_t)];

Ahora mi buffer de transmisión tx_buf es el tamaño óptimo y cuando agregue comandos a este controlador de comunicaciones, mi buffer siempre tendrá el tamaño óptimo. ¡Guay!



Uso X Macros () en el código mucho. El valor proviene de solo agregar datos nuevos solo a la "Lista X" y no modificar ningún otro código.

El uso más común de X Macros () es para asociar texto de error con códigos de error. Cuando se agregan nuevos códigos de error, los programadores deben recordar agregar el código y el texto, generalmente en lugares separados. El X Macro permite que los nuevos datos de error se agreguen en un solo lugar y se llenan automáticamente donde sea que se necesite.

Desafortunadamente, los mecanismos usan una gran cantidad de magia previa al compilador que puede hacer que el código sea algo difícil de leer (por ejemplo, unir cadenas con token1##token2 , crear cadenas con #token ). Debido a esto, normalmente explico qué está haciendo X Macro en los comentarios.

Aquí hay un ejemplo que usa los valores de error / retorno. Todos los datos nuevos se agregan a la lista " X_ERROR ". Ninguno de los otros códigos debe ser modificado.

/* * X Macro() data list * Format: Enum, Value, Text */ #define X_ERROR / X(ERROR_NONE, 1, "Success") / X(ERROR_SYNTAX, 5, "Invalid syntax") / X(ERROR_RANGE, 8, "Out of range") /* * Build an array of error return values * e.g. {0,5,8} */ static int ErrorVal[] = { #define X(Enum,Val,Text) Val, X_ERROR #undef X }; /* * Build an array of error enum names * e.g. {"ERROR_NONE","ERROR_SYNTAX","ERROR_RANGE"} */ static char * ErrorEnum[] = { #define X(Enum,Val,Text) #Enum, X_ERROR #undef X }; /* * Build an array of error strings * e.g. {"Success","Invalid syntax","Out of range"} */ static char * ErrorText[] = { #define X(Enum,Val,Text) Text, X_ERROR #undef X }; /* * Create an enumerated list of error indexes * e.g. 0,1,2 */ enum { #define X(Enum,Val,Text) IDX_##Enum, X_ERROR #undef X IDX_MAX /* Array size */ }; void showErrorInfo(void) { int i; /* * Access the values */ for (i=0; i<IDX_MAX; i++) printf(" %s == %d [%s]/n", ErrorEnum[i], ErrorVal[i], ErrorText[i]); }

También puede usar X Macros () para generar código. Por ejemplo, para probar si un valor de error es "conocido", la macro X puede generar casos en una instrucción switch:

/* * Test validity of an error value * case ERROR_SUCCESS: * case ERROR_SYNTAX: * case ERROR_RANGE: */ switch(value) { #define X(Enum,Val,Text) case Val: X_ERROR #undef X printf("Error %d is ok/n",value); break; default: printf("Invalid error: %d/n",value); break; }