C++中的this指針一直比較神秘。任何類的對象,都有一個this指針,無處不在。那么this指針的本質究竟是什么?this指針什么時候會被用到?今天通過幾段簡單的代碼,來揭秘一下。
要先揭秘this指針,先來說一下函數調用時參數的傳遞過程。考慮以下代碼:
int sum(int i, int j, int k)
{return i + j + k;
}int main()
{int a, b, c;a = 1;b = 2;c = 3; sum(a,b,c);return 0;
}
這是一段非常簡單的函數調用代碼。我們生成其匯編代碼(x86-64),如下所示:
sum(int, int, int):pushq %rbpmovq %rsp, %rbpmovl %edi, -4(%rbp)movl %esi, -8(%rbp)movl %edx, -12(%rbp)movl -4(%rbp), %edxmovl -8(%rbp), %eaxaddl %eax, %edxmovl -12(%rbp), %eaxaddl %edx, %eaxpopq %rbpret
main:pushq %rbpmovq %rsp, %rbpsubq $16, %rspmovl $1, -4(%rbp)movl $2, -8(%rbp)movl $3, -12(%rbp)movl -12(%rbp), %edxmovl -8(%rbp), %ecxmovl -4(%rbp), %eaxmovl %ecx, %esimovl %eax, %edicall sum(int, int, int)movl $0, %eaxleaveret
我們重點來關注一下函數參數的傳遞過程。通過分析main函數的匯編函數, 我用類似于C語言的偽代碼解釋了一下每一行的意思,輔助理解,如下所示。
main pushq %rbpmovq %rsp, %rbp //rbp = rspsubq $16, %rsp //rsp -= 16movl $1, -4(%rbp) //*(rbp-4) = 1movl $2, -8(%rbp) //*(rbp-8) = 2 movl $3, -12(%rbp) //*(rbp-12) = 3movl -12(%rbp), %edx //edx =*(rbp-12)movl -8(%rbp), %ecx //ecx =*(rbp-8) movl -4(%rbp), %eax //eax = *(rbp-4)movl %ecx, %esi //esi = ecx movl %eax, %edi //edi = eaxcall sum(int, int, int)movl $0, %eaxleaveret
在執行這條指令(call sum(int, int, int))前,main函數的棧空間分布如下:
即main函數會存儲三個變量: a, b, c. 同時會將其值分別賦值給edi, esi、edx寄存器。那么我們很好奇,將a, b, c三個變量的值賦值給edi, esi、edx寄存器會有什么用呢?我們先來看一下sum函數,我用類似于C語言的偽代碼解釋了一下每一行的意思,輔助理解,如下所示。
sum(int, int, int):pushq %rbpmovq %rsp, %rbp //rbp = rspmovl %edi, -4(%rbp) //*(rbp-4) = edimovl %esi, -8(%rbp) //*(rbp-8) = esimovl %edx, -12(%rbp) //*(rbp-12) = edxmovl -4(%rbp), %edx //edx = *(rbp-4)movl -8(%rbp), %eax //eax = *(rbp-8)addl %eax, %edx //edx += eax movl -12(%rbp), %eax //eax = *(rbp-12) addl %edx, %eax //eax += edx popq %rbpret
sum函數的棧空間分布如下:
?
我們重點關注一下這幾條指令:
?movl ? ?%edi, -4(%rbp) ? ? //*(rbp-4) = edi
?movl ? ?%esi, -8(%rbp) ? ? //*(rbp-8) = esi
?movl ? ?%edx, -12(%rbp) ?//*(rbp-12) = edx
可以看到,在sum函數的棧空間中,其會分配三個存儲單元,rbp-4, rbp-8, rbp-12存儲1,2,3。而1,2,3這三個值分別又是從edi,? esi、edx三個寄存器中拷貝過來的。而這三個寄存器的值又是來自main函數中a, b, c三個變量的賦值。也就是說,這里edi,? esi、edx三個寄存器,在函數調用時,完成了參數的傳遞。那么這種參數傳遞的現像是不是有什么約定呢??答案是有的!
在Linux/macOS 等 Unix-like系統中,函數的調用約定標準為System V AMD64 ABI,其參數傳遞機制:
參數位置 | 整數/指針寄存器 | 浮點寄存器 |
---|---|---|
第 1 個 | RDI | XMM0 |
第 2 個 | RSI | XMM1 |
第 3 個 | RDX | XMM2 |
第 4 個 | RCX | XMM3 |
第 5 個 | R8 | XMM4 |
第 6 個 | R9 | XMM5 |
第 7+ 個 | 棧(右→左) | XMM6-7 |
從這個約定中得知,在傳遞整數時,第一個參數用的是RDI寄存器,第二個參數用的是RSI寄存器,第三個參數用的是RDX寄存器。上面函數調用的例子中正好符合此調用約定(例子中用的是edi,? esi、edx三個寄存器傳遞第1,第2,第3個參數,而edi,? esi、edx正好是RDI、RSI、RDX三個寄存器的低32位)。
<this指針揭秘繼續...>
?
?
?