嵌套函数在gcc中是坏事吗?

我知道嵌套函数不是标准C的一部分,但由于它们存在于gcc中(并且事实上gcc是我唯一关心的编译器),我倾向于经常使用它们。

这是坏事吗 ? 如果是这样,你能告诉我一些令人讨厌的例子吗? gcc中嵌套函数的状态是什么? 他们会被删除吗?

嵌套函数实际上没有做任何你不能用非嵌套函数做的事情(这就是C和C ++都没有提供它们的原因)。 你说你对其他编译器不感兴趣 – 这一点可能是真的,但是谁知道未来会带来什么? 我会避免它们,以及所有其他GCC“增强function”。

一个小故事来说明这一点 – 我曾经为英国Polytechinc工作,主要使用DEC盒子 – 特别是DEC-10和一些VAXen。 所有工程师都在他们的代码中使用了FORTRAN的许多DEC扩展 – 他们确信我们将永远是DEC商店。 然后我们用一台IBM大型机取代了DEC-10,它的FORTRAN编译器不支持任何扩展。 我可以告诉你,那天有许多人哭泣和咬牙切齿。 我自己的FORTRAN代码(8080模拟器)在几个小时内移植到IBM(几乎全部用于学习如何驱动IBM编译器),因为我已经用沼泽标准FORTRAN-77编写了它。

有时嵌套函数可能很有用,特别是对于围绕大量变量进行混乱的算法。 像写出的4路合并排序之类的东西可能需要保留很多局部变量,并且有许多重复代码使用其中许多变量。 将这些重复代码作为外部辅助例程调用将需要传递大量参数和/或让辅助例程通过另一级别的指针间接访问它们。

在这种情况下,我可以想象嵌套例程可能比其他编写代码的方式允许更高效的程序执行,至少如果编译器优化了通过重新调用最外层函数来完成存在任何递归的情况; 对于非缓存的CPU,内联函数(空间允许)可能更好,但通过单独的例程提供的更紧凑的代码可能会有所帮助。 如果内部函数不能递归地调用自身或彼此,则它们可以与外部函数共享堆栈帧,因此能够访问其变量而不会额外指针取消引用的时间损失。

总而言之,我会避免使用任何特定于编译器的function,除非在直接利益超过可能由于必须以其他方式重写代码而导致的任何未来成本的情况下。

与大多数编程技术一样,嵌套函数应该在适当的时候使用。

您不必使用此方面,但如果需要,嵌套函数可以通过直接访问其包含函数的局部变量来减少传递参数的需要。 那很方便。 仔细使用“隐形”参数可以提高可读性。 粗心使用会使代码变得更加不透明。

避免某些或所有参数使得在其他地方重用嵌套函数变得更加困难,因为任何新的包含函数都必须声明那些相同的变量。 重复使用通常很好,但许多function永远不会被重复使用,因此通常无关紧要。

由于变量的类型与其名称一起inheritance,因此重用嵌套函数可以为您提供廉价的多态性,例如模板的有限和原始版本。

如果函数无意中访问或更改其容器的某个变量,则使用嵌套函数还会引入错误的危险。 想象一个for循环包含对包含for循环的嵌套函数的调用,该循环使用相同的索引而没有本地声明。 如果我正在设计一种语言,我会包含嵌套函数,但需要“inheritancex”或“inheritanceconst x”声明,以使其更明显地发生了什么并避免意外的inheritance和修改。

还有其他一些用途,但嵌套函数最重要的可能是允许外部不可见的内部辅助函数,C和C ++的静态非外部函数的扩展或C ++的私有非公共函数。 具有两个封装级别优于一个封装级别。 它还允许本地重载函数名称,因此您不需要长名称来描述每个函数的工作类型。

当包含函数存储指向包含函数的指针时,以及允许多层嵌套时,内部并发症,但编译器编写者已经处理了这些问题半个多世纪。 没有任何技术问题使得添加到C ++比添加到C更难,但好处更少。

可移植性很重要,但gcc在许多环境中都可用,并且至少另外一个编译器系列支持嵌套函数 – 在AIX上可用的IBM xlc,在PowerPC上运行Linux,在BlueGene上运行Linux,在Linux上运行Linux和z / OS。 请参阅http://publib.boulder.ibm.com/infocenter/comphelp/v8v101index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fnested_functions.htm

嵌套函数可用于一些新的(例如Python)和更多传统语言,包括Ada,Pascal,Fortran,PL / I,PL / IX,Algol和COBOL。 C ++甚至有两个受限制的版本 – 本地类中的方法可以访问其包含函数的静态(但不是自动)变量,并且任何类中的方法都可以访问静态类数据成员和方法。 即将推出的C ++标准具有lamda函数,它们实际上是匿名嵌套函数。 所以编程世界有很多经验和赞成。

嵌套函数很有用但要小心。 始终使用他们帮助的任何function和工具,而不是他们受伤的地方。

正如你所说的那样,它们不是C标准的一部分是一件坏事,因此很多(任何?)其他C编译器都没有实现它们。

另外请记住,g ++没有实现嵌套函数,因此如果您需要将某些代码转储到C ++程序中,则需要删除它们。

嵌套函数可能不好,因为在特定条件下,NX(无执行)安全位将被禁用。 那些条件是:

  • 使用GCC和嵌套函数

  • 使用指向嵌套函数的指针

  • 嵌套函数从父函数访问变量

  • 该架构提供NX(无执行)位保护,例如64位Linux。

当满足上述条件时,GCC将创建一个蹦床https://gcc.gnu.org/onlinedocs/gccint/Trampolines.html 。 为了支持trampolines,堆栈将被标记为可执行文件。 请参阅: https : //www.win.tue.nl/~aeb/linux/hh/protection.html

禁用NX安全位会产生一些安全问题,其中值得注意的是缓冲区溢出保护被禁用。 具体来说,如果攻击者在堆栈上放置了一些代码(例如作为用户可设置图像,数组或字符串的一部分),并且发生了缓冲区溢出,则可以执行攻击者代码。

迟到了,但我不同意接受的答案的断言

嵌套函数实际上不会对非嵌套函数做任何事情。

特别:

TL; DR:嵌套函数可以减少嵌入式环境中的堆栈使用

嵌套函数使您可以访问词法范围的变量作为“本地”变量,而无需将它们推送到调用堆栈。 在处理资源有限的系统(例如嵌入式系统)时,这非常有用。 考虑这个人为的例子:

void do_something(my_obj *obj) { double times2() { return obj->value * 2.0; } double times4() { return times2() * times2(); } ... } 

请注意,一旦你进入do_something(),由于嵌套函数,对times2()和times4()的调用不需要将任何参数推送到堆栈上,只需返回地址(而智能编译器甚至可以优化它们。可能)。

想象一下,如果有很多状态需要访问内部function。 没有嵌套函数,所有状态都必须在堆栈上传递给每个函数。 嵌套函数允许您像局部变量一样访问状态。

我同意Stefan的例子,并且我唯一一次使用嵌套函数(然后我将它们声明为inline函数)也是在类似的场合。

我还建议您很少使用嵌套的内联函数,并且很少使用它们,您应该(在您的脑海中和某些注释中)有一个策略来摆脱它们(甚至可能用条件#ifdef __GCC__实现它)汇编)。

但GCC是一个免费(如语音)编译器,它会产生一些不同……而一些GCC扩展往往成为事实上的标准,并由其他编译器实现。

我认为另一个GCC扩展非常有用的是计算goto,即标签为值 。 编码自动机或字节码解释器时,它非常方便。

通过减少显式参数传递的数量而不引入大量全局状态,嵌套函数可用于使程序更易于阅读和理解。

另一方面,它们不能移植到其他编译器。 (注意编译器,而不是设备.Gcc没有运行的地方不多)。

因此,如果你看到一个可以通过使用嵌套函数使程序更清晰的地方,你必须问自己“我是否在优化便携性或可读性”。

我只是在探索一些不同类型的嵌套函数。 作为C中“懒惰评估”的一种方法。

想象一下这样的代码:

 void vars() { bool b0 = code0; // do something expensive or to ugly to put into if statement bool b1 = code1; if (b0) do_something0(); else if (b1) do_something1(); } 

 void funcs() { bool b0() { return code0; } bool b1() { return code1; } if (b0()) do_something0(); else if (b1()) do_something1(); } 

通过这种方式,您可以获得清晰度(当您第一次看到此类代码时可能会有点混乱),而代码仍在执行时以及仅在需要时执行。 同时将它转换回原始版本非常简单。

如果多次使用相同的“值”,则会出现一个问题。 当在编译时知道所有值时,GCC能够优化为单个“调用”,但我想这对于非平凡的函数调用是不行的。 在这种情况下,可以使用“缓存”,但这会增加不可读性。

我需要嵌套函数来允许我在对象外部使用实用程序代码。

我有照顾各种硬件设备的对象。 它们是由指针作为参数传递给成员函数的结构,而不是在c ++中自动发生的结构。

所以我可能会

  static int ThisDeviceTestBram( ThisDeviceType *pdev ) { int read( int addr ) { return( ThisDevice->read( pdev, addr ); } void write( int addr, int data ) ( ThisDevice->write( pdev, addr, data ); } GenericTestBram( read, write, pdev->BramSize( pdev ) ); } 

GenericTestBram不会也无法了解具有多个实例的ThisDevice。 但它所需要的只是一种阅读和书写方式,以及规模。 ThisDevice-> read(…)和ThisDevice-> Write(…)需要指向ThisDeviceType的指针,以获取有关如何读取和写入此特定实例的块存储器(Bram)的信息。 指针pdev不能具有全局scobe,因为存在多个实例,并且这些实例可以并发运行。 由于通过FPGA接口进行访问,因此传递地址并不是一个简单的问题,因设备而异。

GenericTestBram代码是一个实用函数:

  int GenericTestBram( int ( * read )( int addr ), void ( * write )( int addr, int data ), int size ) { // Do the test } 

因此,测试代码只需编写一次,无需了解调用设备结构的细节。

但是,即使是GCC,你也做不到这一点。 问题是超出范围指针,需要解决的问题。 我知道使f(x,…)隐式知道其父级的唯一方法是传递一个值超出范围的参数:

  static int f( int x ) { static ThisType *p = NULL; if ( x < 0 ) { p = ( ThisType* -x ); } else { return( p->field ); } } return( whatever ); 

函数f可以由具有指针的东西初始化,然后从任何地方调用。 虽然不太理想。

嵌套函数在任何严肃的编程语言中都是必须的。

没有它们,实际的function感是不可用的。

它被称为词汇范围。