c++ - 在链接时合并全局数组/从多个编译单元填充全局数组

标签 c++ c arduino embedded compile-time

我想定义一组东西,比如事件处理程序。的内容 这个数组在编译时是完全已知的,但是定义在 多个编译单元,分布在多个库中 是相当解耦的,至少在最终(静态)链接之前是这样。我想要 也保持这种方式 - 所以添加或删除编译单元将 还可以自动管理事件处理程序而无需修改 事件处理程序的中央列表。

这是我想做的事情的一个例子(但行不通)。

中央.h:

typedef void (*callback_t)(void);

callback_t callbacks[];

中央.c:

#include "central.h"

void do_callbacks(void) {
    int i;
    for (i = 0; i < sizeof(callbacks) / sizeof(*callbacks); ++i)
        callbacks[i]();
}

foo.c:

#include "central.h"

void callback_foo(void) { }

callback_t callbacks[] = {
    &callback_foo
};

酒吧.c:

#include "central.h"

void callback_bar(void) { }

callback_t callbacks[] = {
    &callback_bar
};

我想要的是获得一个callbacks 数组,其中包含 两个元素:&callback_foo&callback_bar。使用上面的代码,有 显然有两个问题:

  • callbacks 数组被定义了多次。
  • sizeof(callbacks) 在编译 central.c 时未知。

在我看来,第一点可以通过合并链接器来解决 两个 callbacks 符号而不是抛出错误(可能通过一些 变量上的属性),但我不确定是否有类似的东西。 即使有,sizeof 问题也应该以某种方式解决。

我意识到这个问题的一个常见解决方案是启动 “注册”回调的函数或构造函数。然而,我只能看到 两种实现方式:

  • 为回调数组使用动态内存 (realloc)。
  • 使用固定大小(比通常需要的大)的静态内存。

由于我在内存有限的微 Controller 平台 (Arduino) 上运行, 这些方法都对我没有吸引力。鉴于 该数组在编译时已知,我希望有一种方法让编译器 也看到这个。

我找到了 thisthis解决方案,但那些需要自定义 链接器脚本,这在我的编译环境中是不可行的 运行(特别是因为这需要明确命名每个 链接描述文件中的这些特殊数组,所以只需要一个 链接描述文件添加在这里不起作用)。

This solution是迄今为止我发现的最好的。它使用链表 在运行时填充,但使用每个静态分配的内存 单独编译单元(例如,下一个指针分配给每个 函数指针)。尽管如此,这些下一个指针的开销不应该 需要 - 有没有更好的方法?

也许结合链接时间优化的动态解决方案可以 以某种方式导致静态分配?

也欢迎就替代方法提出建议,尽管需要 元素具有静态的事物列表和内存效率。

此外:

  • 使用 C++ 没问题,我只是用上面的一些 C 代码来说明问题,反正大多数 Arduino 代码都是 C++。
  • 我使用的是 gcc/avr-gcc,虽然我更喜欢可移植解决方案,但仅 gcc 也可以。
  • 我有可用的模板支持,但没有 STL。
  • 在我使用的 Arduino 环境中,我没有 Makefile 或其他方式可以在编译时轻松运行一些自定义代码,因此我正在寻找可以完全在代码中实现的东西。

最佳答案

正如在之前的一些回答中评论的那样,最好的选择是使用自定义链接描述文件(带有 KEEP(*(SORT(.whatever.*))) 输入部分)。

无论如何,它可以在不修改链接器脚本的情况下完成(下面的工作示例代码),至少在某些带有 gcc 的平台上(在 xtensa 嵌入式设备和 cygwin 上测试)

假设:

  • 我们希望尽可能避免使用 RAM(嵌入式)
  • 我们不希望调用模块知道任何关于带有回调的模块的信息(它是一个库)
  • 列表没有固定大小(库编译时大小未知)
  • 我正在使用 GCC。原理可能在其他编译器上有效,但我没有测试过
  • 此示例中的回调函数不接收任何参数,但如果需要修改起来非常简单

怎么做:

  • 我们需要链接器以某种方式在链接时分配指向函数的指针数组
  • 由于我们不知道数组的大小,我们还需要链接器以某种方式标记数组的结尾

这是非常具体的,因为正确的方法是使用自定义链接描述文件,但如果我们在标准链接描述文件中找到始终“保留”和“排序”的部分,则不这样做也是可行的。

通常,对于 .ctors.* 输入部分是这样的(标准要求 C++ 构造函数按函数名的顺序执行,在标准的 ld 脚本中是这样实现的),所以我们可以破解并尝试一下。

请注意它可能不适用于所有平台(我已经在 xtensa 嵌入式架构和 CygWIN 中对其进行了测试,但这是一种黑客技巧,所以...)。

此外,由于我们将指针放在构造函数部分,我们需要使用一个字节的 RAM(对于整个程序)来跳过 C 运行时初始化期间的回调代码。


测试.c:

一个库,注册了一个名为test 的模块,并在某个时候调用它的回调

#include "callback.h"

CALLBACK_LIST(test);

void do_something_and_call_the_callbacks(void) {

        // ... doing something here ...

        CALLBACKS(test);

        // ... doing something else ...
}

callme1.c:

客户端代码为模块 test 注册两个回调。生成的函数没有名字(它们确实有名字,但它是神奇地生成的,在编译单元内是唯一的)

#include <stdio.h>
#include "callback.h"

CALLBACK(test) {
        printf("%s: %s\n", __FILE__, __FUNCTION__);
}

CALLBACK(test) {
        printf("%s: %s\n", __FILE__, __FUNCTION__);
}

void callme1(void) {} // stub to be called in the test sample to include the compilation unit. Not needed in real code...

callme2.c:

客户端代码正在为模块 test 注册另一个回调...

#include <stdio.h>
#include "callback.h"

CALLBACK(test) {
        printf("%s: %s\n", __FILE__, __FUNCTION__);
}

void callme2(void) {} // stub to be called in the test sample to include the compilation unit. Not needed in real code...

回调.h:

还有魔法……

#ifndef __CALLBACK_H__
#define __CALLBACK_H__

#ifdef __cplusplus
extern "C" {
#endif

typedef void (* callback)(void);
int __attribute__((weak)) _callback_ctor_stub = 0;

#ifdef __cplusplus
}
#endif

#define _PASTE(a, b)    a ## b
#define PASTE(a, b)     _PASTE(a, b)

#define CALLBACK(module) \
        static inline void PASTE(_ ## module ## _callback_, __LINE__)(void); \
        static void PASTE(_ ## module ## _callback_ctor_, __LINE__)(void); \
        static __attribute__((section(".ctors.callback." #module "$2"))) __attribute__((used)) const callback PASTE(__ ## module ## _callback_, __LINE__) = PASTE(_ ## module ## _callback_ctor_, __LINE__); \
        static void PASTE(_ ## module ## _callback_ctor_, __LINE__)(void) { \
                 if(_callback_ctor_stub) PASTE(_ ## module ## _callback_, __LINE__)(); \
        } \
        inline void PASTE(_ ## module ## _callback_, __LINE__)(void)

#define CALLBACK_LIST(module) \
        static __attribute__((section(".ctors.callback." #module "$1"))) const callback _ ## module ## _callbacks_start[0] = {}; \
        static __attribute__((section(".ctors.callback." #module "$3"))) const callback _ ## module ## _callbacks_end[0] = {}

#define CALLBACKS(module) do { \
        const callback *cb; \
        _callback_ctor_stub = 1; \
        for(cb =  _ ## module ## _callbacks_start ; cb <  _ ## module ## _callbacks_end ; cb++) (*cb)(); \
} while(0)

#endif

ma​​in.c:

如果你想试一试......这是一个独立程序的入口点(经过测试并在 gcc-cygwin 上工作)

void do_something_and_call_the_callbacks(void);

int main() {
    do_something_and_call_the_callbacks();
}

输出:

这是我的嵌入式设备中的(相关)输出。函数名称在 callback.h 中生成,并且可以重复,因为函数是静态的

app/callme1.c: _test_callback_8
app/callme1.c: _test_callback_4
app/callme2.c: _test_callback_4

在 CygWIN 中...

$ gcc -c -o callme1.o callme1.c
$ gcc -c -o callme2.o callme2.c
$ gcc -c -o test.o test.c
$ gcc -c -o main.o main.c
$ gcc -o testme test.o callme1.o callme2.o main.o
$ ./testme
callme1.c: _test_callback_4
callme1.c: _test_callback_8
callme2.c: _test_callback_4

链接器映射:

这是链接器生成的映射文件的相关部分

 *(SORT(.ctors.*))
 .ctors.callback.test$1    0x4024f040    0x0    .build/testme.a(test.o)
 .ctors.callback.test$2    0x4024f040    0x8    .build/testme.a(callme1.o)
 .ctors.callback.test$2    0x4024f048    0x4    .build/testme.a(callme2.o)
 .ctors.callback.test$3    0x4024f04c    0x0    .build/testme.a(test.o)

关于c++ - 在链接时合并全局数组/从多个编译单元填充全局数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24283277/

相关文章:

c++ - 重载复制构造函数

c - 指针类型与 PyArray_SimpleNew 不匹配

c - 在C编程中达到EOF时如何退出stdin

c - gcc 的 wrap 选项对函数 printf 有影响吗?

ios - 通过我的手机神奇地将设备桥接到我的网络?

node.js - Nodejs与Arduino之间的串口通信

c++ - Arduino HC-SR04 传感器,平均数据

c++ - #including <iostream> 中断共享对象的链接

c++ - 需要选择一个容器来存储我的数据

c++ - MSVC 2015 使用 2013 平台工具集