代码之家  ›  专栏  ›  技术社区  ›  LB40

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

  •  20
  • LB40  · 技术社区  · 14 年前

    我知道嵌套函数不是标准C的一部分,但是因为它们存在于gcc中(gcc是我唯一关心的编译器),所以我经常使用它们。

    这是件坏事吗?如果是这样,你能给我举些讨厌的例子吗? gcc中嵌套函数的状态是什么?他们会被移走吗?

    11 回复  |  直到 6 年前
        1
  •  17
  •   John Zwinck    13 年前

    嵌套函数真的不做任何你不能用非嵌套函数来做的事情(这就是为什么C和C++都不提供它们)。你说你对其他的编译器不感兴趣-好吧,这可能是现在的问题,但谁知道未来会带来什么呢?我会避开它们,以及所有其他GCC的“增强”。

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

        2
  •  7
  •   supercat    14 年前

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

    在这种情况下,我可以想象嵌套的例程可能比其他编写代码的方法更有效地执行程序,至少如果编译器通过重新调用最外层的函数来优化存在的任何递归的情况;在非CA上,允许空间的内联函数可能更好。但是有单独的例程提供的更紧凑的代码可能会有所帮助。如果内部函数不能递归地调用它们自己或彼此,那么它们可以与外部函数共享一个堆栈框架,从而能够访问其变量,而不需要额外的指针取消引用的时间惩罚。

    尽管如此,我还是会避免使用任何特定于编译器的特性,除非在这样的情况下,立即的好处超过了将来不得不以其他方式重写代码所带来的成本。

        3
  •  6
  •   Ian McIntosh    13 年前

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

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

    由于任何新的包含函数都必须声明这些相同的变量,因此避免某些或所有参数会使在其他地方重用嵌套函数变得更加困难。重用通常是好的,但许多函数永远不会被重用,所以它通常并不重要。

    由于变量的类型是随名称一起继承的,因此重用嵌套函数可以为您提供廉价的多态性,就像模板的有限原始版本一样。

    如果函数无意中访问或更改了其容器的某个变量,使用嵌套函数也会带来错误的危险。假设一个for循环包含对一个嵌套函数的调用,该函数包含一个for循环,使用相同的索引,而不使用局部声明。如果我正在设计一种语言,我将包含嵌套函数,但需要一个“inherit x”或“inherit const x”声明,以使发生的事情更加明显,并避免意外的继承和修改。

    还有几个其他用途,但是嵌套函数最重要的是允许外部不可见的内部帮助函数,扩展到C和C++的静态非外部函数或C++的私有函数而不是公共函数。两级封装比一级封装好。它还允许局部重载函数名,因此不需要长名称来描述每个函数名所使用的类型。

    当一个包含函数存储一个指向包含函数的指针时,以及当允许多个层次的嵌套时,会有内部的复杂问题,但是编译器编写者已经处理这些问题超过半个世纪了。没有比C更难添加到C++的技术问题,但是好处更少。

    可移植性很重要,但是GCC在许多环境中都可用,并且至少有一个其他编译器系列支持嵌套函数——IBM的XLC在AIX上可用,Linux在PowerPC上可用,Linux在BlueGene上,Linux在Cell上可用,以及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函数,这些函数实际上是匿名嵌套函数。因此,编程界有很多经验支持和反对他们。

    嵌套函数很有用,但要小心。总是在有帮助的地方使用任何功能和工具,而不是在受伤的地方。

        4
  •  5
  •   Justin Ethier    14 年前

    正如您所说,从某种意义上说,它们不是C标准的一部分,因此并没有被许多(任何?)实现,这是一件坏事。其他C编译器。

    还要记住,G++不实现嵌套函数,所以如果需要将其中一些代码转储到C++程序中,则需要删除它们。

        5
  •  3
  •   EmbeddedDoctor    9 年前

    嵌套函数可能不好,因为在特定条件下,将禁用nx(no execute)安全位。这些条件是:

    • 使用gcc和嵌套函数

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

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

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

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

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

        6
  •  3
  •   fearless_fool    7 年前

    迟到了,但我不同意公认的回答

    嵌套函数实际上不做任何您不能做的事情 非嵌套的。

    明确地:

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

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

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

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

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

        7
  •  2
  •   Basile Starynkevitch    13 年前

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

    我还建议您很少使用嵌套的内联函数,并且在很少使用它们的情况下(在您的头脑中和一些注释中),您应该有一个消除它们的策略(甚至可以使用条件实现它) #ifdef __GCC__ 汇编)。

    但是GCC是一个自由的(像在语言中)编译器,它有一些不同…一些GCC扩展往往成为事实上的标准,并由其他编译器实现。

    我认为另一个gcc扩展非常有用的是计算goto,即 label as values . 当编码自动机或字节码解释器时,它非常方便。

        8
  •  1
  •   John Lawrence Aspden    13 年前

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

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

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

        9
  •  0
  •   Stefan    13 年前

    我只是在探索嵌套函数的另一种用法。作为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();
    }
    

    通过这种方式,您可以获得清晰的信息(当您第一次看到这样的代码时,可能会有点困惑),而代码仍然在需要时执行。 同时,将其转换回原始版本非常简单。

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

        10
  •  0
  •   Geoff    6 年前

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

    我有处理各种硬件设备的对象。它们是由指针传递给成员函数的结构,而不是在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不知道也不知道这个设备,它有多个实例。但它所需要的只是一种阅读和写作的方式,以及一种尺寸。此设备->读取(…)而这个设备->写(…)需要指向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可以由具有指针的对象初始化,然后从任何地方调用。但不理想。

        11
  •  -3
  •   Herr P.    12 年前

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

    没有它们,实际的功能感就不可用。

    这叫做词法范围界定。