How to eliminate unused elements in structures?

Background

I am working on a modular architecture for embedded devices, where different levels of abstraction must interact with each other. The current approach is to have many functions, variables, and defines the distorted names of the modules to which they belong.

This approach is a bit painful, and I would like to define some common interfaces for the API. The basic idea is to better understand which modules use the same HAL interface.

Sentence

I would like to use OOP-inspired architecture where I use structures as interfaces. All these structures are statically populated.

This solution looks good, but I can spend a lot of memory because the compiler does not know how to chop off the structures and save only what it really needs.

The following example can be built with or without -DDIRECT , and the behavior should be exactly the same.

Example

Source (test.c)

 #include <stdlib.h> int do_foo(int a) { return 42 * a; } #ifdef DIRECT int (*foo)(int) = do_foo; int (*bar)(int); int storage; #else struct foo_ts { int (*do_foo)(int); int (*do_bar)(int); int storage; } foo = {.do_foo = do_foo}; #endif int main(char argc) { #ifdef DIRECT return foo(argc); #else return foo.do_foo(argc); #endif } 

Makefile

 CFLAGS=-O2 -mthumb -mcpu=cortex-m3 -g --specs=nosys.specs CC=arm-none-eabi-gcc upper=$(shell echo $(1) | tr az AZ) EXEC=direct.out with_struct.out all: $(EXEC) %.out:test.c $(CC) $(CFLAGS) -D$(call upper,$(basename $@ )) -o $@ test.c size $@ 

Output

You may notice that the memory area used with the struct option is larger because the compiler does not allow itself to delete unused elements.

 arm-none-eabi-gcc -O2 -mthumb -mcpu=cortex-m3 -g \ --specs=nosys.specs -DDIRECT -o direct.out test.c size direct.out text data bss dec hex filename 992 1092 36 2120 848 direct.out arm-none-eabi-gcc -O2 -mthumb -mcpu=cortex-m3 -g \ --specs=nosys.specs -DWITH_STRUCT -o with_struct.out test.c size with_struct.out text data bss dec hex filename 992 1100 28 2120 848 with_struct.out 

Question

In this example, I demonstrate that using structure is useful for readability and modularity, but it can reduce efficiency and increase memory usage.

Is there a way to get the benefits of both solutions? In other words, is there a way to tell the compiler smarter?

OOP?

Following the comments on this, one suggestion is to use C ++ instead. Unfortunately, the same problem would arise because a class with unused members would never be simplified by the compiler. Therefore, I fall into the same trap with both languages.

Another issue raised was the cause of unused members in structures. To solve this issue, we can present a common 3-axis accelerometer used in an application where only one axis is used. The HAL for this accelerometer can have the read_x_acc , read_y_acc and read_z_acc methods, and the read_z_acc application read_x_acc used.

If I declare a class in C ++ or a structure in C, function pointers to unused methods / functions will still consume memory for nothing.

+5
source share
2 answers

Let me first show you a possible approach to your editing, where your current interface has three functions, but sometimes you only need one of them. You can define two interfaces:

 typedef struct I1DAccel { double (*read)(void); } I1DAccel; typedef struct I3DAccel { union { I1DAccel x; struct { double (*read_x)(void); }; }; union { I1DAccel y; struct { double (*read_y)(void); }; }; union { I1DAccel z; struct { double (*read_z)(void); }; }; } I3DAccel; 

Then you can do the execution of the Accelerometer as follows:

 I1DAccel accel_x = { read_x_acc }; I1DAccel accel_y = { read_y_acc }; I1DAccel accel_z = { read_z_acc }; I3DAccel accel = { .read_x = read_x_acc, .read_y = read_y_acc, .read_z = read_z_acc, }; 

With proper optimization of the connection time, the compiler can discard any of these global structures that are not used by the application code.

Of course, you will consume more memory if only one part of your code requires only accel_x , and the other part requires just accel . You will have to manually track these cases.


Of course, using struct s “interface”, you always consume more memory than without them, pointers must be stored somewhere. Therefore, a typical approach is to simply add the module name to the functions and call them directly, for example, for example, in the case of such an accelerometer:

 typedef int acchndl; // if you need more than one accelerometer double Accel_read_x(acchndl accel); double Accel_read_y(acchndl accel); double Accel_read_z(acchndl accel); 

This is conceptually similar to what you would do, for example, in C ++:

 class Accel { double read_x(); double read_y(); double read_z(); }; double Accel::read_x() { // implementation here } 

Using the simple C above, instead of an instance pointer, you can use any other type of “object descriptor”, as shown by typedef to int , which is often an advantage for inline code.

+1
source

This solution looks good, but I can spend a lot of memory because the compiler does not know how to chop off the structures and save only what it really needs.
...
Is there a way to get the benefits of both solutions? In other words, is there a way to tell the compiler smarter?

One of the problems is that “C” compiles things as a module (compilation unit), and often there is no easy way to find out what will and will not be used in the structure. Consider that a structure can be transferred from module to module, as you suggest. When compiling one module, there is no context / information to know that some other module will use the structure or not.

Good, so this leads to some possible solutions. gcc has the -fwhole-program option . See - fipa-struct-reorg as well as gold and LTO . Here you have to structure your makefile so that the compiler (code generator) has all the information available to find out if it can remove structure elements. This does not mean that these options will do what you want with the current gcc , just for them to require to make it work.


C ++

You can do anything in C, which you can use in C ++; just not like a programmer. See: Operator overload in 'C' . So, how can the 'C' compiler implement your virtual functions? Your virtual C ++ might look like below

 struct foo_ts_virtual_table { int my_type /* RTTI value for polymorphism. */ int (*do_foo)(int); int (*do_bar)(int); } foo_vtable = {.my_type = ENUM_FOO_TS; .do_foo = foo}; struct foo_ts { void * vtable; /* a foo_ts_virtual_table pointer */ int storage; } foo = {.vtable = foo_vtable}; 

This is reasonable memory if you have several foo_ts structures. The downside of this is that the function pointer is hard to line. Often, a simple body can be less than the overhead of an ARM processor. . This results in more code memory and slower execution.

C ++ compilers will aim to eliminate these extraneous functions, simply because this is a common problem in C ++. The analysis of "C" compilers is mixed with your custom code / notation and definition of the language of the structure and compilation units.


Other possibilities are to wrap function calls in macros and pass data to an unrelated section. You can check the unrelated section to see whether or not to enable the function (and pointer) in the last link. Whether you win or not depends on many different design goals. This will certainly complicate the build process and confuse other developers.

In addition, interest may be ARM Linux MULTI_CPU . This is only to remove function pointers if only one type is required at runtime.

If you insist on function pointers, there will be times when it actually generates more code. C ++ compilers will keep accounting records to see when the virtual environment can be aligned, and also possibly not emit unused member functions in the virtual table (and function implementation). These problems may be more pronounced than the additional overhead.

+1
source

Source: https://habr.com/ru/post/1270709/


All Articles