如果编译器可以自行内联函数,为什么内联函数必须是 "defined" in a header
Why must an inline function be "defined" in a header if the compiler can just inline functions on its own accord
我们必须在 header 中定义内联函数的原因是调用该函数的每个编译单元都必须具有完整的定义才能替换调用或替换它。我的问题是,如果编译器可以并且确实进行了自己的内联优化,为什么我们被迫将定义放在 header 文件中,这将需要它深入研究定义函数的 cpp 文件。
换句话说,编译器在我看来有能力在header文件中看到函数"declaration",转到相应的cpp文件并从中提取定义并粘贴它在另一个 cpp 的适当位置。既然如此,为什么非要在header中定义函数,暗示好像编译器不能"see"到其他cpp文件中。
MSDN 关于 Ob2/ 优化设置的说法:
Ob2/ The default value. Allows expansion of functions marked as inline, __inline, or __forceinline, and any other function that the compiler chooses (My emphasis).
不,编译器传统上不能这样做。在经典模型中,编译器'sees' 一次只能处理一个cpp 文件,不能转到任何其他cpp 文件。在这个 cpp 文件编译器中,所谓的 platofirm 本机格式的目标文件,比 linked 有效地使用 linker 从 1970 年代开始,它像锤子一样愚蠢。
这个模型正在慢慢演变。随着越来越有效的 link 时间优化 (LTO),link 用户开始了解什么是 cpp 代码,并且可以执行他们自己的内联。然而,即使使用 link 时间优化模型,编译器完成的内联和优化仍然比 link 时间更有效 - 当 cpp 代码转换为适合的中间格式时,很多重要的上下文丢失linking.
inline 关键字不仅仅是在它被调用的地方扩展实现,实际上主要是声明一个函数的多个定义可能存在于给定的翻译单元中。
这个在之前的其他问题中已经讲过了,是不是比我解释的好多了:)
Why are class member functions inlined?
Is "inline" implicit in C++ member functions defined in class definition
如果编译器已经看到函数的定义,则编译器可以更容易地展开内联函数。让编译器在每个使用该函数的翻译单元中看到函数定义的最简单方法是将定义放在 header 和 #include
中,即 header 函数所在的位置用过的。当你这样做时,你必须将定义标记为 inline
以便编译器(实际上是链接器)不会抱怨在多个翻译单元中看到该函数的定义。
我们被迫在头文件中提供内联函数定义的原因(或者至少,在给定编译单元中内联函数时,以实现可见的某种形式)是 C++ 标准的要求.
但是,该标准并没有特意阻止实现(例如工具链或其部分,例如预处理器、编译器本身、链接器等)做一些更聪明的事情。
一些特定的实现做得更聪明一些,因此即使在编译器不可见的情况下也可以实际内联函数。例如,在基本的 "compile all the source files then link" 工具链中,智能链接器可能意识到函数很小且只调用了几次,并选择(实际上)内联它,即使内联发生的点不可见给编译器(例如,因为调用函数的语句在单独的编译单元中,函数本身在另一个编译单元中)所以编译器不会进行内联。
事实是,标准并没有阻止实现这样做。它只是陈述了所有实现行为的最低要求集。
从本质上讲,编译器对要内联的函数具有可见性的要求是标准的最低要求。如果以这种方式编写程序(例如,所有要内联的函数都在其头文件中定义),则标准保证它将适用于每个(符合标准的)实现。
但这对我们更智能的工具链意味着什么?更智能的工具链必须从格式良好的程序中产生正确的结果——包括在使用这些函数的每个编译单元中定义内联函数的程序。我们的工具链被允许做更聪明的事情(例如在编译单元之间窥视)但是,如果代码是以需要这种更智能的行为的方式编写的(例如编译器在编译单元之间窥视)代码可能会被另一个工具链拒绝。
最后,每个 C++ 实现(工具链、标准库等)都需要符合 C++ 标准的要求。反之则不然——一个实现可能比标准要求的更聪明,但这并不要求其他实现以兼容的方式做事。
从技术上讲,内联不限于编译器的功能。它可能发生在编译器或链接器中。它也可能发生在 运行 时间 - 例如 "Just In Time" 技术实际上可以在 运行 几次后重组可执行代码,以提高后续性能 [这通常发生在虚拟机环境中,它允许这种技术的好处,同时避免与自修改可执行文件相关的问题。
我们必须在 header 中定义内联函数的原因是调用该函数的每个编译单元都必须具有完整的定义才能替换调用或替换它。我的问题是,如果编译器可以并且确实进行了自己的内联优化,为什么我们被迫将定义放在 header 文件中,这将需要它深入研究定义函数的 cpp 文件。
换句话说,编译器在我看来有能力在header文件中看到函数"declaration",转到相应的cpp文件并从中提取定义并粘贴它在另一个 cpp 的适当位置。既然如此,为什么非要在header中定义函数,暗示好像编译器不能"see"到其他cpp文件中。
MSDN 关于 Ob2/ 优化设置的说法:
Ob2/ The default value. Allows expansion of functions marked as inline, __inline, or __forceinline, and any other function that the compiler chooses (My emphasis).
不,编译器传统上不能这样做。在经典模型中,编译器'sees' 一次只能处理一个cpp 文件,不能转到任何其他cpp 文件。在这个 cpp 文件编译器中,所谓的 platofirm 本机格式的目标文件,比 linked 有效地使用 linker 从 1970 年代开始,它像锤子一样愚蠢。
这个模型正在慢慢演变。随着越来越有效的 link 时间优化 (LTO),link 用户开始了解什么是 cpp 代码,并且可以执行他们自己的内联。然而,即使使用 link 时间优化模型,编译器完成的内联和优化仍然比 link 时间更有效 - 当 cpp 代码转换为适合的中间格式时,很多重要的上下文丢失linking.
inline 关键字不仅仅是在它被调用的地方扩展实现,实际上主要是声明一个函数的多个定义可能存在于给定的翻译单元中。
这个在之前的其他问题中已经讲过了,是不是比我解释的好多了:)
Why are class member functions inlined?
Is "inline" implicit in C++ member functions defined in class definition
如果编译器已经看到函数的定义,则编译器可以更容易地展开内联函数。让编译器在每个使用该函数的翻译单元中看到函数定义的最简单方法是将定义放在 header 和 #include
中,即 header 函数所在的位置用过的。当你这样做时,你必须将定义标记为 inline
以便编译器(实际上是链接器)不会抱怨在多个翻译单元中看到该函数的定义。
我们被迫在头文件中提供内联函数定义的原因(或者至少,在给定编译单元中内联函数时,以实现可见的某种形式)是 C++ 标准的要求.
但是,该标准并没有特意阻止实现(例如工具链或其部分,例如预处理器、编译器本身、链接器等)做一些更聪明的事情。
一些特定的实现做得更聪明一些,因此即使在编译器不可见的情况下也可以实际内联函数。例如,在基本的 "compile all the source files then link" 工具链中,智能链接器可能意识到函数很小且只调用了几次,并选择(实际上)内联它,即使内联发生的点不可见给编译器(例如,因为调用函数的语句在单独的编译单元中,函数本身在另一个编译单元中)所以编译器不会进行内联。
事实是,标准并没有阻止实现这样做。它只是陈述了所有实现行为的最低要求集。
从本质上讲,编译器对要内联的函数具有可见性的要求是标准的最低要求。如果以这种方式编写程序(例如,所有要内联的函数都在其头文件中定义),则标准保证它将适用于每个(符合标准的)实现。
但这对我们更智能的工具链意味着什么?更智能的工具链必须从格式良好的程序中产生正确的结果——包括在使用这些函数的每个编译单元中定义内联函数的程序。我们的工具链被允许做更聪明的事情(例如在编译单元之间窥视)但是,如果代码是以需要这种更智能的行为的方式编写的(例如编译器在编译单元之间窥视)代码可能会被另一个工具链拒绝。
最后,每个 C++ 实现(工具链、标准库等)都需要符合 C++ 标准的要求。反之则不然——一个实现可能比标准要求的更聪明,但这并不要求其他实现以兼容的方式做事。
从技术上讲,内联不限于编译器的功能。它可能发生在编译器或链接器中。它也可能发生在 运行 时间 - 例如 "Just In Time" 技术实际上可以在 运行 几次后重组可执行代码,以提高后续性能 [这通常发生在虚拟机环境中,它允许这种技术的好处,同时避免与自修改可执行文件相关的问题。