教你用彙編模擬實現C++模板函數,不要錯過!

如果說模板類定義的是一種數據類型,那麼模板函數定義的就是一種函數。既然是函數,那麼就有輸入數據和輸出數據。和模板類的概念差不多,模板函數的初衷也是為了在函數操作上抽取共同的特性,屏蔽的是類型的不同和差異。我們可以通過下面一個簡單的代碼說明問題:

int int_compare(int a, int b)
{
return a > b ? a : b;
}
double double_compare(double a, double b)
{
return a > b ? a : b;
}
上面的一段代碼是取較大值的一段代碼。兩個函數之間最大的差別就是輸入數據類型和輸出數據類型之間的差別,那我們有沒有一種辦法可以屏蔽這種數據類型之間的差別呢?有。那就是函數模板:template
type compare(type a, type b)
{
return a > b ? a : b;
}
可以看到,模板函數和普通函數沒有什麼區別,只是在函數的上面把類型抽象成了type,那麼模板函數應該怎麼使用呢?246: int i_value = compare(2, 3);
00401458 push 3
0040145A push 2
0040145C call @ILT+10(compare) (0040100f)
00401461 add esp,8
00401464 mov dword ptr [ebp-4],eax
247: double d_value = compare(2.3, 3.1);
00401467 push 4008CCCCh
0040146C push 0CCCCCCCDh
00401471 push 40026666h
00401476 push 66666666h
0040147B call @ILT+5(compare) (0040100a)
00401480 add esp,10h
00401483 fstp qword ptr [ebp-0Ch]
248: }

彙編代碼表明,兩個compare調用的函數地址並不是一致的。其中整數的compare地址是0x40100f,而double的地址是0x0040100a。這說明編譯器在編譯的時候幫我們同時生成了兩個compare函數。所以說,模板類的本質就是在編譯器增加判斷處理工作的同時,減少手工的重複勞動。同時和模板類不一樣,模板函數不需要顯示定義函數的參數類型,這是因為可以從入參判斷出函數的類型。 如果參數類型是 class類型呢? 我們可以試一試。首先定義基本class:

class data
{
int value;
public:
explicit data(int m): value(m) {}
~data() {}
int get_value() { return value;}
int operator > (data& d) {return this->get_value() > d.get_value();}
};
接著,我們調用compare函數:256: data m(4), n(2);
0040148D push 4
0040148F lea ecx,[ebp-10h]
00401492 call @ILT+40(data::data) (0040102d)
00401497 mov dword ptr [ebp-4],0
0040149E push 2
004014A0 lea ecx,[ebp-14h]
004014A3 call @ILT+40(data::data) (0040102d)
004014A8 mov byte ptr [ebp-4],1
257: data p = compare(m,n);
004014AC mov eax,dword ptr [ebp-14h]
004014AF push eax
004014B0 mov ecx,dword ptr [ebp-10h]
004014B3 push ecx
004014B4 lea edx,[ebp-18h]
004014B7 push edx
004014B8 call @ILT+15(compare) (00401014)
004014BD add esp,0Ch
258: }

256行: data構造了兩個基本變量m和n 257行: 我們調用模板函數compare, 函數地址為0x401014,注意dx為p的地址,也就是堆棧臨時變量的地址

為了看到算術符>重載,我們跟進compare函數:

241: return a > b ? a : b;
0040212B lea eax,[ebp+10h]
0040212E push eax
0040212F lea ecx,[ebp+0Ch]
00402132 call @ILT+55(data::operator>) (0040103c)
00402137 test eax,eax
00402139 je compare+53h (00402143)
0040213B lea ecx,[ebp+0Ch]

0040213E mov dword ptr [ebp-18h],ecx
00402141 jmp compare+59h (00402149)
00402143 lea edx,[ebp+10h]
00402146 mov dword ptr [ebp-18h],edx
00402149 mov eax,dword ptr [ebp-18h]
0040214C mov dword ptr [ebp-10h],eax
0040214F mov ecx,dword ptr [ebp-10h]
00402152 mov edx,dword ptr [ecx]
00402154 mov eax,dword ptr [ebp+8]
00402157 mov dword ptr [eax],edx
00402159 mov ecx,dword ptr [ebp-14h]
0040215C or ecx,1
0040215F mov dword ptr [ebp-14h],ecx
00402162 mov byte ptr [ebp-4],1
00402166 lea ecx,[ebp+0Ch]
00402169 call @ILT+25(data::~data) (0040101e)
0040216E mov byte ptr [ebp-4],0
00402172 lea ecx,[ebp+10h]
00402175 call @ILT+25(data::~data) (0040101e)
0040217A mov eax,dword ptr [ebp+8]

我們發現compare模板語句下面構建了很多彙編語句,有一些冗長,我們可以大略介紹一下: (1) 開頭調用call 0x0040103C函數就是調用重載運算符函數,[ebp-18h]表示即將被複制的是a數據還是b數據

(2) 比較返回結果後,開始複製數據,具體見0x402157,其中臨時變量[ebp-14h]和臨時變量[ebp-4]的操作可以忽略

(3) 函數返回前,對臨時變量a和b進行析構處理,見代碼0x402169和代碼0x402175。

注意:

(1)編寫模板函數前先保證自己的函數是編寫正確的

(2)模板函數的優先級低於非模板函數

(3)模板函數的類型可以是自定義類型,也可以是c、c++語言的基本類型

(4)模板函數的使用經常和類的算術運算符混合使用,注意技巧

(5)模板函數中涉及指針部分的內容,務必注意


分享到:


相關文章: