now*_*wox 5 c embedded gcc arm
我正致力于嵌入式设备的模块化架构,其中不同的抽象层必须彼此通信.当前的方法是拥有大量的函数,变量和定义它们所属的模块名称.
这种方法有点痛苦,我想为API定义某种通用接口.关键的想法是更好地了解哪些模块共享相同的HAL接口.
我想使用OOP启发的架构,我将结构用作接口.所有这些结构都是静态填充的.
这个解决方案看起来不错但我可能会浪费大量内存,因为编译器不知道如何切断结构并只保留它真正需要的内容.
以下示例可以使用或不使用构建,-DDIRECT并且行为应该完全相同.
#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
}
Run Code Online (Sandbox Code Playgroud)
CFLAGS=-O2 -mthumb -mcpu=cortex-m3 -g --specs=nosys.specs
CC=arm-none-eabi-gcc
upper=$(shell echo $(1) | tr a-z A-Z)
EXEC=direct.out with_struct.out
all: $(EXEC)
%.out:test.c
$(CC) $(CFLAGS) -D$(call upper,$(basename $@)) -o $@ test.c
size $@
Run Code Online (Sandbox Code Playgroud)
可以注意到struct变体使用的内存占用量更大,因为编译器不允许自己删除未使用的成员.
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
Run Code Online (Sandbox Code Playgroud)
在这个例子中,我演示了使用结构有利于可读性和模块化,但它可能会降低效率并增加内存使用量.
有没有办法获得两种解决方案的优势?换句话说,有没有办法告诉编译器更聪明?
关于这个问题的评论,一个建议是使用C++代替.不幸的是,会出现同样的问题,因为编译器永远不会简化具有未使用成员的类.所以我和两种语言陷入了同样的陷阱.
另一个提出的观点是结构中未使用成员的原因.为了解决这个问题,我们可以想象一个通用的3轴加速度计,用于仅使用1轴的应用中.该加速度计的HAL可以有方法read_x_acc,read_y_acc和read_z_acc而只有read_x_acc在应用程序使用.
如果我用C++或C语言中的结构声明一个类,那么未使用的方法/函数的函数指针仍会消耗内存.
小智 1
让我首先向您展示一种可能的编辑方法,您当前的界面具有三个功能,但有时您只需要其中一个。您可以定义两个接口:
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;
Run Code Online (Sandbox Code Playgroud)
然后您可以使加速度计的实现执行以下操作:
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,
};
Run Code Online (Sandbox Code Playgroud)
通过适当的链接时优化,编译器可以丢弃应用程序代码未使用的任何全局结构。
当然,如果代码的一部分仅需要而accel_x另一部分需要整个accel. 您必须手动查找这些案例。
当然,使用“接口” struct,您总是会比没有它们消耗更多的内存,指针必须存储在某个地方。因此,典型的方法确实是将模块名称添加到函数前面并直接调用它们,例如在这样的加速度计的情况下:
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);
Run Code Online (Sandbox Code Playgroud)
这在概念上类似于您在 C++ 中所做的事情:
class Accel
{
double read_x();
double read_y();
double read_z();
};
double Accel::read_x()
{
// implementation here
}
Run Code Online (Sandbox Code Playgroud)
使用上面的普通 C,您可以使用任何其他类型的“对象句柄”,而不是实例指针typedef,如to所示int,这通常是嵌入式代码的优势。