c - attribute - a title
¿Está bien#incluir el archivo de origen.c para mantener el código C incorporado? (5)
Debe usar los inicializadores designados como se muestra en la respuesta de Ian Abbot.
Además, si los índices de matriz son adyacentes como parece ser el caso aquí, puede usar una enumeración en su lugar:
toto.h
typedef enum
{
TOTO_IND,
TITI_IND,
...
TATA_IND,
TOTO_N // this is not a data item but the number of items in the enum
} toto_t;
toto.c
const MyElements elems [] = {
[TITI_IND] = {"TITI", 27, "English"},
[TATA_IND] = {"TATA", 45, "Spanish"},
[TOTO_IND] = {"TOTO", 18, "French"},
};
Y ahora puede verificar la integridad de los datos de la matriz en su totalidad con una aserción estática:
_Static_assert(sizeof elems/sizeof *elems == TOTO_N,
"Mismatch between toto_t and elems is causing rain in Africa");
_Static_assert(sizeof elems/sizeof *elems == TOTO_N, ERR_MSG);
donde ERR_MSG
se define como
#define STR(x) STR2(x)
#define STR2(x) #x
#define ERR_MSG "Mismatching toto_t. Holding on line " STR(__LINE__)
No soy un programador experto en C y sé que incluir el archivo fuente .c
de otro se considera una mala práctica, pero tengo una situación en la que creo que podría ayudar a la capacidad de mantenimiento.
Tengo una gran estructura con muchos elementos y uso #define
para mantener los índices.
#define TOTO_IND 0
#define TITI_IND 1
…
#define TATA_IND 50
static const MyElements elems [] = {
{"TOTO", 18, "French"},
{"TITI", 27, "English"},
...,
{"TATA", 45, "Spanish"}
}
Dado que necesito acceder a la estructura desde el índice, necesito mantener sincronizada la declaración #define
y la estructura. Eso significa que debo insertar nuevos elementos en el lugar correcto y actualizar la #define
consecuencia.
Es propenso a errores y realmente no me gusta (pero por consideraciones de rendimiento, no encontré una mejor solución).
De todos modos, este archivo también contiene muchas funciones para manejar esta estructura. También quiero mantener la separación de código y evitar las variables globales.
Para hacer las cosas "más fáciles", estaba pensando en mover esta "definición propensa a errores" a un único archivo fuente .c
que solo contendría esta estructura. Este archivo sería "el archivo peligroso, tenga cuidado" e incluirlo en mi archivo "funcional normal" real.
¿Qué piensa usted al respecto? ¿Es una situación válida para incluir el archivo fuente .c
? ¿Hay otra forma mejor de manejar mi estructura?
Definir la const
como static
en varios archivos no es una buena idea porque crea múltiples instancias de la variable grande MyElements
. Esto aumentará la memoria en un sistema integrado. El calificador static
necesita ser eliminado.
Aquí hay una solución sugerida:
en archivo.h
#define TOTO_IND 0
#define TITI_IND 1
…
#define TATA_IND 50
#define MAX_ELEMS 51
extern const MyElements elems[MAX_ELEMS];
en archivo.c
#include "file.h"
const MyElements elems [MAX_ELEMS] = {
{"TOTO", 18, "French"},
{"TITI", 27, "English"},
...,
{"TATA", 45, "Spanish"}
}
Después de la modificación, coloque #include "file.h"
en los archivos .c requeridos.
Otras respuestas ya lo han cubierto de una manera más clara, pero para completar, aquí hay un enfoque de macros x, si está dispuesto a seguir este camino y arriesgarse a la rabia de su compañero de trabajo.
Las macros X son una forma de generación de código, utilizando el preprocesador C incorporado. El objetivo es reducir la repetición al mínimo, aunque con algunos inconvenientes:
- El archivo de origen que genera enums y estructuras utilizando el preprocesador puede parecer complejo si no está acostumbrado a ellos.
- En comparación con una secuencia de comandos de compilación externa que generaría los archivos de origen, con las macros x nunca verá cómo se ve el código generado durante la compilación, a menos que use una configuración de compilador y verifique el archivo preprocesado manualmente.
- Como no ve la salida preprocesada, no puede usar el depurador para recorrer el código generado de la misma manera que lo haría con el código generado por un script externo.
Comience por crear una lista de invocaciones de macro en un archivo separado, por ejemplo, elements.inc
, sin definir lo que realmente hace la macro en este punto:
// elements.inc
// each row passes a set of parameters to the macro,
// although at this point we haven''t defined what the
// macro will output
XMACRO(TOTO, 18, French)
XMACRO(TITI, 27, English)
XMACRO(TATA, 45, Spanish)
Y luego define la macro cada vez que necesita incluir esta lista, de modo que cada invocación se represente en una sola fila de la construcción que desea crear, y generalmente la repite varias veces seguidas, es decir,
// concatenate id with "_IND" to create enums, ignore code and description
// (notice how you don''t need to use all parameters each time)
// e.g. XMACRO(TOTO, 18, French) => TOTO_IND,
#define XMACRO(id, code, description) id ## _IND,
typedef enum
{
# include "elements.inc"
ELEMENTS_COUNT
}
Elements;
#undef XMACRO
// create struct entries
// e.g. XMACRO(TOTO, 18, French) => [TOTO_IND] = { "TOTO", 18, "French" },
#define XMACRO(id, code, description) [id ## _IND] = { #id, code, #description },
const MyElements elems[] = {
{
# include "elements.inc"
};
#undef XMACRO
Que se preprocesará en algo como:
typedef enum
{
TOTO_IND,
TITI_IND,
TATA_IND,
ELEMENTS_COUNT
}
Elements;
const MyElements elems[] = {
{
[TOTO_IND] = { "TOTO", 18, "French" },
[TITI_IND] = { "TITI", 27, "English" },
[TATA_IND] = { "TATA", 45, "Spanish" },
};
Obviamente, el mantenimiento frecuente de la lista se vuelve más fácil, a expensas de que el código de generación se vuelva más complicado.
Para abordar la pregunta específica sobre el uso de #include
con un archivo .c
(las otras respuestas sugieren mejores opciones, especialmente la de Groo), generalmente no es necesario.
Todo lo que se encuentra en un archivo .c
puede hacerse visible y accesible desde el exterior, por lo que puede hacer referencia a él a través de prototipos de funciones y #extern
. Por ejemplo, podría hacer referencia a su tabla con #extern const MyElements elems [];
en su archivo principal .c
Alternativamente, puede colocar las definiciones en un archivo .h
e incluirlo. Eso le permite segregar el código como desee. Tenga en cuenta que todo lo que #include
hace es insertar el contenido del archivo incluido donde se encuentra la instrucción #include
, por lo que no tiene que tener ninguna extensión de archivo en particular. .h
es usado por la convención y la mayoría de los IDE agregarán automáticamente archivos .c
a la lista de archivos que se compilarán, pero en lo que respecta al compilador la denominación es arbitraria.
Podría usar los inicializadores designados para inicializar los elementos de elems[]
sin tener que conocer el valor explícito de cada identificador de índice (o macro).
const MyElements elems[] = {
[TOTO_IND] = {"TOTO", 18, "French"},
[TITI_IND] = {"TITI", 27, "English"},
[TATA_IND] = {"TATA", 45, "Spanish"},
};
Los elementos de la matriz se inicializarán de la misma manera, incluso si cambia el orden en que aparecen en el código fuente:
const MyElements elems[] = {
[TITI_IND] = {"TITI", 27, "English"},
[TATA_IND] = {"TATA", 45, "Spanish"},
[TOTO_IND] = {"TOTO", 18, "French"},
};
Si la longitud de la matriz se configura automáticamente desde el inicializador como se [NUM_ELEMS]
anteriormente (es decir, al usar []
lugar de [NUM_ELEMS]
), la longitud será mayor que el índice máximo del elemento.
Esto le permite mantener los valores de índice y la declaración externa de la matriz elems
en un archivo .h, y definir el contenido de la matriz elems
en un archivo .c separado.