首页 > 解决方案 > 为什么编译器认为 GCC 中嵌套函数(GNU 扩展)的地址“不是常量”?

问题描述

GNU C 编译器包含一个很好的 C 语言扩展,称为嵌套函数。但是,文档在某些方面尚不清楚。例如,它说

可以通过存储其地址或将地址传递给另一个函数来从其名称范围之外调用嵌套函数 [...]

如果您在包含函数退出后尝试通过其地址调用嵌套函数,那么一切都会崩溃。

如果您尝试在包含范围级别退出后调用它,并且如果它引用了一些不再在范围内的变量,那么您可能很幸运,但冒险并不明智。

但是,如果嵌套函数没有引用超出范围的任何内容,那么您应该是安全的。

所以一方面,它说如果你在包含函数退出后调用嵌套函数,“所有的地狱都会崩溃”,但前面几句话它说在某些情况下这样做是可以的。

我想“超出范围的事情”是指自动变量,因此特别是重构应该是安全的

static int f() { ... }

int outer() {
    ... // some use of f
}

进入

int outer() {
    int f() { ... } // f does not refer to outer's local variables
    ... // some use of f
}

f如果模块中除了函数中没有其他用途outer,即使假设outer函数以某种方式泄漏了f超出其自身范围的地址。

但是,我惊讶地发现以下代码无法编译

int main(void) {
    void nested_function(void) {}

    static const struct { void (*function_pointer)(void); } s = {
        .function_pointer = nested_function
    };

    return 0;
}

与投诉initializer element is not constant(即nested_function)。

这种限制有什么理由吗?(我无法想象函数的地址是非常量的,即使它是嵌套的)

标签: cgccgnunested-functionstatic-initialization

解决方案


在当前的 GCC 实现中,嵌套函数的不必要的静态链指针仅在优化期间被省略。它不会反映在类型系统中(与不绑定任何内容的 C++ lambda 不同)。如果没有优化,编译器必须在堆栈上创建一个蹦床,所以在这种情况下函数地址实际上是非常量的。


推荐阅读