使用预处理器将外部函数指针更改为外部指针

changing extern function pointer to extern pointer using preprocessor

我正在使用我不应该更改它的库文件,包括我的 h 文件。

库的代码看起来像这样:

#include "my_file"
extern void (*some_func)();
void foo()
{
    (some_func)();
}

我的问题是我希望 some_func 是外部函数而不是指向函数的外部指针(我正在实施和链接 some_func)。以及 main 将如何调用它。

这样我会节省很少的 运行 时间和代码 space,并且没有人会错误地改变这个全局。

可能吗?

我考虑添加 my_file.h 东西作为 #define *some_func some_func 但它不会编译,因为#define 中不允许使用星号。

编辑

该文件尚未编译,因此 my_file.h 处的更改将影响编译。

首先你说库的源不能改。嗯,这样不好,有些“背叛”是必要的

我的做法是让指针some_func的声明原样,一个非常量可写变量,但是实现它作为常量不可写变量,它会被初始化一次想要的地址。

这是最小的、可重现的例子。

该库已按照您向我们展示的方式实施:

// lib.c

#include "my_file"

extern void (*some_func)();

void foo()
{
    (some_func)();
}

既然你在库的源代码中有这个包含文件,我提供一个。但是它是空的。

// my_file

我使用一个声明库的 public API 的头文件。这个文件还有指针的可写声明,让违规者相信可以改

// lib.h

extern void (*some_func)();

void foo();

我分离了一个有问题的模块来尝试不可能的事情。它有一个头文件和一个实现文件。在源代码中标记了错误的分配,已经揭示了将要发生的事情。

// offender.h

void offend(void);
// offender.c

#include <stdio.h>

#include "lib.h"
#include "offender.h"

static void other_func()
{
    puts("other_func");
}

void offend(void)
{
    some_func = other_func; // the assignment gives a run-time error
}

测试程序由这个小源组成。为避免编译器错误,声明必须归因于 const。在这里,我们包含声明头文件的地方,我们可以使用一些预处理器魔法。

// main.c

#include <stdio.h>

#define some_func const some_func
#include "lib.h"
#undef some_func

#include "offender.h"

static void my_func()
{
    puts("my_func");
}

void (* const some_func)() = my_func;

int main(void)
{
    foo();

    offend();
    foo();

    return 0;
}

诀窍是,编译器将指针变量放在可执行文件的只读部分。 const 属性仅供编译器使用,不存储在中间目标文件中,linker 愉快地解析所有引用。对该变量的任何写入访问都会产生 运行 时间错误。

现在所有这些都编译成可执行文件,我在 Windows 上使用了 GCC。我没有费心去创建一个单独的库,因为它对效果没有影响。

gcc -Wall -Wextra -g main.c offender.c lib.c -o test.exe

如果我运行“cmd”中的可执行文件,它只会打印“my_func”。显然 foo() 的第二次调用从未执行过。 ERRORLEVEL 为 -1073741819,即 0xC0000005。查找此代码给出了“STATUS_ACCESS_VIOLATION”的含义,在其他系统上称为“分段错误”。

因为我特意用调试标志编译-g,我可以使用调试器进行更深入的检查。

d:\tmp\Whosebug3> gdb -q test.exe
Reading symbols from test.exe...done.
(gdb) r
Starting program: d:\tmp\Whosebug3\test.exe
[New Thread 12696.0x1f00]
[New Thread 12696.0x15d8]
my_func

Thread 1 received signal SIGSEGV, Segmentation fault.
0x00000000004015c9 in offend () at offender.c:16
16          some_func = other_func;

好的,如我所愿,分配被阻止。但是,系统的反应相当刺耳。

不幸的是,我们无法获得编译时或 link 时错误。这是因为库的设计,如你所说,它是固定的。

如果您使用的是 GCC 或相关的,您可以查看 ifunc attribute。它应该在加载时修补一个小蹦床。因此,在调用该函数时,会使用已知的静态地址调用蹦床,然后在蹦床内部有一条用真实地址修补的跳转指令。所以当运行的时候,所有的跳转位置都直接在代码里面,配合指令缓存应该是高效的。请注意,它甚至可能比这更有效,但至多与调用函数指针一样糟糕。以下是您将如何实施它:

extern void (*some_func)(void); // defined in the header you do not have control about

void some_func_resolved(void) __attribute__((ifunc("resolve_some_func")));

static void (*resolve_some_func(void)) (void)
{
    return some_func;
}

// call some_func_resolved instead now