Posibilidades de asignar memoria para el diseño de firmware modular en C

16

los enfoques modulares son bastante útiles en general (portátiles y limpios), así que trato de programar módulos lo más independientes posible de cualquier otro módulo. 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 estructura descriptiva) a cualquier función dentro del módulo que se llame.

Ahora mismo, 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 modificarse mediante el uso de las funciones de interfaz proporcionadas
  • instancias múltiples
  • memoria asignada por el enlazador

Veo las siguientes posibilidades, todas en conflicto con uno de mis objetivos:

declaración global

múltiples instancias, asignadas por el enlazador, pero la estructura no es opaca

(#includes)
module_struct module;

void main(){
   module_init(&module);
}

malloc

estructura opaca, varias instancias, pero allcotion on heap

en module.h:

typedef module_struct Module;

en module.c function init, malloc y 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 el módulo

estructura opaca, asignada por el enlazador, solo un número predefinido de instancias

mantenga toda la estructura y la memoria interna 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 combinarlos de alguna manera para la estructura opaca, el vinculador en lugar de la asignación de almacenamiento dinámico y múltiples / cualquier número de instancias?

resolución

como se propone en algunas respuestas a continuación, creo que la mejor manera es:

  1. reserva espacio para los módulos MODULE_MAX_INSTANCE_COUNT en el archivo fuente de los módulos
  2. no define MODULE_MAX_INSTANCE_COUNT en el módulo mismo
  3. 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 que se desean para la aplicación
  4. al inicializar una instancia, devuelva la dirección de memoria (* void) de la estructura de destino o el índice de módulos (lo que más le guste)
pregunta L. Heinrichs

5 respuestas

4
  

¿Existe una opción para combinar estos de alguna manera para la estructura anónima,   vinculador en lugar de la asignación de montón y múltiples / cualquier número de   instancias?

Claro que hay. Primero, sin embargo, reconozca que el "cualquier número" de instancias debe ser fijo, o al menos un límite superior establecido, en el momento de la compilación. Este es un requisito previo para que las instancias se asignen de forma estática (lo que se denomina "asignación de vinculador"). Puede hacer que el número sea ajustable sin modificar la fuente declarando 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 serie 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

#include <module.h>

// 4 instances by default; can be overridden at compile time
#ifndef NUM_MODULE_INSTANCES
#define NUM_MODULE_INSTANCES 4
#endif

struct module {
    int demo;
};

// has internal linkage, so is not directly visible from other files:
static struct module instances[NUM_MODULE_INSTANCES];

// module functions

struct module *module_init(unsigned index) {
    instances[index].demo = 42;
    return &instances[index];
}

Supongo que ya estás familiarizado con la forma en que 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

#ifndef MODULE_H
#define MODULE_H

struct module;

struct module *module_init(unsigned index);

// other functions ...

#endif

Ahora struct module es opaco en unidades de traducción distintas de module.c , * y puede acceder y usar hasta el número de instancias definidas en el momento de la 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.

    
respondido por el John Bollinger
22

Programo pequeños microcontroladores en C ++, que logran exactamente lo que quieres.

Lo que usted llama un módulo es una clase de C ++, puede contener datos (ya sea accesibles externamente o no) y funciones (igualmente). El constructor (una función dedicada) lo inicializa. El constructor puede tomar parámetros de tiempo de ejecución o (mis favoritos) 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, según 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 la pila, probablemente en la principal. (No me gustan los globales de C ++ debido al orden de inicialización global no definido, 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 todas las sobrecargas y permite la optimización. Combina esto con una herramienta que calcula el tamaño de pila y puedes dormir sin preocupaciones :)

Mi charla sobre esta forma de codificación en C ++: ¿Objetos? ¡No, gracias!

A muchos programadores integrados / microcontroladores parece que no les gusta C ++ porque creen que les obligaría a usar all de C ++. Eso no es absolutamente necesario, y sería una muy mala idea. (¡Probablemente tampoco uses todos los C! Piensa en montón, punto flotante, setjmp / longjmp, printf, ...)

En un comentario, Adam Haun menciona RAII y la 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 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 utilizar objetos globales inicializados con código. (Utilizo un script de vinculador que fallará cuando tenga objetos inicializados con código global). OMI, tales "objetos" deben crearse y distribuirse explícitamente. Esto incluye un 'objeto' 'de espera' 'que proporciona una función de espera (). En mi configuración, este es el "objeto" que establece la velocidad de reloj del chip.

Hablando de RAII: esta 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 se usa más en sistemas más grandes (los sistemas integrados pequeños en su mayoría no usan desasignación de memoria dinámica ). Piense en bloquear un recurso: puede convertir el recurso bloqueado en un objeto contenedor, y restringir el acceso al recurso para que solo sea posible a través del bloqueo contenedor. Cuando el contenedor queda fuera del alcance, el recurso se desbloquea. Esto evita el acceso sin bloqueo, y hace que sea mucho más improbable que se olvide del desbloqueo. con algo de magia (de plantilla) puede tener cero gastos generales.

La pregunta original no mencionó C, de ahí mi respuesta centrada en C ++. Si realmente debe ser C ....

Podrías usar trucos de macros: declarar tus estucos públicamente, por lo que tienen un tipo y pueden asignarse globalmente, pero manipular los nombres de sus componentes más allá de la facilidad de uso, a menos que alguna macro se defina de manera diferente, como es el caso en el módulo. archivo c Para mayor seguridad, puede usar el tiempo de compilación en la mutilació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 make-file 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, encomiendo un script construido que especifica no-exceptions, no-rtti, y da un error de vinculador cuando se utiliza el montón o están presentes los globales con código inicializado. Y especifica warning = error y habilita casi todas las advertencias.

  • Recomiendo el uso de plantillas, pero con constexpr y los conceptos, la metaprogramación es cada vez menos necesaria.

  • "programadores Arduino confundidos" Me gustaría mucho reemplazar el estilo de programación Arduino (cableado, replicación de código en bibliotecas) con un enfoque moderno en C ++, que puede ser más sencillo, más seguro y producir más rápido y más pequeño código. Si solo tuviera el tiempo y el poder ...

respondido por el Wouter van Ooijen
7

Creo que FreeRTOS (¿quizás otro sistema operativo?) hace algo parecido a lo que estás buscando al definir 2 versiones diferentes de la estructura.
El "real", utilizado internamente por las funciones del sistema operativo, y el "falso" que es del mismo tamaño que el "real", pero no tiene miembros útiles dentro (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 'falsa' externa como un identificador, y esto se encuadra como un puntero a una estructura 'real' para que las funciones del sistema operativo puedan hacer lo que necesitan. hacer.

    
respondido por el brhans
2
  

Estructura anónima, por lo que la estructura solo puede modificarse mediante el uso de las funciones de interfaz proporcionadas

En mi opinión, esto no tiene sentido. Puede poner un comentario allí, pero no tiene sentido intentar ocultarlo más.

C nunca proporcionará un aislamiento tan alto, incluso si no hay una declaración para la estructura, será fácil sobreescribirla accidentalmente, por ejemplo. memcpy () o error de desbordamiento de búfer.

En vez de eso, simplemente dale un nombre a la estructura y confía en que otras personas 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.

    
respondido por el jpa
2

Las preguntas de SW puro se hacen mejor en enlace .

El concepto al exponer una estructura de tipo incompleto a la persona que llama, como usted describe, a menudo se llama "tipo opaco" u "punteros opacos": estructura anónima significa algo completamente distinto.

El problema con esto es que la persona que llama no podrá asignar instancias del objeto, solo los punteros a él. En una PC, usaría malloc dentro del "constructor" de objetos, pero malloc es un no-go en sistemas integrados.

Entonces, lo que haces en el modo integrado es proporcionar un grupo de memoria. Tiene una cantidad limitada de RAM, por lo que restringir el número de objetos que se pueden crear no suele ser un problema.

Consulte Asignación estática de tipos de datos opacos en SO.

    
respondido por el Lundin

Lea otras preguntas en las etiquetas