在阅读代码的过程中,我们有时候会碰到使用 inline 修饰的函数。这是C99 新增的内联函数(inline function)。这种内联函数相当于给编译器一个建议,告诉它将函数的代码插入到调用的地方。编译器可以选择忽略内联函数的建议,继续将函数编译为常规函数。
简单的说,对于常规函数编译器会将它解释成压栈,然后 call 函数名这样的指令;对于内联函数,编译器可以直接在调用处“展开”。这样就避免了压栈和 call 的开销。这样的设计让人很容易联想起来宏定义。
以一个例子来进行说明:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
inline void swapINT1( UINTN *p1, UINTN *p2 ) // 一个内联函数
{
UINTN tmp = *p1; *p1 = *p2; *p2 = tmp;
}
void swapINT2( UINTN *p1, UINTN *p2 ) // 一个内联函数
{
UINTN tmp = *p1; *p1 = *p2; *p2 = tmp;
}
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
UINTN a=12,b=34;
swapINT1(&a,&b);
swapINT2(&a,&b);
return(0);
}
代码非常简单,定义了一个内联函数 swapINT1() 和一个普通函数swapINT2()。我们在编译种加入 /FAcs 让它生成汇编文件。
ShellAppMain PROC ; COMDAT
; 36 : {
$LN5:
00000 48 89 54 24 10 mov QWORD PTR [rsp+16], rdx
00005 48 89 4c 24 08 mov QWORD PTR [rsp+8], rcx
0000a 48 83 ec 48 sub rsp, 72 ; 00000048H
; 37 : UINTN a=12,b=34;
0000e 48 c7 44 24 28
0c 00 00 00 mov QWORD PTR a$[rsp], 12
00017 48 c7 44 24 20
22 00 00 00 mov QWORD PTR b$[rsp], 34 ; 00000022H
; 14 : UINTN tmp = *p1; *p1 = *p2; *p2 = tmp;
00020 48 8b 44 24 28 mov rax, QWORD PTR a$[rsp]
00025 48 89 44 24 30 mov QWORD PTR tmp$1[rsp], rax
0002a 48 8b 44 24 20 mov rax, QWORD PTR b$[rsp]
0002f 48 89 44 24 28 mov QWORD PTR a$[rsp], rax
00034 48 8b 44 24 30 mov rax, QWORD PTR tmp$1[rsp]
00039 48 89 44 24 20 mov QWORD PTR b$[rsp], rax
; 38 : swapINT1(&a,&b);
; 39 : swapINT2(&a,&b);
0003e 48 8d 54 24 20 lea rdx, QWORD PTR b$[rsp]
00043 48 8d 4c 24 28 lea rcx, QWORD PTR a$[rsp]
00048 e8 00 00 00 00 call swapINT2
; 40 :
; 41 : return(0);
0004d 33 c0 xor eax, eax
; 42 : }
0004f 48 83 c4 48 add rsp, 72 ; 00000048H
00053 c3 ret 0
ShellAppMain ENDP
从上面可以看到 swapINT1() 被“展开”了,swapINT2() 则是正常的通过 call 来调用的。
需要注意的是:内联函数对于编译器来说只是“建议”,编译器完全可以不接受这个建议。例如,r如果函数比较复杂,编译器不会遵从这个建议。
参考: