有時我們會將對象作為參數(shù)傳入函數(shù),其中有不少所謂的“陷阱”牺勾,大部分都與對象中申請的內(nèi)存釋放有關青柄,到底是什么原因?qū)е碌倪@些問題呢?所謂的默認的拷貝構造函數(shù)又是什么呢映胁?它又做了怎么樣的工作木羹,編譯器又是怎么樣調(diào)用它的呢?讓我們一起走進C++的內(nèi)部世界
當類所占用的空間很小時解孙,比如只有幾個int數(shù)據(jù)成員坑填,編譯器會選擇直接將幾個數(shù)據(jù)成員做壓棧操作(先聲明的數(shù)據(jù)后壓棧,后聲明的數(shù)據(jù)先壓棧)弛姜,以供調(diào)用函數(shù)使用
測試代碼:
class UnitTest
{
public:
int m_One;
int m_Two;
};
void ShowUnitTest(UnitTest unit)
{
printf("%d %d %s\n", unit.m_One, unit.m_Two);
}
int main(int argc, char *argv[])
{
UnitTest test;
test.m_One = 1;
test.m_Two = 2;
ShowUnitTest(test);
system("pause");
return 0;
}
看其反匯編的代碼:
int main(int argc, char *argv[])
{
00BB1420 push ebp
00BB1421 mov ebp,esp
00BB1423 sub esp,0D0h
00BB1429 push ebx
00BB142A push esi
00BB142B push edi
00BB142C lea edi,[ebp-0D0h]
00BB1432 mov ecx,34h
00BB1437 mov eax,0CCCCCCCCh
00BB143C rep stos dword ptr es:[edi]
UnitTest test;
test.m_One = 1;
00BB143E mov dword ptr [test],1
test.m_Two = 2;
00BB1445 mov dword ptr [ebp-8],2
ShowUnitTest(test);
00BB144C mov eax,dword ptr [ebp-8]
00BB144F push eax
00BB1450 mov ecx,dword ptr [test]
00BB1453 push ecx
00BB1454 call ShowUnitTest (0BB11E5h)
可以看到00BB144C 的壓棧順序確實是先聲明后壓棧脐瑰,后聲明先壓棧,直接使用push解決問題廷臼。
再下面我們看一下類的體積較大時編譯器的處理過程
先看一段代碼:
class UnitTest
{
public:
int m_One;
int m_Two;
char m_sName[32];
};
void ShowUnitTest(UnitTest unit)
{
printf("%d %d %s\n", unit.m_One, unit.m_Two, unit.m_sName);
}
int main(int argc, char *argv[])
{
UnitTest test;
test.m_One = 1;
test.m_Two = 2;
char tmp[] = "name";
strncpy_s(test.m_sName, tmp, sizeof(tmp));
ShowUnitTest(test);
system("pause");
return 0;
}
反匯編代碼為:
int main(int argc, char *argv[])
{
00053D00 push ebp
00053D01 mov ebp,esp
00053D03 sub esp,104h
00053D09 push ebx
00053D0A push esi
00053D0B push edi
00053D0C lea edi,[ebp-104h]
00053D12 mov ecx,41h
00053D17 mov eax,0CCCCCCCCh
00053D1C rep stos dword ptr es:[edi]
00053D1E mov eax,dword ptr ds:[00058004h]
00053D23 xor eax,ebp
00053D25 mov dword ptr [ebp-4],eax
UnitTest test;
test.m_One = 1;
00053D28 mov dword ptr [test],1
test.m_Two = 2;
00053D2F mov dword ptr [ebp-2Ch],2
char tmp[] = "name";
00053D36 mov eax,dword ptr ds:[00055864h]
00053D3B mov dword ptr [tmp],eax
00053D3E mov cl,byte ptr ds:[55868h]
00053D44 mov byte ptr [ebp-3Ch],cl
strncpy_s(test.m_sName, tmp, sizeof(tmp));
00053D47 push 5
00053D49 lea eax,[tmp]
00053D4C push eax
00053D4D lea ecx,[ebp-28h]
00053D50 push ecx
00053D51 call strncpy_s<32> (0511EFh)
00053D56 add esp,0Ch
ShowUnitTest(test);
00053D59 sub esp,28h
00053D5C mov ecx,0Ah
00053D61 lea esi,[test]
00053D64 mov edi,esp
00053D66 rep movs dword ptr es:[edi],dword ptr [esi]
00053D68 call ShowUnitTest (0511E5h)
我們直接觀察0x00053D59 開始調(diào)用ShowUnitTest處的匯編代碼:
我們發(fā)現(xiàn)首先通過棧指著esp 開辟了0x28H(40)大小的空間苍在,正好是UnitTest對象的大小。
然后通過rep movs 指令將對象test拷貝了一份到此處荠商。
而這里的操作即是《C++ Primer》中所說的調(diào)用默認拷貝構造函數(shù)并創(chuàng)建的臨時變量寂恬。
編譯器在這里插入了申請棧空間及直接復制對象在棧上內(nèi)存的內(nèi)存數(shù)據(jù)的匯編指令(即淺拷貝)
然后我們觀察ShowUnitTest的內(nèi)部實現(xiàn):
ShowUnitTest:
00281420 push ebp
00281421 mov ebp,esp
00281423 sub esp,0C0h
00281429 push ebx
0028142A push esi
0028142B push edi
0028142C lea edi,[ebp-0C0h]
00281432 mov ecx,30h
00281437 mov eax,0CCCCCCCCh
0028143C rep stos dword ptr es:[edi]
printf("%d %d %s\n", unit.m_One, unit.m_Two, unit.m_sName);
0028143E mov esi,esp
00281440 lea eax,[ebp+10h]
00281443 push eax
00281444 mov ecx,dword ptr [ebp+0Ch]
00281447 push ecx
00281448 mov edx,dword ptr [unit]
0028144B push edx
0028144C push 2859DCh
00281451 call dword ptr ds:[289118h]
00281457 add esp,10h
0028145A cmp esi,esp
0028145C call __RTC_CheckEsp (028113Bh)
}
從00281440這里可以看出莱没,程序在向ebp的下方(即更高的地址位置)進行尋址操作掠剑,而操作的位置就是該函數(shù)的調(diào)用函數(shù)的棧幀,而 ebp + 10h 對應的就是臨時變量的m_sName的首地址郊愧,ebp+0Ch 即臨時對象m_Two的首地址朴译, [unit] 即ebp+08 井佑,即臨時對象的首地址,也就是對象m_One的位置眠寿。