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

2024-05-05

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

这是我想做的一个例子(但不起作用)。

中央.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
};

bar.c:

#include "central.h"

void callback_bar(void) { }

callback_t callbacks[] = {
    &callback_bar
};

我想要发生的是得到一个callbacks数组,其中包含 两个要素:&callback_foo and &callback_bar。有了上面的代码,就有了 显然有两个问题:

  • The callbacks数组被定义多次。
  • sizeof(callbacks)编译时不知道central.c.

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

我意识到解决这个问题的一个常见方法就是创办一家初创公司 “注册”回调的函数或构造函数。然而我只能看到 有两种方法可以实现这一点:

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

由于我在内存有限的微控制器平台(Arduino)上运行, 这两种方法都不吸引我。并考虑到全部内容 该数组在编译时是已知的,我希望有一种方法可以让编译器 也看到这个。

我发现了this https://stackoverflow.com/a/17712647/740048 and this https://stackoverflow.com/a/4152185/740048解决方案,但这些需要定制 链接描述文件,这在我的编译环境中是不可行的 运行(尤其不是因为这需要明确命名每个 链接器脚本中这些特殊数组的组成,因此只需一个 链接描述文件添加在这里不起作用)。

这个解决方案 https://stackoverflow.com/a/15142972/740048是迄今为止我发现的最好的。它使用链表 在运行时填充,但使用在每个中静态分配的内存 单独编译单元(例如,为每个单元分配一个下一个指针) 函数指针)。尽管如此,这些下一个指针的开销不应该 需要 - 有更好的方法吗?

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

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

此外:

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

正如之前的一些答案中所评论的,最好的选择是使用自定义链接器脚本(带有KEEP(*(SORT(.whatever.*)))输入部分)。

Anyway, 无需修改链接描述文件即可完成(下面的工作示例代码),至少在一些带有 gcc 的平台上(在 xtensa 嵌入式设备和 cygwin 上测试)

假设:

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

怎么做:

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

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

通常情况下,这对于.ctors.*输入部分(标准要求 C++ 构造函数按函数名称顺序执行,并且在标准 ld 脚本中是这样实现的),因此我们可以稍微修改一下并尝试一下。

只是要考虑到它可能不适用于所有平台(我已经在 xtensa 嵌入式架构和 CygWIN 中测试过它,但这是一个黑客技巧,所以......)。

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


test.c:

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

#include "callback.h"

CALLBACK_LIST(test);

void do_something_and_call_the_callbacks(void) {

        // ... doing something here ...

        CALLBACKS(test);

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

呼叫我1.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...

呼叫我2.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

main.c:

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

void do_something_and_call_the_callbacks(void);

int main() {
    do_something_and_call_the_callbacks();
}

output:

这是我的嵌入式设备中的(相关)输出。函数名称生成于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)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在链接时合并全局数组/从多个编译单元填充全局数组 的相关文章

  • UTF8/UTF16 和 Base64 在编码方面有什么区别

    In c 我们可以使用下面的类来进行编码 System Text Encoding UTF8 System Text Encoding UTF16 System Text Encoding ASCII 为什么没有System Text En
  • 部署 MVC4 项目时出错:找不到文件或程序集

    过去 我只需使用 Visual Studio 2012 发布到 AWS 菜单项即可部署我的 MVC4 网站 到 AWS Elastic Beanstalk 现在 程序可以在本地编译并运行 但无法部署 从消息来看 它似乎正在寻找不在当前部署的
  • Func 方法参数的首选命名约定是什么?

    我承认这个问题是主观的 但我对社区的观点感兴趣 我有一个缓存类 它采用类型的缓存加载器函数Func
  • Cygwin 下使用 CMake 编译库

    我一直在尝试使用 CMake 来编译 TinyXML 作为一种迷你项目 尝试学习 CMake 作为补充 我试图将其编译成动态库并自行安装 以便它可以工作 到目前为止 我已经设法编译和安装它 但它编译成 dll 和 dll a 让它工作的唯一
  • 为什么 POSIX 允许在只读模式下超出现有文件结尾 (fseek) 进行搜索

    为什么寻找文件结尾很有用 为什么 POSIX 让我们像示例中那样在以只读方式打开的文件中进行查找 c http en cppreference com w c io fseek http en cppreference com w c io
  • 为什么禁止在 constexpr 函数中使用 goto?

    C 14 对你能做什么和不能做什么有规则constexpr功能 其中一些 没有asm 没有静态变量 看起来相当合理 但标准也不允许goto in constexpr功能 即使它允许其他控制流机制 这种区别背后的原因是什么 我以为我们已经过去
  • 写入和读取文本文件 - C# Windows 通用平台应用程序 Windows 10

    有用 但在显示任何内容之前 您必须在文本框中输入内容 我想那是因为我使用了 TextChanged 事件处理程序 如果我希望它在没有用户交互的情况下显示文本文件的内容 我应该使用哪个事件处理程序 因此 我想在按下按钮时将一些数据写入 C W
  • 使用 Google Analytics API 在 C# 中显示信息

    我一整天都在寻找一个好的解决方案 但谷歌发展得太快了 我找不到有效的解决方案 我想做的是 我有一个 Web 应用程序 它有一个管理部分 用户需要登录才能查看信息 在本节中 我想显示来自 GA 的一些数据 例如某些特定网址的综合浏览量 因为我
  • C# 用数组封送结构体

    假设我有一个类似于 public struct MyStruct public float a 我想用一些自定义数组大小实例化一个这样的结构 在本例中假设为 2 然后我将其封送到字节数组中 MyStruct s new MyStruct s
  • .Net Core / 控制台应用程序 / 配置 / XML

    我第一次尝试使用新的 ConfigurationBuilder 和选项模式进入 Net Core 库 这里有很多很好的例子 https docs asp net en latest fundamentals configuration ht
  • 线程、进程和 Application.Exit()

    我的应用程序由主消息循环 GUI 和线程 Task Factory 组成 在线程中我调用一些第三方应用程序var p new Process 但是当我调用Application Exit 在消息循环中 我可以看到在线程中启动的进程仍在内存中
  • 我的 strlcpy 版本

    海湾合作委员会 4 4 4 c89 我的程序做了很多字符串处理 我不想使用 strncpy 因为它不会终止 我不能使用 strlcpy 因为它不可移植 只是几个问题 我怎样才能让我的函数正常运行 以确保它完全安全稳定 单元测试 这对于生产来
  • 初始化变量的不同方式

    在 C 中初始化变量有多种方法 int z 3 与 int 相同z 3 Is int z z 3 same as int z z 3 您可以使用 int z z 3 Or just int z 3 Or int z 3 Or int z i
  • 网络参考共享类

    我用 Java 编写了一些 SOAP Web 服务 在 JBoss 5 1 上运行 其中两个共享一个类 AddressTO Web 服务在我的 ApplycationServer 上正确部署 一切都很顺利 直到我尝试在我的 C 客户端中使用
  • C 中的位移位

    如果与有符号整数对应的位模式右移 则 1 vacant bit will be filled by the sign bit 2 vacant bit will be filled by 0 3 The outcome is impleme
  • 已过时 - OpenCV 的错误模式

    我正在使用 OpenCV 1 进行一些图像处理 并且对 cvSetErrMode 函数 它是 CxCore 的一部分 感到困惑 OpenCV 具有三种错误模式 叶 调用错误处理程序后 程序终止 Parent 程序没有终止 但错误处理程序被调
  • 如何在内存中存储分子?

    我想将分子存储在内存中 这些可以是简单的分子 Methane CH4 C H bond length 108 7 pm H H angle 109 degrees But also more complex molecules like p
  • 窗体最大化时自动缩放子控件

    有没有办法在最大化屏幕或更改分辨率时使 Windows 窗体上的所有内容自动缩放 我发现手动缩放它是正确的 但是当切换分辨率时我每次都必须更改它 this AutoScaleDimensions new System Drawing Siz
  • 如何连接字符串和常量字符?

    我需要将 hello world 放入c中 我怎样才能做到这一点 string a hello const char b world const char C string a hello const char b world a b co
  • 将 viewbag 从操作控制器传递到部分视图

    我有一个带有部分视图的 mvc 视图 控制器中有一个 ActionResult 方法 它将返回 PartialView 因此 我需要将 ViewBag 数据从 ActionResult 方法传递到 Partial View 这是我的控制器

随机推荐

  • 在参数化中传递 pytest 夹具

    通过在 pytest mark parametrize 中传递 conftest py 中定义的装置 我收到以下错误 pytest alist 0220 0221 test 1 py v s NameError name alist is
  • 我们如何获取不同文件系统使用的文件分隔符?

    大家下午好 据我了解 Android 有 至少 2 个文件系统 一个用于 内部 存储 例如 data和 system 另一个用于 外部 存储 例如 mnt sdcard 这意味着当我们将文件保存到 内部 存储时 Context getFil
  • LessCSS 中的 @media 和 @font-face 支持

    你好 有谁知道如何使用 LessCSS 进行媒体查询吗 media screen and max width 600px container width 480px 给我以下错误 Syntax Error on line 23 expect
  • Javascript 不会删除 div 中的所有元素

    创建这段 JavaScript 代码是为了删除 div 内的所有输入 function remove inputs var elements document getElementById thediv getElementsByTagNa
  • 无法使用 Shinyjs() 禁用闪亮的应用程序单选按钮

    我正在尝试禁用闪亮的应用程序单选按钮 趋势 input Product A input month All 使用Shinyjs包 但没有成功 我的 ui 页面定义为 ui lt fluidPage shinyjs useShinyjs pa
  • WPF TabItem 标题模板

    示例代码
  • 如何更改 Android 中 DatePicker 的样式?

    我想更改 Android 中日期 时间选择器对话框的默认颜色 以便它与我的应用程序主题相匹配 我在Google上搜索了解决方案 但没有找到解决方案 我正在做的是创造一种新风格 不知道日期选择器对话框有哪些可用属性 如果有人可以发布一个链接
  • 为什么不能执行 mov [eax], [ebx] [重复]

    这个问题在这里已经有答案了 我可以做这个 mov eax ebx 和这个 mov eax ebx 甚至这个 mov eax ebx 但不是这个 错误C2415 mov eax ebx 只是wtf 为什么 它与 ptr1 ptr2 相同 为什
  • 解析带有 @ at 符号的 JSON (arobase)

    我的 JSON 对象的计算结果为 io IO type XXX 如果这个变量被调用my json 我如何访问 typeXXX 的值 我试过my json type 但这会产生错误 帮助表示赞赏 谢谢 Nick 对字符串使用方括号表示法 va
  • Team Foundation Build 还是 TeamCity?

    我们主要是一家从事 NET LOB 开发的 MS 商店 我们还在 CRM 应用程序中使用 MS Dynamics 所有开发人员目前都在使用 VS SQL Server 2008 我们也使用 VSS 但每个人在工作中都讨厌它 而且很快就会被淘
  • Guava Joiner 无法添加前缀和后缀[重复]

    这个问题在这里已经有答案了 我要求 Joiner 能够为元素添加前缀和后缀 例如 String str a b c Joiner on prefix suffix join str 预期输出为 a b c 我们有什么替代方案吗 因为番石榴不
  • 检查存储过程是否正在运行?

    是否可以检查 SQL Server 中当前是否有任何存储过程正在运行 我问过一次 查看 Sql Server 2000 如何找出当前正在运行哪些存储过程 https stackoverflow com questions 129086 sq
  • Razor 视图中的内联 If

    在我的控制器中 我有内联 If 语句 ViewBag NameSortParam If String IsNullOrEmpty sortOrder Name desc 在我看来 如果出现以下情况 我似乎无法使用内联 Code If Tru
  • Apache2启动失败,无错误日志

    我会重新启动 Apache2 但出现错误 sudo service apache2 start Starting web server apache2 Action start failed The Apache error log may
  • 将数据插入多个表 PHP MySQL

    我有一个用于存储食谱的基本数据结构 它由三个表组成 如下所示 表 1 食谱 recipe id recipe name 表 2 成分 成分 ID 成分名称 表 3 配方 成分 配方 id 成分 id 我在添加新配方时遇到问题 想知道插入的最
  • 使用 CSS 首字下沉

    我怎样才能使每个段落的第一个字符看起来像这样 我更喜欢只使用 CSS p first letter float left font size 5em line height 0 5em padding bottom 0 05em paddi
  • 从 C++ 代码自动生成流程图 [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我需要自动地用 C 代码构造流程图 最好每个源文件一个流程图 有没有任何工具 最好是 C Python
  • 批量设置变量=%变量:~1%是什么意思

    谁能解释一下是什么 1 在批处理文件中的以下语句中 我分配的值 variable到服务器名称并尝试过echo variable 我得到与输出相同的服务器名称 谁能解释一下下面的语句是如何工作的 set variable variable 1
  • 如何使用 Kinect 追踪一个人 (trackingID)

    我想跟踪第一个人 并使用这个人的右手在我制作的应用程序中导航 我可以接管光标 现在我只想跟踪一个人 因此 基本上 当一个人在程序中导航时 有人走在他身后或与这个人一起看 如果他们移动 kinect 不应该识别其他任何人 我怎样才能实现这个
  • 在链接时合并全局数组/从多个编译单元填充全局数组

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