我是一名C / C ++开发人员,这里有几个让我感到困惑的问题。
由于
答案 0 :(得分:41)
正如之前的答案中所建议的那样,使用inline
关键字可以通过内联函数调用来加快代码速度,通常以增加可执行文件为代价。 “内联函数调用”只是意味着在相应地填充参数后,用函数的实际代码将调用替换为目标函数。
但是,当设置为高优化时,现代编译器非常擅长自动内联函数调用,而无需用户提示。实际上,编译器通常更好来确定对速度增益的内联调用是什么。
为了获得性能而明确声明函数inline
(几乎?)总是不必要的!
此外,编译器可以并 忽略 inline
请求(如果适合)。如果对函数的调用不可能内联(即使用非平凡的递归或函数指针),而且如果函数太大而无法获得有意义的性能增益,编译器也会这样做。
但是,使用inline
关键字has other effects声明内联函数,并且实际上可能必需以满足一个定义规则(ODR):C ++中的此规则标准规定给定符号可以多次声明但只能定义一次。如果链接编辑器(=链接器)遇到几个相同的符号定义,则会生成错误。
此问题的一个解决方案是通过声明static
来确保编译单元不通过给出内部链接来导出给定符号。
但是,标记函数inline
通常会更好。这告诉链接器将编译单元中此函数的所有定义合并为一个定义,一个地址和共享函数静态变量。
例如,请考虑以下程序:
// header.hpp
#ifndef HEADER_HPP
#define HEADER_HPP
#include <cmath>
#include <numeric>
#include <vector>
using vec = std::vector<double>;
/*inline*/ double mean(vec const& sample) {
return std::accumulate(begin(sample), end(sample), 0.0) / sample.size();
}
#endif // !defined(HEADER_HPP)
// test.cpp
#include "header.hpp"
#include <iostream>
#include <iomanip>
void print_mean(vec const& sample) {
std::cout << "Sample with x̂ = " << mean(sample) << '\n';
}
// main.cpp
#include "header.hpp"
void print_mean(vec const&); // Forward declaration.
int main() {
vec x{4, 3, 5, 4, 5, 5, 6, 3, 8, 6, 8, 3, 1, 7};
print_mean(x);
}
请注意,.cpp
个文件都包含头文件,因此包含mean
的函数定义。虽然文件是使用包含防止双重包含的保护来保存的,但这将导致相同函数的两个定义,尽管在不同的编译单元中。
现在,如果您尝试链接这两个编译单元 - 例如使用以下命令:
⟩⟩⟩ g++ -std=c++11 -pedantic main.cpp test.cpp
你会收到一条错误,上面写着“重复符号__Z4meanRKNSt3__16vectorIdNS_9allocatorIdEEEE”(这是我们函数mean
的{{3}})。
但是,如果您在函数定义前面取消注释inline
修饰符,则代码会正确编译和链接。
功能模板是一种特殊情况:它们始终内联,无论它们是否以这种方式声明。这并不意味着编译器会将调用内联到它们,但它们不会违反ODR。对于在类或结构中定义的成员函数也是如此。
答案 1 :(得分:37)
- “常规”代码和内联代码之间有很大区别吗?
是和否。不,因为内联函数或方法具有与常规函数或方法完全相同的特性,最重要的一个是它们都是类型安全的。是的,因为编译器生成的汇编代码会有所不同;使用常规函数,每个调用将被转换为几个步骤:在堆栈上推送参数,跳转到函数,弹出参数等,而对内联函数的调用将被其实际代码替换,如宏。
- 内联代码只是宏的“形式”吗?
否!宏是简单的文本替换,可能导致严重的错误。请考虑以下代码:
#define unsafe(i) ( (i) >= 0 ? (i) : -(i) )
[...]
unsafe(x++); // x is incremented twice!
unsafe(f()); // f() is called twice!
[...]
使用内联函数,您可以确保在实际执行函数之前评估参数。它们也将被类型检查,并最终转换为匹配形式参数类型。
- 选择内联代码时必须做出哪些权衡?
通常,使用内联函数时程序执行应该更快,但使用更大的二进制代码。有关详细信息,请阅读GoTW#33。
答案 2 :(得分:16)
内联代码本质上就像宏一样,但它是真正的实际代码,可以进行优化。非常小的函数通常适用于内联,因为与该方法所做的少量实际工作相比,设置函数调用所需的工作(将参数加载到适当的寄存器中)是昂贵的。使用内联,不需要设置函数调用,因为代码直接“粘贴”到任何使用它的方法中。
内联增加代码大小,这是它的主要缺点。如果代码太大而无法容纳到CPU缓存中,则可能会出现严重的减速。在极少数情况下,您只需要担心这种情况,因为在很多地方您不太可能使用方法,增加的代码会导致问题。
总而言之,内联非常适合加速多次调用的小方法,但不是太多的地方(不过100个地方仍然没问题 - 你需要进入极端的例子才能获得任何重要的代码膨胀)。
编辑:正如其他人所指出的那样,内联只是对编译器的建议。它可以自由地忽略你,如果它认为你正在制作愚蠢的请求,如内联一个巨大的25行方法。
答案 3 :(得分:7)
是 - 内联代码不涉及函数调用,并将寄存器变量保存到堆栈中。它每次被“调用”时都会使用程序空间。总的来说,执行起来需要的时间更少,因为处理器中没有分支,节省了状态,清除了缓存等等。
宏和内联代码有相似之处。最大的区别在于内联代码是专门格式化的函数,因此编译器和未来的维护者有更多的选择。具体来说,如果你告诉编译器优化代码空间,或者未来的维护者最终扩展它并在代码中的许多地方使用它,它很容易变成一个函数。
选择内联代码时必须做出哪些权衡?
应该注意的是,保存和跳转到函数的寄存器会占用代码空间,因此对于非常小的函数,内联可以占用的空间少于函数。
- 亚当
答案 4 :(得分:2)
这取决于编译器......
假设你有一个愚蠢的编译器。通过指示必须内联的函数,它将在每次调用时放置函数内容的副本。
优点:没有函数调用开销(放置参数,推送当前PC,跳转到函数等)。例如,在大循环的中心部分可能很重要。
不便:膨胀生成的二进制文件。
这是一个宏吗?不是真的,因为编译器仍会检查参数的类型等。
智能编译器怎么样?如果他们“感觉”函数太复杂/太大,他们可以忽略内联指令。也许他们可以自动内联一些简单的函数,比如简单的getter / setter。
答案 5 :(得分:2)
Inline与宏的不同之处在于它是对编译器的暗示(编译器可能决定不内联代码!)并且宏是编译之前的源代码文本生成,因此被“强制”内联。
答案 6 :(得分:1)
标记函数内联意味着编译器具有选项以包含在“in-line”中,如果编译器选择这样做的话相比之下,宏始终就地扩展。内联函数将设置适当的调试符号,以允许符号调试器跟踪源的来源,而调试宏则令人困惑。内联函数需要是有效的函数,而宏是......好吧,不要。
决定声明内联函数主要是空间权衡 - 如果编译器决定内联它,你的程序会更大(特别是如果它也不是静态的,在这种情况下至少需要一个非内联副本)供任何外部物品使用);实际上,如果函数很大,这可能会导致性能下降,因为较少的代码适合缓存。然而,一般的性能提升只是你摆脱了函数调用本身的开销;对于一个被称为内循环一部分的小函数来说,这是一种有意义的权衡。
如果您信任您的编译器,请自由地标记内部循环中使用的小函数inline
;在决定是否内联时,编译器将负责做正确的事。
答案 7 :(得分:0)
如果您在f.e中将代码标记为内联。 C ++你也告诉编译器代码应该内联执行,即。代码块将“或多或少”插入调用它的位置(从而消除堆栈上的推送,弹出和跳跃)。所以,是的......如果函数适合这种行为,建议使用。
答案 8 :(得分:0)
“inline”就像2000年代的“注册”。不要打扰,编译器可以更好地决定优化什么。
答案 9 :(得分:0)
通过内联,编译器在调用点插入函数的实现。 你正在做的是删除函数调用开销。 但是,无法保证您的所有内联候选者实际上都会被编译器内联。但是,对于较小的函数,编译器总是内联的。 因此,如果你有一个多次调用的函数但只有有限数量的代码 - 几行 - 你可以从内联中受益,因为函数调用开销可能需要比函数本身执行更长的时间。
一个很好的内联候选者的典型例子是简单具体类的getter。
CPoint
{
public:
inline int x() const { return m_x ; }
inline int y() const { return m_y ; }
private:
int m_x ;
int m_y ;
};
某些编译器(例如VC2005)具有强制内联选项,使用该选项时无需指定“inline”关键字。
答案 10 :(得分:0)
我不会重申上述内容,但值得注意的是,虚拟函数不会被内联,因为调用的函数在运行时被解析。
答案 11 :(得分:0)
通常在优化级别3(在GCC情况下为-O3)启用内联。在某些情况下(如果可能的话),它可以显着提高速度。
程序中的显式内联可以通过增加代码大小的成本来提高速度。
您应该看到哪个适合:代码大小或速度,并决定是否应将其包含在您的程序中。
你可以打开优化的第3级而忘记它,让编译器完成他的工作。
答案 12 :(得分:0)
你内心的答案应该归结为速度。 如果你在一个紧密的循环中调用一个函数,并且它不是一个超级巨大的函数,而是在CALLING函数中浪费了很多时间,然后使该函数内联并且你会得到很多爆炸你的降压。
答案 13 :(得分:0)
首先,内联是一个请求编译器内联函数。所以编译器可以内联或不内联。
答案 14 :(得分:0)
如果代码运行缓慢,请让您的分析器找到麻烦点并进行处理。
我已经停止向头文件添加内联函数,它会增加耦合,但回报很少。
答案 15 :(得分:0)
内联代码更快。无需执行函数调用(每个函数调用都需要一些时间)。缺点是你无法将指针传递给内联函数,因为函数并不真正作为函数存在,因此没有指针。此函数也无法导出到公共(例如,库中的内联函数在链接到库的二进制文件中不可用)。另一个是你的二进制文件中的代码部分会增长,如果你从不同的地方调用函数(因为每次生成函数的副本而不是只有一个副本并且总是跳到那里)
通常您不必手动决定是否应该内联函数。例如。 GCC将根据优化级别(-Ox)自动决定并根据其他参数。它将考虑诸如“功能有多大?”之类的事情。 (指令数量),在代码中调用的频率,通过内联函数调整二进制文件的大小,以及其他一些指标。例如。如果一个函数是静态的(因此不会导出)并且只在代码中调用一次并且你从不使用指向该函数的指针,那么GCC决定自动内联它的可能性很大,因为它不会产生负面影响(二进制文件)只通过一次内联就不会变大。