C++憤恨者札記1——類對象作為函數參數的數據傳遞過程
C++繁雜的機制,加上枯燥的教科書,再加上無法回避地要使用它,注定要造就一批C++憤恨者。本文作為C++憤恨者札記系列第一篇,從匯編角度,觀察類對象作為函數參數時的數據傳遞過程。
若沒有特殊說明,編譯器使用的是VC++,反匯編使用的是Windbg.下面是它們的版本號:
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86
Microsoft (R) Windows Debugger Version 6.11.0001.404 X86
測試代碼如下:
[cpp]
class Node
{
public:
Node(){}
//Node(Node& n);
int data1;
int data2;
int data3;
int data4;
int data5;
int data6;
int data7;
};
//Node::Node(Node &n)
//{
//}
void Fn( int a, Node n, int b )
{
n.data1 = 100;
n.data2 = 100;
a = 100;
b = 10;
}
void main()
{
Node n;
Fn(1, n, 2);
}
--------------------------------------------------
未使用拷貝構造函數時,調用Fn的反匯編代碼:
[plain]
00fa1421 6a02 push 2 ;第三個參數入棧
00fa1423 83ec1c sub esp,1Ch ;為Node n分配棧內存, 注意,構造函數Node(),並沒調用
00fa1426 b907000000 mov ecx,7 ;rep循環次數
00fa142b 8d75e0 lea esi,[ebp-20h] ;Node n地址
00fa142e 8bfc mov edi,esp ;棧空間地址
00fa1430 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] ;把n內容拷貝到棧空間上
;A5 MOVS m32, m32 Move doubleword
;at address DS:(E)SI to address ES:(E)DI
00fa1432 6a01 push 1 ;第一個參數入棧
00fa1434 e8a2fdffff call hello!ILT+470(?FnYAXHVNodeHZ) (00fa11db)
00fa1439 83c424 add esp,24h ;恢復棧平衡,4+1CH+4=24H
類對象參數位於棧上,是通過sub esp size來分配的。數據是通過內存拷貝來初始化。
--------------------------------------------------
使用拷貝構造函數時,即上面代碼把注釋去掉,調用Fn的反匯編代碼:
[plain]
01002406 6a02 push 2 ;第三個參數入棧
01002408 83ec1c sub esp,1Ch ;開辟棧空間
0100240b 8bcc mov ecx,esp ;棧內存首址保存在ecx中,拷貝構造函數的this指針
0100240d 8d45e0 lea eax,[ebp-20h] ;實參地址
01002410 50 push eax ;作為拷貝構造函數的參數
01002411 e8d4edffff call hello!ILT+485(??0NodeQAEAAV0Z) (010011ea) ;拷貝構造函數,替換了rep movs內存拷貝
01002416 6a01 push 1 ;第一個參數入棧
01002418 e8beedffff call hello!ILT+470(?FnYAXHVNodeHZ) (010011db)
0100241d 83c424 add esp,24h ;恢復棧平衡
類參數仍然位於棧上,也是通過sub esp size來分配的。數據是通過拷貝構造函數初始化的,C++的機制就是繁多--||。
--------------------------------------------------
下面是Fn的反匯編結果,它可不管Node n是怎麼初始化的,只要把它在棧上的位置找到就OK啦。
[plain]
hello!Fn:
00a41a60 55 push ebp | old ebp | ebp
00a41a61 8bec mov ebp,esp |-------------|
| ret address | ebp+4
00a41a63 81ecc0000000 sub esp,0C0h |-------------|
| int a | ebp+8
00a41a69 53 push ebx |-------------|
00a41a6a 56 push esi | Node n | ebp+0CH
00a41a6b 57 push edi |-------------|
| int b | ebp+28H
00a41a6c 8dbd40ffffff lea edi,[ebp-0C0h]
00a41a72 b930000000 mov ecx,30h
00a41a77 b8cccccccc mov eax,0CCCCCCCCh
00a41a7c f3ab rep stos dword ptr es:[edi] ;以是為局部變量空間初始化,debug版特有的
00a41a7e c7450c64000000 mov dword ptr [ebp+0Ch],64h ;n.data1 = 100; 顯示ebp+0Ch是參數n的起始地址
00a41a85 c7451064000000 mov dword ptr [ebp+10h],64h ;n.data2 = 100;
00a41a8c c7450864000000 mov dword ptr [ebp+8],64h ;a = 100;
00a41a93 c745280a000000 mov dword ptr [ebp+28h],0Ah ;b = 10;
00a41a9a 5f pop edi
00a41a9b 5e pop esi
00a41a9c 5b pop ebx
00a41a9d 8be5 mov esp,ebp
00a41a9f 5d pop ebp
00a41aa0 c3 ret
--------------------------------------------------
總結:
類對象做為函數參數時,是被存放在棧上的,不影響實參的數據。
若未重寫拷貝構造函數,類的其它構造函數將不會被調用。形參的數據是通過內存拷貝傳遞的。若重寫了,拷貝構造函數將會在初始化形參時被調用,不再進行內存拷貝工作。
作者:tms_li