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

发布于 2024-09-03 16:20:06 字数 1431 浏览 6 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

乖乖 2024-09-10 16:20:06

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

一个小故事可以说明这一点 - 我曾经在英国理工学院工作,该公司主要使用 DEC 盒子 - 特别是 DEC-10 和一些 VAXen。所有工程学院的教师都在他们的代码中使用了 FORTRAN 的许多 DEC 扩展 - 他们确信我们将永远保留为 DEC 商店。然后我们用 IBM 大型机替换了 DEC-10,其 FORTRAN 编译器不支持任何扩展。我可以告诉你,那天有很多哀号声和切齿声。我自己的 FORTRAN 代码(一个 8080 模拟器)在几个小时内就移植到了 IBM(几乎全部时间都花在学习如何驱动 IBM 编译器上),因为我是用沼泽标准的 FORTRAN-77 编写的。

Nested functions really don't do anything that you can't do with non-nested ones (which is why neither C nor C++ provide them). You say you are not interested in other compilers - well this may be atrue at this moment, but who knows what the future will bring? I would avoid them, along with all other GCC "enhancements".

A small story to illustrate this - I used to work for a UK Polytechinc which mostly used DEC boxes - specifically a DEC-10 and some VAXen. All the engineering faculty used the many DEC extensions to FORTRAN in their code - they were certain that we would remain a DEC shop forever. And then we replaced the DEC-10 with an IBM mainframe, the FORTRAN compiler of which didn't support any of the extensions. There was much wailing and gnashing of teeth on that day, I can tell you. My own FORTRAN code (an 8080 simulator) ported over to the IBM in a couple of hours (almost all taken up with learning how to drive the IBM compiler), because I had written it in bog-standard FORTRAN-77.

陌路终见情 2024-09-10 16:20:06

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

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

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

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

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

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

当包含函数存储指向被包含函数的指针时,以及允许多层嵌套时,会出现内部复杂情况,但编译器编写者半个多世纪以来一直在处理这些问题。不存在使添加到 C++ 比添加到 C 更困难的技术问题,但好处较少。

可移植性很重要,但 gcc 可在许多环境中使用,并且至少还有其他系列编译器支持嵌套函数 - IBM 的 xlc 可在 AIX 上使用、Linux on PowerPC、Linux on BlueGene、Linux on 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 函数,它们实际上是匿名嵌套函数。因此,编程界对它们有很多赞成和反对的经验。

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

Like most programming techniques, nested functions should be used when and only when they are appropriate.

You aren't forced to use this aspect, but if you want, nested functions reduce the need to pass parameters by directly accessing their containing function's local variables. That's convenient. Careful use of "invisible" parameters can improve readability. Careless use can make code much more opaque.

Avoiding some or all parameters makes it harder to reuse a nested function elsewhere because any new containing function would have to declare those same variables. Reuse is usually good, but many functions will never be reused so it often doesn't matter.

Since a variable's type is inherited along with its name, reusing nested functions can give you inexpensive polymorphism, like a limited and primitive version of templates.

Using nested functions also introduces the danger of bugs if a function unintentionally accesses or changes one of its container's variables. Imagine a for loop containing a call to a nested function containing a for loop using the same index without a local declaration. If I were designing a language, I would include nested functions but require an "inherit x" or "inherit const x" declaration to make it more obvious what's happening and to avoid unintended inheritance and modification.

There are several other uses, but maybe the most important thing nested functions do is allow internal helper functions that are not visible externally, an extension to C's and C++'s static not extern functions or to C++'s private not public functions. Having two levels of encapsulation is better than one. It also allows local overloading of function names, so you don't need long names describing what type each one works on.

There are internal complications when a containing function stores a pointer to a contained function, and when multiple levels of nesting are allowed, but compiler writers have been dealing with those issues for over half a century. There are no technical issues making it harder to add to C++ than to C, but the benefits are less.

Portability is important, but gcc is available in many environments, and at least one other family of compilers supports nested functions - IBM's xlc available on AIX, Linux on PowerPC, Linux on BlueGene, Linux on Cell, and z/OS. See
http://publib.boulder.ibm.com/infocenter/comphelp/v8v101index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fnested_functions.htm

Nested functions are available in some new (eg, Python) and many more traditional languages, including Ada, Pascal, Fortran, PL/I, PL/IX, Algol and COBOL. C++ even has two restricted versions - methods in a local class can access its containing function's static (but not auto) variables, and methods in any class can access static class data members and methods. The upcoming C++ standard has lamda functions, which are really anonymous nested functions. So the programming world has lots of experience pro and con with them.

Nested functions are useful but take care. Always use any features and tools where they help, not where they hurt.

梦明 2024-09-10 16:20:06

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

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

话虽这么说,我会避免使用任何特定于编译器的功能,除非直接的好处超过了因必须以其他方式重写代码而可能导致的任何未来成本。

There are times nested functions can be useful, particularly with algorithms that shuffle around lots of variables. Something like a written-out 4-way merge sort could need to keep a lot of local variables, and have a number of pieces of repeated code which use many of them. Calling those bits of repeated code as an outside helper routine would require passing a large number of parameters and/or having the helper routine access them through another level of pointer indirection.

Under such circumstances, I could imagine that nested routines might allow for more efficient program execution than other means of writing the code, at least if the compiler optimizes for the situation where there any recursion that exists is done via re-calling the outermost function; inline functions, space permitting, might be better on non-cached CPUs, but the more compact code offered by having separate routines might be helpful. If inner functions cannot call themselves or each other recursively, they can share a stack frame with the outer function and would thus be able to access its variables without the time penalty of an extra pointer dereference.

All that being said, I would avoid using any compiler-specific features except in circumstances where the immediate benefit outweighs any future cost that might result from having to rewrite the code some other way.

荒岛晴空 2024-09-10 16:20:06

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

另请记住,g++ 不实现嵌套函数,因此如果您需要获取其中一些代码并将其转储到 C++ 程序中,则需要删除它们。

As you said, they are a bad thing in the sense that they are not part of the C standard, and as such are not implemented by many (any?) other C compilers.

Also keep in mind that g++ does not implement nested functions, so you will need to remove them if you ever need to take some of that code and dump it into a C++ program.

姐不稀罕 2024-09-10 16:20:06

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

  • 使用 GCC 和嵌套函数

    使用

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

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

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

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

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

Nested functions can be bad, because under specific conditions the NX (no-execute) security bit will be disabled. Those conditions are:

  • GCC and nested functions are used

  • a pointer to the nested function is used

  • the nested function accesses variables from the parent function

  • the architecture offers NX (no-execute) bit protection, for instance 64-bit linux.

When the above conditions are met, GCC will create a trampoline https://gcc.gnu.org/onlinedocs/gccint/Trampolines.html. To support trampolines, the stack will be marked executable. see: https://www.win.tue.nl/~aeb/linux/hh/protection.html

Disabling the NX security bit creates several security issues, with the notable one being buffer overrun protection is disabled. Specifically, if an attacker placed some code on the stack (say as part of a user settable image, array or string), and a buffer overrun occurred, then the attackers code could be executed.

孤单情人 2024-09-10 16:20:06

更新

我投票删除我自己的帖子,因为它不正确。具体来说,编译器必须插入一个蹦床函数才能利用嵌套函数,因此堆栈空间中的任何节省都会丢失。

如果有编译器大师想要纠正我,请这样做!

原始答案:

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

嵌套函数确实不会做任何你不能做的事情
非嵌套的。

具体来说:

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

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

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

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

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

update

I'm voting to delete my own post because it's incorrect. Specifically, the compiler must insert a trampoline function to take advantage of the nested functions, so any savings in stack space are lost.

If some compiler guru wants to correct me, please do so!

original answer:

Late to the party, but I disagree with the accepted answer's assertion that

Nested functions really don't do anything that you can't do with
non-nested ones.

Specifically:

TL;DR: Nested Functions Can Reduce Stack Usage in Embedded Environments

Nested functions give you access to lexically scoped variables as "local" variables without needing to push them onto the call stack. This can be really useful when working on a system with limited resource, e.g. embedded systems. Consider this contrived example:

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

Note that once you're inside do_something(), because of nested functions, the calls to times2() and times4() don't need to push any parameters onto the stack, just return addresses (and smart compilers even optimize them out when possible).

Imagine if there was a lot of state that the internal functions needed to access. Without nested functions, all that state would have to be passed on the stack to each of the functions. Nested functions let you access the state like local variables.

国粹 2024-09-10 16:20:06

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

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

但是 GCC 是一个免费的(就像在语音中)编译器,它有一些区别......并且一些 GCC 扩展往往成为事实上的标准并由其他编译器实现。

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

I agree with Stefan's example, and the only time I used nested functions (and then I am declaring them inline) is in a similar occasion.

I would also suggest that you should rarely use nested inline functions rarely, and the few times you use them you should have (in your mind and in some comment) a strategy to get rid of them (perhaps even implement it with conditional #ifdef __GCC__ compilation).

But GCC being a free (like in speech) compiler, it makes some difference... And some GCC extensions tend to become de facto standards and are implemented by other compilers.

Another GCC extension I think is very useful is the computed goto, i.e. label as values. When coding automatons or bytecode interpreters it is very handy.

甜警司 2024-09-10 16:20:06

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

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

因此,如果您看到可以通过使用嵌套函数使程序更清晰的地方,您必须问自己“我是否针对可移植性或可读性进行了优化”。

Nested functions can be used to make a program easier to read and understand, by cutting down on the amount of explicit parameter passing without introducing lots of global state.

On the other hand, they're not portable to other compilers. (Note compilers, not devices. There aren't many places where gcc doesn't run).

So if you see a place where you can make your program clearer by using a nested function, you have to ask yourself 'Am I optimising for portability or readability'.

可遇━不可求 2024-09-10 16:20:06

我只是在探索嵌套函数的不同用途。作为 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 能够优化为单个“调用”,但我猜这对于非平凡的函数调用等不起作用。在这种情况下,可以使用“缓存”,但这会增加不可读性。

I'm just exploring a bit different kind of use of nested functions. As an approach for 'lazy evaluation' in C.

Imagine such code:

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();
} 

versus

void funcs()
{
  bool b0() { return code0; }
  bool b1() { return code1; }

  if      (b0()) do_something0();
  else if (b1()) do_something1();
}

This way you get clarity (well, it might be a little confusing when you see such code for the first time) while code is still executed when and only if needed.
At the same time it's pretty simple to convert it back to original version.

One problem arises here if same 'value' is used multiple times. GCC was able to optimize to single 'call' when all the values are known at compile time, but I guess that wouldn't work for non trivial function calls or so. In this case 'caching' could be used, but this adds to non readability.

脸赞 2024-09-10 16:20:06

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

我有照顾各种硬件设备的对象。它们是通过指针作为参数传递给成员函数的结构,而不是像 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 不能具有全局作用域,因为存在多个实例化,并且这些实例化可能同时运行。由于访问是通过 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 可以由具有指针的对象初始化,然后从任何地方调用。但并不理想。

I need nested functions to allow me to use utility code outside an object.

I have objects which look after various hardware devices. They are structures which are passed by pointer as parameters to member functions, rather as happens automagically in c++.

So I might have

    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 doesn't and cannot know about ThisDevice, which has multiple instantiations. But all it needs is a means of reading and writing, and a size. ThisDevice->read( ... ) and ThisDevice->Write( ... ) need the pointer to a ThisDeviceType to obtain info about how to read and write the block memory (Bram) of this particular instantiation. The pointer, pdev, cannot have global scobe, since multiple instantiations exist, and these might run concurrently. Since access occurs across an FPGA interface, it is not a simple question of passing an address, and varies from device to device.

The GenericTestBram code is a utility function:

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

The test code, therefore, need be written only once and need not be aware of the details of the structure of the calling device.

Even wih GCC, however, you cannot do this. The problem is the out of scope pointer, the very problem needed to be solved. The only way I know of to make f(x, ... ) implicitly aware of its parent is to pass a parameter with a value out of range:

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

Function f can be initialised by something which has the pointer, then be called from anywhere. Not ideal though.

┈┾☆殇 2024-09-10 16:20:06

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

没有它们,函数的实际意义就无法使用。

这称为词法作用域。

Nested functions are a MUST-HAVE in any serious programming language.

Without them, the actual sense of functions isn't usable.

It's called lexical scoping.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文