Possibilities for allocating memory for modular firmware design in C

I program small micro-controllers in C++, which achieves exactly what you want.

What you call a module is a C++ class, it can contain data (either externally accessible or not) and functions (likewise). The constructor (a dedicated function) initilializes it. The constructor can take run-time parameters or (my favourite) compile-time (template) parameters. The functions within the class implicitly get the class variable as first parameter. (Or, often my preference, the class can act as a hidden singleton, so all data is accessed without this overhead).

The class object can be global (so you know at link-time that everything will fit), or stack-local, presumably in the main. (I don't like C++ globals because of the undefined global initialization order, so I prefer stack-local).

My preferred programming style is that modules are static classes, and their (static) configuration is by template parameters. This avoids nearly all overhad and enables optimization. Combine this with a tool that calculates the stack size and you can sleep without worries :)

My talk on this way of coding in C++: Objects? No Thanks!

A lot of embedded / microcontroller programmers seem to dislike C++ because they think it would force them to use all of C++. That is absolutely not neccessary, and would be a very bad idea. (You probably don't use all of C either! Think heap, floating point, setjmp/longjmp, printf, ...)


In a comment Adam Haun mentions RAII and initialization. IMO RAII has more to do with deconstruction, but his point is valid: global objects will be constructed before your main starts, so they might work on invalid assumptions (like a main clock speed that will be changed lateron). That is one more reason NOT to use global code-initialized objects. (I use a linker script that will fail when I have global code-initialized objects.) IMO such 'objects' should be explicitly created and passed around. This includes a 'waiting' facility 'object' that provides a wait() function. In my setup this is 'object' that sets the clock speed of the chip.

Talking about RAII: that is one more C++ feature that is very usefull in small embedded systems, although not for the reason (memory deallocation) it most used for in largere systems (small embedded systems mostly don't use dynamic memory deallocation). Think of locking a resource: you can make the locked resource a wrapper object, and restrict access to the resource to be only possible via the locking wrapper. When the wrapper goes out of scope, the resource is unlocked. This prevents access without locking, and makes it much more unlikely to forget the unlocking. with some (template) magic it can be zero-overhead.


The original question didn't mention C, hence my C++-centric answer. If it realy must be C....

You could use macro trickery: declare your stucts publicly, so they have a type and can be allocated globally, but mangle the names of their components beyond usability, unless some macro is defined differently, which is the case in your module's .c file. For extra security you could use the compile time in the mangling.

Or have a public version of your struct that has nothing usefull in it, and have the private version (with usefull data) only in your .c file, and assert that they are the same size. A bit of make-file trickery could automate this.


@Lundins comment about bad (embedded) programmers:

  • The type of programmer you describe would probably make a mess in any language. Macro's (present in C and C++) are one obvious way.

  • Tooling can help to some extent. For my students I mandate a built script that specifies no-exceptions, no-rtti, and gives a linker error when either the heap is used or code-initialized globals are present. And it specifies warning=error and enables nearly all warnings.

  • I encourage using templates, but with constexpr and concepts metaprogramming is less and less required.

  • "confused Arduino programmers" I would very much like to replace the Arduino (wiring, replication of code in libraries) programming style with a modern C++ approach, which can be easiere, more secure, and produce faster and smaller code. If only I had the time and power....


I believe FreeRTOS (maybe another OS?) does something like what you're looking for by defining 2 different versions of the struct.
The 'real' one, used internally by the OS functions, and a 'fake' one which is the same size as the 'real' one, but doesn't have any useful members inside (just a bunch of int dummy1 and similar).
Only the 'fake' struct is exposed outside of the OS code, and this is used to allocate memory to static instances of the struct.
Internally, when functions in the OS are called, they are passed the address of the external 'fake' struct as a handle, and this is then typecast as a pointer to a 'real' struct so the OS functions can do what they need to do.


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.

Tags:

C

Firmware

Design