使用预处理器将外部函数指针更改为外部指针
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
我正在使用我不应该更改它的库文件,包括我的 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