为什么C标准不支持嵌套函数?

时间:2009-08-28 16:25:43

标签: c function standards

在装配中实现它似乎不太难。

gcc还有一个标志(-fnested-functions)来启用它们。

9 个答案:

答案 0 :(得分:11)

事实证明,它们实际上并不是那么容易实现。

内部函数是否可以访问包含范围的变量? 如果没有,嵌套就没有意义了;只需将其设置为静态(以限制其所在的翻译单元的可见性)并添加注释“这是仅由myfunc()使用的辅助函数”。

如果你想要访问包含范围的变量,你基本上强迫它生成闭包(另一种方法是限制你可以用嵌套函数做什么来使它们变得无用)。 我认为GCC实际上通过为包含函数的每次调用生成(在运行时)一个唯一的thunk来处理这个问题,该函数设置一个上下文指针,然后调用嵌套函数。这最终是一个相当Icky hack,以及一些完全合理的实现无法做到的事情(例如,在禁止执行可写内存的系统上 - 许多现代操作系统出于安全原因这样做)。 使其一般工作的唯一合理方法是强制所有函数指针携带隐藏的上下文参数,并且所有函数都接受它(因为在一般情况下,你不知道何时调用它是否是闭包或一个未公开的功能)。出于技术和文化方面的原因,这在C语言中是不合适的,因此我们坚持使用显式上下文指针来伪造闭包而不是嵌套函数,或者使用具有所需基础结构的更高级语言。做得好。

答案 1 :(得分:10)

我想来自BDFL(Guido van Rossum)的quote something

  

这是因为嵌套函数定义无法访问   周围块的局部变量 - 仅对于全局变量   包含模块。这样做是为了查找全局变量   必须走一串字典 - 如C中的,只有两个   嵌套作用域:locals和globals(除此之外,内置函数)。   因此,嵌套函数的用途有限。这是一个   基于语言允许的经验,深思熟虑的决定   像Pascal和两个Algols这样的嵌套嵌套 - 代码也是如此   许多嵌套作用域与具有太多GOTO的代码一样可读。

重点是我的。

我相信他指的是Python中的嵌套作用域(正如David在评论中指出的那样,这是从1993年开始,Python现在支持完全嵌套的函数) - 但我认为该语句仍适用。

它的另一部分可能是closures

如果您有类似C-like代码的功能:

(*int()) foo() {
    int x = 5;
    int bar() {
        x = x + 1;
        return x;
    }
    return &bar;
}

如果在某种回调中使用bar,x会发生什么?这在许多较新的高级语言中都有明确的定义,但是AFAIK没有明确定义的方法来跟踪C中的x - bar每次都返回6,或者连续调用bar返回递增值?这可能会为C的相对简单的定义增加一个全新的复杂层。

答案 2 :(得分:5)

有关潜在问题,请参阅C FAQ 20.24the GCC manual

  

如果您尝试调用嵌套函数   通过它的地址后   包含函数已退出,全部   地狱会破碎。如果你试着   在包含范围级别之后调用它   退出了,如果它指的是一些   不再存在的变量   范围,你可能很幸运,但事实并非如此   明智地承担风险。但是,如果   嵌套函数没有引用   任何超出范围的东西,   你应该安全。

这并不比C标准的其他一些有问题的部分严重,所以我说原因主要是历史性的(C99并不是 与K& RC功能不同 - 明智)。

在某些情况下,具有词法范围的嵌套函数可能会有用(考虑一个递归的内部函数,它不需要额外的堆栈空间用于外部作用域中的变量而不需要静态变量),但希望你能信任编译器正确内联这些函数,即具有单独函数的解决方案将更加冗长。

答案 3 :(得分:5)

嵌套函数是一件非常微妙的事情。你会让他们闭嘴吗?如果没有,那么它们对常规函数没有好处,因为它们不能访问任何局部变量。如果他们这样做,那么你如何处理堆栈分配的变量呢?你必须将它们放在其他地方,以便以后调用嵌套函数时,变量仍然存在。这意味着它们将占用内存,因此您必须在堆上为它们分配空间。没有GC,这意味着程序员现在负责清理功能。等等...... C#做到这一点,但他们有一个GC,它是一种比C更新的语言。

答案 4 :(得分:3)

将成员函数添加到结构中也不会太难,但它们也不在标准中。

无论是否易于实现,功能都不会添加到基于soley的C标准中。它是许多其他因素的组合,包括标准编写的时间点和当时/实际的标准。

答案 5 :(得分:2)

ANSI C已成立20年。也许在1983年到1989年之间,委员会可能已经根据当时的编译技术状况对其进行了讨论,但如果他们这样做,他们的推理就会在昏暗和遥远的过去中消失。

答案 6 :(得分:2)

还有一个原因:嵌套函数很有价值并不清楚。二十多年前,我曾经在(VAX)Pascal中进行大规模的编程和维护。我们有很多旧代码,它们大量使用嵌套函数。起初,我认为这很酷(与之前一直在工作的K& R C相比)并开始自己做。过了一会儿,我觉得这是一场灾难,然后停了下来。

问题是函数可以在范围内有很多很好的变量,计算嵌套它的所有函数的变量。 (有些旧代码有十级嵌套;五代很常见,直到我改变主意,我自己编写了一些后者。)嵌套堆栈中的变量可以有相同的名称,这样“内部”函数局部变量可以在更多“外部”函数中屏蔽同名变量。函数的局部变量,在C语言中完全私有,可以通过调用嵌套函数来修改。这种爵士乐的可能组合几乎是无限的,这是读取代码时理解的噩梦。

所以,我开始调用这个编程构造“半全局变量”而不是“嵌套函数”,并告诉其他人在编写代码时,唯一比全局变量更糟的是半全局变量,请不要再创造了。如果可以的话,我会用语言禁止它。遗憾的是,编译器没有这样的选择......

答案 7 :(得分:1)

我不同意Dave Vandervies。

定义嵌套函数比在全局范围内定义嵌套函数要好得多,使其成为静态函数并添加注释“这是一个仅由myfunc()使用的辅助函数”。

如果您需要辅助函数来辅助函数怎么办?你会添加注释“这是仅由myfunc使用的第一个辅助函数的辅助函数”吗?在没有完全污染命名空间的情况下,您从哪里获取所有这些函数所需的名称?

编写代码会有多混乱?

但是,当然,如何处理闭包存在问题,即返回一个指向函数的指针,该函数可以访问返回它的函数中定义的变量。

答案 8 :(得分:0)

您不允许在包含的函数中引用包含函数的局部变量,并且嵌套只是一个没有太多用处的作用域功能,或者您这样做。如果这样做,它不是一个如此简单的功能:您必须能够在访问正确的数据时从另一个调用嵌套函数,并且还必须考虑递归调用。这并非不可能 - 技术众所周知,并且在设计C时已经掌握得很好(Algol 60已经具有该功能)。但它使运行时组织和编译器变得复杂,并且阻止了对汇编语言的简单映射(函数指针必须传递有关该函数的信息;还有一些替代方法,例如使用gcc)。它超出了系统实现语言C的设计范围。