返回

文章详情

使用GCC的嵌套函数与宽指针和无跳板 II

Hacker News2026年8月15日 07:59

使用GCC的嵌套函数与宽指针和无跳板 II 马丁·尤克,2026年7月14日 引言 当获取嵌套函数的地址时,GCC会在运行时创建一个跳板。这个跳板被放置在栈上,这意味着栈必须是可执行的。这是一个问题,因为不可执行的栈是一个重要的安全特性。近年来,GCC已有选项将跳板放置在堆上,这更好,但仍然不理想,因为分配成本较高,当使用longjmp时,跳板可能会泄漏。我之前指出如何通过一种新的宽指针类型来避免这一点,并谈到了一个实现了核心功能的GCC初步补丁,使用这一功能是必要的。在这里,我想给出两个更新。 GCC 16:无捕获的嵌套函数 首先,GCC 16发布了。在GCC 16中,保证不访问父函数变量的嵌套函数将不需要跳板。在实践中,这在进行优化时已经得到保证,但现在在不优化时也得到了保证并进行了文档化。这还意味着,您可以安全地从父函数中返回这样的函数,当尝试返回一个访问局部上下文的函数时,您会得到一个警告(至少在编译器明显的简单情况下),如以下示例(Godbolt 示例) typedef int cb_f(int); cb_f *foo(int x) { int worker(int y) { return x + y; // 捕获 } return worker; } cb_f *bar(int _x) { static int x; x = _x; int worker(int y) { return x + y; // 未捕获 } return worker; } 不访问父函数变量的嵌套函数仍然可以访问静态变量、命名常量或类型(如果没有被修改)。这对于编写回调函数是有用的(Godbolt 示例)。 typedef int cb_f(void *, int y); int process(cb_f *cb, void *data) { return cb(data, 5); } int foo(int x) { struct { int x; } data = { .x = x }; int worker(void *_data, int y) { typeof(data) *data = _data; return data->x + y; } return process(worker, &data); } 不过,嵌套函数的真正力量在于能够直接访问父函数的变量。另外,上面的代码看起来就像我们通过手动添加一些样板代码来模拟这样的函数 - 编译器当然应该能够为我们提供帮助。这是我的第二个更新的内容。 GCC 17:带捕获的嵌套函数 最近,我的一个补丁的版本被合并到GCC的开发分支中,该补丁实现了两个可以与现有的内置__builtin_call_with_static_chain一起使用的新内置函数,从而不需要跳板来编写这个示例(Godbolt 示例)。 typedef int cb_f(int y); static int process(cb_f *cb, void *data) { return __builtin_call_with_static_chain(cb(5), data); } int main() { int x = 7; int worker(int y) { return x + y; } return process(__builtin_call_code_address(worker), __builtin_call_static_chain(worker)); } 实际上,一旦跳板不再存在,GCC在后台所做的就是自动将其转换为上述示例!手动编写的版本唯一的区别是数据指针是通过特殊寄存器传递的。许多其他编程语言也使用这个寄存器来传递静态链,因此ABI已经为此用途保留。即使没有适当的语言支持,我们仍然可以通过定义通用宽指针类型并通过宏包装内置函数使这个示例更优雅。(Godbolt 示例) #define wide(T) struct wide_##T { typeof(T) *code; void *chain; } typedef int cb_f(int y); int baz(wide(cb_f) p, int x) { return CALL(p, (x)); } int foo(int k) { int bar(int x) { return k + x; } return baz(CLOSURE(cb_f, bar), 2 * k); } 编译器会快乐地将其优化为实现乘以三的单个汇编指令。 foo: lea eax, [rdi+rdi*2] ret 展望 虽然除了GCC之外,还有其他支持嵌套函数的编译器,但该功能并不真正可移植。在未来,我将讨论如何在clang和GCC中使用嵌套函数,以及如何通过一种黑客方式,甚至在旧版本的GCC上避免创建跳板。 文献 GCC,GCC 16版本系列的变化、新功能和修复 GCC,嵌套函数 GCC,构造函数调用

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡