Electronic – Possibilities for allocating memory for modular firmware design in C

cdesignfirmware

modular approaches are pretty handy in general (portable and clean), so I try to program modules as independent of any other modules as possible. Most of my approaches are based on a struct that describes the module itself. An initialization function sets the primary parameters, afterwards a handler (pointer to desriptive struct) is passed to whatever function inside the module is called.

Right now, I am wondering what the best approach of allocation memory for the struct describing a module may be. If possible, I'd like the following:

  • Opaque struct, so the struct may only be altered by the use of provided interface functions
  • Multiple instances
  • memory allocated by linker

I see the following possibilities, that all conflict with one of my goals:

global declaration

multiple instances, allcoted by linker, but struct is not opaque

(#includes)
module_struct module;

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

malloc

opaque struct, multiple instances, but allcotion on heap

in module.h:

typedef module_struct Module;

in module.c init function, malloc and return pointer to allocated memory

module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;

in main.c

(#includes)
Module *module;

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

declaration in module

opaque struct, allocated by linker, only a predefined number of instances

keep the whole struct and memory internal to the module and never expose a handler or struct.

(#includes)

void main(){
    module_init(_no_param_or_index_if_multiple_instances_possible_);
}

Is there an option to combine these somehow for opaque struct, linker instead of heap allocation and multiple/any number of instances?

solution

as proposed in some answers below, I think the best way is to:

  1. reserve space for MODULE_MAX_INSTANCE_COUNT modules in the modules source file
  2. do not define MODULE_MAX_INSTANCE_COUNT in the module itsself
  3. add an #ifndef MODULE_MAX_INSTANCE_COUNT #error to the modules header file to make sure the modules user is aware of this limitation and defines the maximum number of instances wanted for the application
  4. on initialization of an instance return either the memory address (*void) of the desricptive struct or the modules index (whatever you like more)

Best Answer

Is there an option to combine these somehow for anonymous struct, linker instead of heap allocation and multiple/any number of instances?

Sure there is. First, however, recognize that the "any number" of instances must be fixed, or at least an upper bound established, at compile time. This is a prerequisite for the instances to be statically allocated (what you're calling "linker allocation"). You might make the number adjustable without source modification by declaring a macro that specifies it.

Then the source file containing the actual struct declaration and all its associated functions also declares an array of instances with internal linkage. It provides either an array, with external linkage, of pointers to the instances or else a function for accessing the various pointers by index. The function variation is a bit more modular:

module.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];
}

I guess you're already familiar with how the header would then declare the struct as an incomplete type and declare all the functions (written in terms of pointers to that type). For example:

module.h

#ifndef MODULE_H
#define MODULE_H

struct module;

struct module *module_init(unsigned index);

// other functions ...

#endif

Now struct module is opaque in translation units other than module.c,* and you can access and use up to the number of instances defined at compile time without any dynamic allocation.


*Unless you copy its definition, of course. The point is that module.h does not do that.