Los enfoques modulares son bastante útiles en general (portátiles y limpios), por lo que trato de programar los módulos de la forma más independiente posible. La mayoría de mis enfoques se basan en una estructura que describe el módulo en sí. Una función de inicialización establece los parámetros primarios, luego se pasa un controlador (puntero a la estructura descriptiva) a cualquier función dentro del módulo que se llame.
En este momento, me pregunto cuál puede ser el mejor enfoque de la memoria de asignación para la estructura que describe un módulo. Si es posible, me gustaría lo siguiente:
- Estructura opaca, por lo que la estructura solo puede alterarse mediante el uso de funciones de interfaz proporcionadas
- Múltiples instancias
- memoria asignada por el enlazador
Veo las siguientes posibilidades, que todos entran en conflicto con uno de mis objetivos:
declaración global
múltiples instancias, todas citadas por el enlazador, pero struct no es opaco
(#includes)
module_struct module;
void main(){
module_init(&module);
}
malloc
estructura opaca, múltiples instancias, pero allcotion en el montón
en module.h:
typedef module_struct Module;
en la función init module.c, malloc y el puntero de retorno a la memoria asignada
module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;
en main.c
(#includes)
Module *module;
void main(){
module = module_init();
}
declaración en módulo
estructura opaca, asignada por el vinculador, solo un número predefinido de instancias
mantenga toda la estructura y la memoria internas del módulo y nunca exponga un controlador o estructura.
(#includes)
void main(){
module_init(_no_param_or_index_if_multiple_instances_possible_);
}
¿Existe una opción para combinar estos de alguna manera para estructura opaca, enlazador en lugar de asignación de montón y múltiples / cualquier número de instancias?
solución
Como se propone en algunas respuestas a continuación, creo que la mejor manera es:
- reservar espacio para módulos MODULE_MAX_INSTANCE_COUNT en el archivo fuente de módulos
- no defina MODULE_MAX_INSTANCE_COUNT en el módulo en sí
- agregue un #ifndef MODULE_MAX_INSTANCE_COUNT #error al archivo de encabezado de los módulos para asegurarse de que el usuario de los módulos esté al tanto de esta limitación y defina el número máximo de instancias deseadas para la aplicación
- en la inicialización de una instancia, devuelva la dirección de memoria (* void) de la estructura descriptiva o el índice de módulos (lo que quiera más)
Respuestas:
Claro que lo hay. Primero, sin embargo, reconozca que "cualquier número" de instancias debe ser fijo, o al menos un límite superior establecido, en tiempo de compilación. Este es un requisito previo para que las instancias se asignen estáticamente (lo que se llama "asignación de vinculador"). Puede hacer que el número sea ajustable sin modificación de fuente al declarar una macro que lo especifique.
Luego, el archivo fuente que contiene la declaración de estructura real y todas sus funciones asociadas también declara una matriz de instancias con enlace interno. Proporciona una matriz, con enlace externo, de punteros a las instancias o una función para acceder a los diversos punteros por índice. La variación de la función es un poco más modular:
módulo.c
Supongo que ya está familiarizado con cómo el encabezado declararía la estructura como un tipo incompleto y declararía todas las funciones (escritas en términos de punteros a ese tipo). Por ejemplo:
módulo.h
Ahora
struct module
es opaco en unidades de traducción distintas a *module.c
, y puede acceder y utilizar hasta el número de instancias definidas en tiempo de compilación sin ninguna asignación dinámica.* A menos que copie su definición, por supuesto. El punto es que
module.h
no hace eso.fuente
Programa pequeños microcontroladores en C ++, que logra exactamente lo que quieres.
Lo que llama un módulo es una clase C ++, puede contener datos (accesibles externamente o no) y funciones (del mismo modo). El constructor (una función dedicada) lo inicializa. El constructor puede tomar parámetros de tiempo de ejecución o (mi favorito) parámetros de tiempo de compilación (plantilla). Las funciones dentro de la clase obtienen implícitamente la variable de clase como primer parámetro. (O, a menudo, mi preferencia, la clase puede actuar como un singleton oculto, por lo que se accede a todos los datos sin esta sobrecarga).
El objeto de clase puede ser global (para que sepa en el momento del enlace que todo encajará), o local de pila, presumiblemente en el principal. (No me gustan los globales de C ++ debido al orden de inicialización global indefinido, por lo que prefiero stack-local).
Mi estilo de programación preferido es que los módulos son clases estáticas, y su configuración (estática) es por parámetros de plantilla. Esto evita casi todo exceso y permite la optimización. Combina esto con una herramienta que calcula el tamaño de la pila y puedes dormir sin preocupaciones :)
Mi charla sobre esta forma de codificación en C ++: ¿Objetos? ¡No, gracias!
A muchos programadores integrados / microcontroladores no les gusta C ++ porque piensan que los obligaría a usar todo C ++. Eso no es absolutamente necesario, y sería una muy mala idea. (¡Probablemente tampoco use todo C! Piense en montón, coma flotante, setjmp / longjmp, printf, ...)
En un comentario, Adam Haun menciona RAII e inicialización. IMO RAII tiene más que ver con la deconstrucción, pero su punto es válido: los objetos globales se construirán antes de que comience su inicio principal, por lo que podrían funcionar en suposiciones no válidas (como una velocidad de reloj principal que se cambiará más adelante). Esa es una razón más para NO usar objetos globalizados con código inicializado. (Uso un script de enlazador que fallará cuando tenga objetos globalizados con código inicializado). OMI, tales 'objetos' deberían crearse y pasarse explícitamente. Esto incluye un 'objeto' de facilidad de 'espera' que proporciona una función wait (). En mi configuración, este es un 'objeto' que establece la velocidad de reloj del chip.
Hablando de RAII: esa es una característica más de C ++ que es muy útil en sistemas integrados pequeños, aunque no por la razón (desasignación de memoria) que más se usa en sistemas grandes (los sistemas integrados pequeños en su mayoría no usan desasignación de memoria dinámica). Piense en bloquear un recurso: puede hacer que el recurso bloqueado sea un objeto contenedor y restringir el acceso al recurso para que solo sea posible a través del contenedor de bloqueo. Cuando el contenedor queda fuera de alcance, el recurso se desbloquea. Esto impide el acceso sin bloqueo y hace que sea mucho más improbable que olvide el desbloqueo. con algo de magia (plantilla) puede ser cero sobrecarga.
La pregunta original no mencionaba C, de ahí mi respuesta centrada en C ++. Si realmente debe ser C ...
Puede usar el truco de macros: declare sus objetos públicamente, de modo que tengan un tipo y se puedan asignar globalmente, pero destruya los nombres de sus componentes más allá de la usabilidad, a menos que alguna macro se defina de manera diferente, como es el caso en el archivo .c de su módulo. Para mayor seguridad, puede utilizar el tiempo de compilación en la destrucción.
O tenga una versión pública de su estructura que no tenga nada útil, y tenga la versión privada (con datos útiles) solo en su archivo .c, y afirme que son del mismo tamaño. Un poco de truco de creación de archivos podría automatizar esto.
@Lundins comenta sobre programadores malos (incrustados):
El tipo de programador que describas probablemente haría un desastre en cualquier idioma. Las macros (presentes en C y C ++) son una forma obvia.
Las herramientas pueden ayudar hasta cierto punto. Para mis alumnos, solicito una secuencia de comandos integrada que especifique no-excepciones, no-rtti, y da un error de enlace cuando se usa el montón o están presentes los valores globales inicializados por código. Y especifica advertencia = error y habilita casi todas las advertencias.
Recomiendo el uso de plantillas, pero con constexpr y conceptos, la metaprogramación es cada vez menos necesaria.
"programadores confundidos de Arduino" Me gustaría mucho reemplazar el estilo de programación Arduino (cableado, replicación de código en bibliotecas) con un enfoque moderno de C ++, que puede ser más fácil, más seguro y producir código más rápido y más pequeño. Si tan solo tuviera el tiempo y el poder ...
fuente
Creo que FreeRTOS (¿tal vez otro sistema operativo?) Hace algo como lo que estás buscando al definir 2 versiones diferentes de la estructura.
El 'real', utilizado internamente por las funciones del sistema operativo, y uno 'falso' que tiene el mismo tamaño que el 'real', pero que no tiene miembros útiles en su interior (solo un montón de
int dummy1
y similares).Solo la estructura 'falsa' está expuesta fuera del código del sistema operativo, y esto se usa para asignar memoria a instancias estáticas de la estructura.
Internamente, cuando se llaman funciones en el sistema operativo, se les pasa la dirección de la estructura externa 'falsa' como un identificador, y esto se convierte en un puntero a una estructura 'real' para que las funciones del sistema operativo puedan hacer lo que necesitan hacer.
fuente
En mi opinión, esto no tiene sentido. Puede poner un comentario allí, pero no tiene sentido tratar de ocultarlo más.
C nunca proporcionará un aislamiento tan alto, incluso si no hay una declaración para la estructura, será fácil sobrescribirla accidentalmente con, por ejemplo, memcpy () o desbordamiento de búfer.
En cambio, solo dale un nombre a la estructura y confía en otras personas para que también escriban un buen código. También facilitará la depuración cuando la estructura tenga un nombre que pueda usar para referirse a ella.
fuente
Las preguntas de SW puro se hacen mejor en /programming/ .
El concepto de exponer una estructura de tipo incompleto a la persona que llama, como usted describe, a menudo se llama "tipo opaco" o "punteros opacos": la estructura anónima significa algo completamente diferente.
El problema con esto es que la persona que llama no podrá asignar instancias del objeto, solo punteros a él. En una PC, usarías
malloc
dentro de los objetos "constructor", pero malloc es un no-go en sistemas embebidos.Entonces, lo que haces en incrustado es proporcionar un grupo de memoria. Tiene una cantidad limitada de RAM, por lo que restringir la cantidad de objetos que se pueden crear generalmente no es un problema.
Consulte Asignación estática de tipos de datos opacos en SO.
fuente