inline關鍵字僅僅是對編譯器的建議,編譯器有權力決定一個函數是否在調用處嵌入。因為內聯函數要在調用處展開,編譯器必須能在每一個調用處能看到該函數的定義,因此最好將函數實現放在頭文件中(而且實現在類定義中的成員函數即便不加inline關鍵字也會自動成為內聯函數)。在實現文件中該函數之前要加上inline關鍵字的方式是有問題的:如果調用的obj文件在函數定義之前生成,那么該處就無法嵌入內聯函數了。如果普通函數需要成為內聯函數,在定義時加上inline關鍵字。
- 包含了遞歸、循環等結構的函數一般不會被內聯。
- 虛擬函數一般不會內聯,但是如果編譯器能在編譯時確定具體的調用函數,那么仍然會就地展開該函數。
- 如果通過函數指針調用內聯函數,那么該函數將不會內聯而是通過call進行調用。
- 構造和析構函數一般會生成大量代碼,因此一般也不適合內聯。
- 如果內聯函數調用了其他函數也不會被內聯。
如果想要阻止某函數被內聯,可以在函數體前加上 __attribute__((noinline)) 。
有時函數是否原地展開與編譯時指定的優先級有關,下面就是一個例子:
inline int foo(int x) {
return x+42;
}
int main(int argc, char* argv[])
{
printf("%d\n", foo(42));
return 0;
}
使用如下命令行得到匯編代碼,g++ -S -Wall -DDEBUG -D_GNU_SOURCE -std=c++11 -I/usr/include test.cpp:
.section .text._Z3fooi,"axG",@progbits,_Z3fooi,comdat
.weak _Z3fooi
.type _Z3fooi, @function
_Z3fooi:
//rbp寄存器的值入棧,保存main函數的棧基地址
pushq %rbp
//將rsp寄存器的值寫入到rbp寄存器,將main函數的棧頂指針賦予foo函數的棧底指針
movq %rsp, %rbp
//此時edi寄存器中的值是42,放入foo棧底向上4byte處
movl %edi, -4(%rbp)
//將42放入eax寄存器
movl -4(%rbp), %eax
//立即數21與eax寄存器中的值(42)相加
addl $21, %eax
//恢復main函數的棧底
popq %rbp
//返回
ret
.size _Z3fooi, .-_Z3fooi
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
//rbp寄存器的值入棧,保存_start函數的棧基地址
pushq %rbp
//將rsp寄存器的值寫入到rbp寄存器,將_start函數的棧頂指針賦予main函數的棧底指針
movq %rsp, %rbp
//rsp寄存器的值減去立即數16,即main函數的棧大小為16
subq $16, %rsp
//將edi寄存器中的值放入rbp寄存器中的值減4的位置(棧底向上4byte)
movl %edi, -4(%rbp)
//將rsi寄存器中的值放入rbp寄存器中的值減16的位置(棧底向上16byte)
movq %rsi, -16(%rbp)
//立即數42放入edi寄存器
movl $42, %edi
//調用函數foo
call _Z3fooi
//eax寄存器的值(63)放入esi寄存器
movl %eax, %esi
//LC0段地址放入edi寄存器
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
ret
.size main, .-main
使用如下命令行生成匯編代碼:g++ -S -O -Wall -DDEBUG -D_GNU_SOURCE -std=c++11 -I/usr/include test.cpp(增加了一個優化選項)
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d\n"
.globl main
.type main, @function
main:
subq $8, %rsp
movl $63, %esi
movl $.LC0, %edi
call printf
movl $0, %eax
addq $8, %rsp
ret
.size main, .-main
可見foo函數並未被調用,而是直接將立即數63放入esi寄存器作為printf函數的參數。