在main()之前的编译时或运行时初始化函数指针的全局数组

azu*_*elo 4 c c++

我正在尝试在C或C++中在编译时初始化函数指针的全局数组.像这样的东西:

module.h中

typedef int16_t (*myfunc_t)(void);
extern myfunc_array[];
Run Code Online (Sandbox Code Playgroud)

module.cpp

#include "module.h"
int16_t myfunc_1();
int16_t myfunc_2();
...
int16_t myfunc_N();

// the ordering of functions is not that important
myfunc_array[] = { myfunc_1, myfunc_2, ... , myfunc_N };
Run Code Online (Sandbox Code Playgroud)

func1.cpp,func2.cpp,... funcN.cpp(指向单个func.cpp文件的符号链接,以便创建不同的目标文件:func1.o,func2.o,func3.o,...,funcN .o.NUMBER定义使用g++ -DNUMBER=N)

#include "module.h"
#define CONCAT2(x, y) x ## y
#define CONCAT(x, y) CONCAT2(x, y)

int16_t CONCAT(myfunc_, NUMBER)() { ... }
Run Code Online (Sandbox Code Playgroud)

使用g ++ -DNUMBER = N编译时,预处理后变为:

func1.cpp

...
int16_t myfunc_1() { ... }
Run Code Online (Sandbox Code Playgroud)

func2.cpp

...
int16_t myfunc_2() { ... }
Run Code Online (Sandbox Code Playgroud)

等等.

声明myfunc_N()和初始化myfunc_array[]并不酷,因为N经常变化,可能在10到200之间.我不想使用脚本或Makefile来生成它们.功能的排序并不重要,我可以解决这个问题.是否有更简洁/更聪明的方法来做到这一点?

Ben*_*son 18

如何创建一个低级函数注册表

首先,创建一个宏,在特殊部分中放置指向函数的指针:

/* original typedef from question: */
typedef int16_t (*myfunc)(void);

#define myfunc_register(N) \
    static myfunc registered_##myfunc_##N \
      __attribute__((__section__(".myfunc_registry"))) = myfunc_##N
Run Code Online (Sandbox Code Playgroud)

静态变量名是任意的(它永远不会被使用),但选择一个富有表现力的名称是很好的.您可以通过将注册放在函数下方来使用它:

myfunc_register(NUMBER);
Run Code Online (Sandbox Code Playgroud)

现在,当您编译文件时(每次),它将在该部分中指向您的函数.myfunc_registry.这将按原样编译,但如果没有链接器脚本,它将不会对您有任何好处.感谢caf,指出了相对较新的INSERT AFTER功能:

SECTIONS
{
    .rel.rodata.myfunc_registry : {
        PROVIDE(myfunc_registry_start = .);
        *(.myfunc_registry)
        PROVIDE(myfunc_registry_end = .);
    }
}
INSERT AFTER .text;
Run Code Online (Sandbox Code Playgroud)

这个方案最难的部分是创建整个链接器脚本:您需要将该片段嵌入到主机实际链接描述文件中,这可能只能通过手工构建binutils并检查编译树或通过来实现strings ld.这很遗憾,因为我非常喜欢链接器脚本技巧.

链接与gcc -Wl,-Tlinkerscript.ld ...-T选项将增强(而不是替换)现有的链接描述文件.

现在链接器将收集所有指向section属性的指针,并有助于在列表前后提供一个指向的符号:

extern myfunc myfunc_registry_start[], myfunc_registry_end[];
Run Code Online (Sandbox Code Playgroud)

现在您可以访问您的阵列:

/* this cannot be static because it is not know at compile time */
size_t myfunc_registry_size = (myfunc_registry_end - myfunc_registry_start);
int i;

for (i = 0; i < myfunc_registry_size); ++i)
    (*myfunc_registry_start[i])();
Run Code Online (Sandbox Code Playgroud)

它们不会有任何特定的顺序.您可以通过将它们放入__section__(".myfunc_registry." #N)并在链接器收集中对它们进行编号*(.myfunc_registry.*),但排序将是lexographic而不是numeric.

我用gcc 4.3.0测试了这个(虽然gcc部件已经可以使用了很长时间)和ld 2.18.50(你需要一个相当新的ld用于INSERT AFTER魔术).

这与编译器和链接器共同执行全局ctors的方式非常相似,因此使用静态C++类构造函数来注册函数并使其更具可移植性要容易得多.

您可以在Linux内核中找到相关示例,例如__initcall与此非常类似.

  • 编译时可以使用`-Wl, - verbose`选项来查看内部链接描述文件. (2认同)
  • 您实际上并不需要链接描述文件。你可以依赖 GNU ld 的 `__start_SEGMENTNAME` 和 `__stop_SEGMENTNAME` 符号,或者通过以特定顺序传递 `.o` 文件名来创建你自己的(比如 `crtbegin.o` 和 `crtend.o` 如何为全局构造函数工作和析构函数)。 (2认同)

Mat*_*ner 0

我本来打算建议这个问题更多地与 C 有关,但转念一想,您想要的是函数指针的全局容器,并将可用函数注册到其中。我相信这就是所谓的单例(不寒而栗)。

您可以创建myfunc_array一个向量,或者包装一个 C 等价物,并提供一个函数将myfuncs 推入其中。现在,您终于可以创建一个类(您也可以在 C 中执行此操作),该类接受 amyfunc并将其推入全局数组中。这一切都将在调用 main 之前立即发生。以下是一些可以让您思考的代码片段:

// a header

extern vector<myfunc> myfunc_array;

struct _register_myfunc { 
    _register_myfunc(myfunc lolz0rs) {
        myfunc_array.push_back(lolz0rs);
    }
}

#define register_myfunc(lolz0rs) static _register_myfunc _unique_name(lolz0rs);

// a source

vector<myfunc> myfunc_array;

// another source

int16_t myfunc_1() { ... }
register_myfunc(myfunc_1);

// another source

int16_t myfunc_2() { ... }
register_myfunc(myfunc_2);
Run Code Online (Sandbox Code Playgroud)

请记住以下几点: