You'd better be very sure that a fixed layout is what you want! Tearing it down and putting in a dynamic one could get very tricky!
I suggest the problems that any embedded framework are trying to manage are:
Calculating offsets to data
It should be possible to create a single struct for all memory but this _so_ doesn't feel like the right way to do it. C compilers are not usually asked to work with multi-megabyte structures and I get the feeling doing this is not very portable between compilers.
If structures are not used, then five sets of defines are needed, based on what is essentially a data schema:
- sizes of simple types
- offsets of fields within group types
- sizes of group types
- offsets of groups in runs of groups
- size of runs of groups
- (possibly also absolute addresses of runs of groups if performance dictates)
These defines have a tree-like dependency tree, that rapidly gets very complicated in raw C because types usually have to be packed/aligned eg in 4-byte clumps for optimal performance. The fully expanded defines can quickly end up more complex than some compilers are happy to process.
The easiest way to manage these issues in raw C projects is to calculate the offsets with a C program that is a "build tool" executable of the project and import them into the project as a .h file that contains explicit opffset numbers. With this approach a single macro taking a base address and relevant indices should be made available during the main compile for accessing each leaf of the data structure.
Avoiding function pointer corruption and maximising debugging productivity
If function pointers are stored in the object they are more vulnerable to data corruption leading to mysterious errors. The better way (for those memory ranges that contain different object types from time to time) is to store a vtable code in the object which is a lookup index into a set of function pointer sets.
The vtables can again be computed and generated as a .h file with #defines by a generator C program that is an executable "build tool".
A special constructor macro is required to write in the appropriate vtable id to initialise usage of an object.
Both of these problems are effectively well-solved already by for example the objective-C preprocessor (which will output raw C), but you can do it from scratch if you want to stay with a very small set of tools.
Allocating memory blocks to resources/tasks in the static memory structure
If you need to support multi-threading, associating short-lived dynamic tasks with particular indexes in the tree structure (nearest thing to allocating an object in the equivalent procedural/OO program) is perhaps best performed by trial-locking an arbitrary index, using for example an atomic increment (from zero with ==1 check) or mutex, then checking to see if the block is available, and if so, marking it as used, then unlocking the block.
If multi-threading support is not required then this is unnecessary; I suggest writing a custom framework for managing such resource allocation processes which can be run in either a multi-threaded or single-threaded mode, to allow the rest of the code base to be unconcerned with this topic, and to allow faster performance on single-threaded systems.