inlineで修飾された関数を inline 関数と呼びます.コンパイル時にC++ コンパイラはinline 関数が呼び出される場所を展開します. 関数スタックのオーバーヘッドはありません. インライン関数はプログラムの操作の効率を向上させます.
注: インライン化は関数をインライン化として定義するだけであり、これはコンパイラにオプションを与えることと同じです.コンパイラがインライン化を使用するかどうかは、完全にコンパイラ自体次第です. 一般に、特定の要件を満たす関数のみがインライン展開を使用します
逆アセンブルの形式を見て、インライン関数と非インライン関数の基本的な実装を観察します. 次のように加算関数 Add(int int) を実装するとします。
int Add(int left, int right)
{
return left + right;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
return 0;
}
現時点では inline キーワードは追加されていません。g++ を使用してこのコードをコンパイルし、objdump を使用して逆アセンブリを表示し、アセンブリを次のように取得します。
0000000000400521 <main>:
400521: 55 push %rbp // %rbp入栈,保存上一个栈帧的栈底地址
400522: 48 89 e5 mov %rsp,%rbp // %rsp值给%rbp,%rbp保存当前栈帧栈底地址
400525: 48 83 ec 10 sub $0x10,%rsp // %rsp-0x10,为main函数开辟16个字节栈帧
400529: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) // 将10 保存在栈底-4地址处
400530: c7 45 f8 14 00 00 00 movl $0x14,-0x8(%rbp) // 将20 保存在占地-8位置处
400537: 8b 55 f8 mov -0x8(%rbp),%edx // 将20 保存到寄存器%edx内
40053a: 8b 45 fc mov -0x4(%rbp),%eax // 将10 保存到寄存器%eax内
40053d: 89 d6 mov %edx,%esi // 将20 传递到寄存器%esi内作为第二个参数
40053f: 89 c7 mov %eax,%edi // 将10 传递到寄存器%edi内作为第一个参数
400541: e8 c7 ff ff ff callq 40050d <_Z3Addii>// call函数Add实现函数调用
400546: 89 45 f4 mov %eax,-0xc(%rbp) // 将%eax中的结果(由Add返回)保存到栈上(ret)
400549: b8 00 00 00 00 mov $0x0,%eax // 将0传入%eax作为main函数返回值
40054e: c9 leaveq // 恢复%rsp和%rbp
40054f: c3 retq
000000000040050d <_Z3Addii>:
40050d: 55 push %rbp // 将main函数的栈底地址入栈
40050e: 48 89 e5 mov %rsp,%rbp // %rsp保存当前栈底
400511: 89 7d fc mov %edi,-0x4(%rbp) // 将参数一入栈
400514: 89 75 f8 mov %esi,-0x8(%rbp) // 将参数二入栈
400517: 8b 45 f8 mov -0x8(%rbp),%eax // 将参数二保存到寄存器%eax
40051a: 8b 55 fc mov -0x4(%rbp),%edx // 将参数一保存到寄存器%edx
40051d: 01 d0 add %edx,%eax // 完成 10 + 20,并将结果保存到%eax中
40051f: 5d pop %rbp // 从栈中弹出main函数栈底地址并保存到%rbp
400520: c3 retq // 从栈中弹出返回地址并返回main
通常の関数呼び出しでは、40050d にある <_Z3Addii> を call 命令で呼び出しますが、このとき、関数 Add のスタック フレームが開かれるため、ある程度のオーバーヘッドが発生します。
次に、Add 関数のインライン バージョンを見てください。g++ はデフォルトでインラインを使用しないため、いくつかの処理を行う必要があります。
inline int Add(int left, int right) __attribute__((always_inline));
inline int Add(int left, int right)
{
return left + right;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
return 0;
}
宣言の後に __attribute__((always_inline)) を追加して、g++ コンパイラにインラインの使用を強制します。
また、objdump を使用して以下のように逆アセンブルを取得すると、取得した逆アセンブルに <_Z3Addii> が存在しなくなります
000000000040064d <main>:
40064d: 55 push %rbp
40064e: 48 89 e5 mov %rsp,%rbp
400651: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) // 10 保存到栈上(rbp-4)a
400658: c7 45 f8 14 00 00 00 movl $0x14,-0x8(%rbp) // 20 保存到栈上(rbp-8)b
40065f: 8b 45 fc mov -0x4(%rbp),%eax // 10 保存到寄存器%eax内
400662: 89 45 f0 mov %eax,-0x10(%rbp) // 将10保存到栈上(rbp-16)left
400665: 8b 45 f8 mov -0x8(%rbp),%eax // 20 保存到寄存器%eax内
400668: 89 45 ec mov %eax,-0x14(%rbp) // 将20保存到栈上(rbp-20)right
40066b: 8b 45 ec mov -0x14(%rbp),%eax // 将20保存到寄存器%eax内
40066e: 8b 55 f0 mov -0x10(%rbp),%edx // 将10保存到寄存器%edx内
400671: 01 d0 add %edx,%eax // 10 + 20,将结果保存到%eax
400673: 89 45 f4 mov %eax,-0xc(%rbp) // 将结果保存到栈上(rbp-12)ret
400676: b8 00 00 00 00 mov $0x0,%eax // 将0传入%eax作为main函数返回值
40067b: 5d pop %rbp // 恢复%rbp
40067c: c3 retq
インライン関数を使用した後、元の関数呼び出しの操作は、現在の関数のスタック フレームで直接実行されます. 実際には、スタック上にパラメーター a と b のコピーが作成されます. 関数スタック フレーム全体が
- inline は時間とスペースを交換する方法です. コンパイラが関数をインライン関数として扱う場合, コンパイル段階で関数呼び出しを関数本体に置き換えます. 欠点: オブジェクトファイルが大きくなる可能性があります. 利点: 呼び出しが少ないオーバーヘッド、プログラム操作の効率を改善
- インラインはコンパイラへの単なる提案です. 異なるコンパイラは異なるインライン実装メカニズムを持っているかもしれません. 一般的な提案は: 関数を小さくすることです (つまり, 関数はあまり長くありません. 正確なステートメントはありません.再帰的で頻繁に呼び出される関数はインラインで変更しないでください。そうしないと、コンパイラはインライン機能を無視します
- インラインでは、宣言と定義の分離 (異なるファイル) は推奨されません。分離するとリンク エラーが発生します。インライン展開のため、関数アドレスがなく、リンクが見つからない
- 関数をインライン化するには、キーワードinline を関数定義本体と一緒に配置する必要があります。関数宣言の前に inline を配置するだけでは効果がありません
したがって、C 言語で使用されるマクロ関数については、代わりに C++ の inline を使用することをお勧めします。