为什么定义在栈中的这个C函数指针在传递时无效?
Why is this C function pointer defined in the stack not valid when passed?
我试图将在父块范围内定义的函数指针传递给另一个函数。我在不同的环境中同时遇到工作和段错误。 (我不是C专家)
代码:
#include <stdio.h>
#include <stdbool.h>
void test_function(bool (*function_pointer) (int x)) {
printf("addr passed function_pointer %p\n", function_pointer);
if (function_pointer(100)) {
printf(" run: true\n");
} else {
printf(" run: false\n");
}
}
bool function_outside_main(int x) {
return x < 0;
}
int main(void) {
// run with function defined globally
printf("addr function_outside_main %p\n", function_outside_main);
test_function(function_outside_main);
// run with function defined in this stack block
bool function_inside_main(int x) {
return x > 0;
}
printf("addr function_inside_main %p\n", function_inside_main);
test_function(function_inside_main); // shouldn't the address be valid?
}
在 GCC 版本 5.4.0
的 Ubuntu 16.04.4 上(在 Amazon EC2 上)它与输出一起工作:
addr function_outside_main 0x400620
addr passed function_pointer 0x400620
run: false
addr function_inside_main 0x7ffc018d5690
addr passed function_pointer 0x7ffc018d5690
run: true
在 GCC 版本 9.3.0
的 Ubuntu 20.04.1 上(在 Windows WSL 下)它因段错误而失败:
addr function_outside_main 0x7ff19c8631dd
addr passed function_pointer 0x7ff19c8631dd
run: false
addr function_inside_main 0x7ffffc033b20
addr passed function_pointer 0x7ffffc033b20
zsh: segmentation fault (core dumped) ./a.out
使用这个 gcc 扩展,你不能在定义它的函数范围之外调用它。这是一个未定义的行为。非常类似于解引用函数范围外的指针引用的局部自动变量
我无权访问 WSL(或 9.3 gcc),但我确实注意到一个警告,即内部函数在其中定义的函数返回后可能不会被调用。通常这对于被延迟的函数来说会很麻烦,但我怀疑你的编译器已经用函数结尾折叠了最终语句( test_function(function_inside_main); // shouldn't the address be valid?
);那么你能不能在该语句之后添加一行:printf("Hello, world\n");
,看看它是否神奇地修复了它?
这就是扩展的危险,也就是 定义自己的 C 类语言。
像这样的嵌套函数是 gcc 扩展,不是 C 标准的一部分。
gcc 使用的实现通常1 涉及为嵌套函数创建一个on-stack thunk,因此调用它需要可执行堆栈支持。 Linux(和 Windows)的较新版本默认为 non-executable 堆栈,因此会崩溃。
要使此工作正常,您可以使用 gcc 的 -z execstack
选项,或者您可以使用 execstack
工具修改二进制文件以在创建后指定可执行堆栈。
1在某些带有 -O 的 gcc 版本中,它可以确定何时嵌套函数实际上不需要嵌套(它们从不引用包含范围), 而不是在这些情况下使用 thunk
我试图将在父块范围内定义的函数指针传递给另一个函数。我在不同的环境中同时遇到工作和段错误。 (我不是C专家)
代码:
#include <stdio.h>
#include <stdbool.h>
void test_function(bool (*function_pointer) (int x)) {
printf("addr passed function_pointer %p\n", function_pointer);
if (function_pointer(100)) {
printf(" run: true\n");
} else {
printf(" run: false\n");
}
}
bool function_outside_main(int x) {
return x < 0;
}
int main(void) {
// run with function defined globally
printf("addr function_outside_main %p\n", function_outside_main);
test_function(function_outside_main);
// run with function defined in this stack block
bool function_inside_main(int x) {
return x > 0;
}
printf("addr function_inside_main %p\n", function_inside_main);
test_function(function_inside_main); // shouldn't the address be valid?
}
在 GCC 版本 5.4.0
的 Ubuntu 16.04.4 上(在 Amazon EC2 上)它与输出一起工作:
addr function_outside_main 0x400620
addr passed function_pointer 0x400620
run: false
addr function_inside_main 0x7ffc018d5690
addr passed function_pointer 0x7ffc018d5690
run: true
在 GCC 版本 9.3.0
的 Ubuntu 20.04.1 上(在 Windows WSL 下)它因段错误而失败:
addr function_outside_main 0x7ff19c8631dd
addr passed function_pointer 0x7ff19c8631dd
run: false
addr function_inside_main 0x7ffffc033b20
addr passed function_pointer 0x7ffffc033b20
zsh: segmentation fault (core dumped) ./a.out
使用这个 gcc 扩展,你不能在定义它的函数范围之外调用它。这是一个未定义的行为。非常类似于解引用函数范围外的指针引用的局部自动变量
我无权访问 WSL(或 9.3 gcc),但我确实注意到一个警告,即内部函数在其中定义的函数返回后可能不会被调用。通常这对于被延迟的函数来说会很麻烦,但我怀疑你的编译器已经用函数结尾折叠了最终语句( test_function(function_inside_main); // shouldn't the address be valid?
);那么你能不能在该语句之后添加一行:printf("Hello, world\n");
,看看它是否神奇地修复了它?
这就是扩展的危险,也就是 定义自己的 C 类语言。
像这样的嵌套函数是 gcc 扩展,不是 C 标准的一部分。
gcc 使用的实现通常1 涉及为嵌套函数创建一个on-stack thunk,因此调用它需要可执行堆栈支持。 Linux(和 Windows)的较新版本默认为 non-executable 堆栈,因此会崩溃。
要使此工作正常,您可以使用 gcc 的 -z execstack
选项,或者您可以使用 execstack
工具修改二进制文件以在创建后指定可执行堆栈。
1在某些带有 -O 的 gcc 版本中,它可以确定何时嵌套函数实际上不需要嵌套(它们从不引用包含范围), 而不是在这些情况下使用 thunk