Stanislav Petrov Asked:2020-02-25 17:51:55 +0000 UTC2020-02-25 17:51:55 +0000 UTC 2020-02-25 17:51:55 +0000 UTC 为什么包含循环、递归或被指针调用的函数极易发生内联(inline) 772 为什么包含循环、递归或被指针调用的函数极易发生内联(inline) c++ 2 个回答 Voted Best Answer AnT stands with Russia 2020-02-26T01:14:30Z2020-02-26T01:14:30Z 这个问题是基于术语混淆。函数调用内联不是函数本身的属性,而始终是单个函数调用的属性。当然,函数本身的属性在决定是否内联特定调用方面发挥作用,但在一般情况下,这个决定仍然是根据具体情况做出的。一些函数调用可能是内联的,而对同一函数的其他调用可能不是内联的。甚至在特定调用中指定的函数参数也会影响是否内联该特定调用的决定。 没有内联循环函数调用的声明属于“很久以前并且不是真的”类别。 每个编译器都实现了一些启发式方法,使其能够就对给定函数的调用是否值得内联做出一般决定。在其中一个旧编译器中,这种启发式方法还包括检查函数体的循环(Borland 或类似的东西)。 事实上,循环并不代表嵌入的任何障碍。我不知道有任何现代实现拒绝内联带有循环的函数调用。 通过指针调用的函数没有内联的声明只是我在上面写的术语混淆的一个生动例子。通常不内联的是通过指针进行的调用。同时,直接调用同一个函数也可以内联。 为什么通常不可能内联通过指针进行的调用是显而易见的:因为在编译时并不清楚将调用哪个函数。但是,如果编译器能够在编译时确定将通过指针调用哪个函数,它也很容易内联这样的调用。 基于这种术语混淆的经典常见问题是内联类虚拟方法调用的问题。毕竟,它们被称为“通过指针”,因此不能内置,对吧?不对。首先,用户自己可以直接进行虚方法调用,无需使用虚机制。其次,在大量上下文中,编译器本身能够确定在给定调用中调用了哪个特定方法,并因此内联该调用。 递归函数调用也可以内联。如果编译器能够在编译时估计递归的最大深度,那么它可以将这些调用(“扩展递归”)内联到其全部深度。如果编译器无法进行这样的评估,那么它可以将递归调用内联到某个固定深度,然后执行常规递归调用。在这方面,内联递归函数调用很像展开循环。 嵌入函数调用的最简单和最明显的情况之一是,例如,已知某个函数在程序中只被调用一次的情况(在空间意义上,即程序源代码它的调用只包含一个地方)。没有理由让这样的函数作为一个单独的函数存在,不管这个函数有多“重”。(这里有许多附带警告,但它们与本主题无关。)例如,如果一个函数是内部绑定的并且在其翻译单元中只调用一次,那么现代编译器将内联该调用而不管任何其他标准。在 GCC 编译器中,一个选项对此负责,该选项-finline-functions-called-once已在 mode 中启用-O1。 作为另一个示例,MSVC++ 编译器具有控制递归函数的内联和递归扫描深度的选项#pragma。inline_recursioninline_depth Harry 2020-02-25T17:58:13Z2020-02-25T17:58:13Z 好吧,递归很清楚——如果它没有被编译器转换成循环,那么会发生什么?我们嵌入代码,在它调用的地方我们再次嵌入代码,在调用的地方......我们应该嵌入多少次代码?:) 通过指针调用 - 再次:如果是内置的,则该函数没有明确的序言 - 结尾,并且不能通过地址调用 - 所以当通过指针调用时,必须有一个非内置实例至少该功能 - 以便有一些东西可以调用。 在调用的地方,不能通过指针嵌入任何内容 - 因为编译器嵌入,并且在编译时指针的值是未知的。 关于周期-在我看来,您在这里混淆了一些东西。内部带有循环的函数是非常可嵌入的——当然,如果它有意义:) 如果你的意思是循环本身是未展开的——那么再次,这已经完成,但有其自身的限制——例如,编译器可能不会知道实际上将完成多少次迭代 - 那么他如何展开循环?..
这个问题是基于术语混淆。函数调用内联不是函数本身的属性,而始终是单个函数调用的属性。当然,函数本身的属性在决定是否内联特定调用方面发挥作用,但在一般情况下,这个决定仍然是根据具体情况做出的。一些函数调用可能是内联的,而对同一函数的其他调用可能不是内联的。甚至在特定调用中指定的函数参数也会影响是否内联该特定调用的决定。
没有内联循环函数调用的声明属于“很久以前并且不是真的”类别。
每个编译器都实现了一些启发式方法,使其能够就对给定函数的调用是否值得内联做出一般决定。在其中一个旧编译器中,这种启发式方法还包括检查函数体的循环(Borland 或类似的东西)。
事实上,循环并不代表嵌入的任何障碍。我不知道有任何现代实现拒绝内联带有循环的函数调用。
通过指针调用的函数没有内联的声明只是我在上面写的术语混淆的一个生动例子。通常不内联的是通过指针进行的调用。同时,直接调用同一个函数也可以内联。
为什么通常不可能内联通过指针进行的调用是显而易见的:因为在编译时并不清楚将调用哪个函数。但是,如果编译器能够在编译时确定将通过指针调用哪个函数,它也很容易内联这样的调用。
基于这种术语混淆的经典常见问题是内联类虚拟方法调用的问题。毕竟,它们被称为“通过指针”,因此不能内置,对吧?不对。首先,用户自己可以直接进行虚方法调用,无需使用虚机制。其次,在大量上下文中,编译器本身能够确定在给定调用中调用了哪个特定方法,并因此内联该调用。
递归函数调用也可以内联。如果编译器能够在编译时估计递归的最大深度,那么它可以将这些调用(“扩展递归”)内联到其全部深度。如果编译器无法进行这样的评估,那么它可以将递归调用内联到某个固定深度,然后执行常规递归调用。在这方面,内联递归函数调用很像展开循环。
嵌入函数调用的最简单和最明显的情况之一是,例如,已知某个函数在程序中只被调用一次的情况(在空间意义上,即程序源代码它的调用只包含一个地方)。没有理由让这样的函数作为一个单独的函数存在,不管这个函数有多“重”。(这里有许多附带警告,但它们与本主题无关。)例如,如果一个函数是内部绑定的并且在其翻译单元中只调用一次,那么现代编译器将内联该调用而不管任何其他标准。在 GCC 编译器中,一个选项对此负责,该选项
-finline-functions-called-once已在 mode 中启用-O1。作为另一个示例,MSVC++ 编译器具有控制递归函数的内联和递归扫描深度的选项
#pragma。inline_recursioninline_depth好吧,递归很清楚——如果它没有被编译器转换成循环,那么会发生什么?我们嵌入代码,在它调用的地方我们再次嵌入代码,在调用的地方......我们应该嵌入多少次代码?:)
通过指针调用 - 再次:如果是内置的,则该函数没有明确的序言 - 结尾,并且不能通过地址调用 - 所以当通过指针调用时,必须有一个非内置实例至少该功能 - 以便有一些东西可以调用。
在调用的地方,不能通过指针嵌入任何内容 - 因为编译器嵌入,并且在编译时指针的值是未知的。
关于周期-在我看来,您在这里混淆了一些东西。内部带有循环的函数是非常可嵌入的——当然,如果它有意义:) 如果你的意思是循环本身是未展开的——那么再次,这已经完成,但有其自身的限制——例如,编译器可能不会知道实际上将完成多少次迭代 - 那么他如何展开循环?..