国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
C/C++之深入分析inline函數(shù)

from http://blog.csdn.net/prsniper/article/details/7297315

2012.02

C/C++以其飚捍的執(zhí)行效率和近乎ASM的強大功能,使得這類語言在IT技術發(fā)展的驚濤駭浪中,久戰(zhàn)不衰.

C/C++中一個重要的特色就是內(nèi)聯(lián)函數(shù)(在函數(shù)的聲明代碼有inline),那么我們就深入探討一下這個神秘的小家伙吧.

 

如果跨過高級語言進入?yún)R編層面,函數(shù)的調(diào)用是通過堆棧完成的,往往函數(shù)參數(shù)越多,堆棧操作也越多,完成后堆棧的清理任務也更繁重.而內(nèi)聯(lián)函數(shù)則是面向編譯的小伎倆,直接將內(nèi)聯(lián)函數(shù)的源碼,復制到主調(diào)函數(shù)中,省去了堆棧操作,一定程度上提高了程序的性能(CPU緩存正在改變這一點,我們后面再說),先來看一段簡單的代碼:

  1. // http://blog.csdn.net/prsniper  
  2.   
  3. #include "stdafx.h"  
  4.   
  5. inline void fnHelloWorld3();  
  6.   
  7. int main(int argc, char* argv[])    /*命令行argc:參數(shù)數(shù)量,argv[]參數(shù)數(shù)組,字符串形式*/  
  8. {   void fnHelloWorld2();   //聲明函數(shù)  
  9.   
  10.     printf("Hello World1!\n");  
  11.     fnHelloWorld2();    //call function  
  12.     fnHelloWorld3();    //call inline function  
  13.     return 0;  
  14. }  
  15.   
  16. void fnHelloWorld2()  
  17. {   printf("Hello World2!\n");  
  18. }  
  19.   
  20. inline void fnHelloWorld3()  
  21. {   printf("Hello World3!\n");  
  22. }  


又是一個hello world程序.在VC6中我們直接進入DASM進行DEBUG(VC6以上版本也可以的,比如VC.NET2003)

7:    int main(int argc, char* argv[])    /*命令行argc:參數(shù)數(shù)量,argv[]參數(shù)數(shù)組,字符串形式*/
8:    {   void fnHelloWorld2();   //聲明函數(shù)
00401030   push        ebp    ;基址指針入棧
00401031   mov         ebp,esp    ;堆棧棧頂指針傳送到基枝指針,其實就是不管前面的堆棧,一切從這里開始
00401033   sub         esp,40h    ;ESP減少0x40=64字節(jié),這就是函數(shù)的堆棧空間
00401036   push        ebx    ;存儲器指針入棧
00401037   push        esi    ;源指針
00401038   push        edi    ;目的指針
00401039   lea         edi,[ebp-40h]    ;不改變EBP的情況下,將EBP-0x40的值傳給EDI,見下
0040103C   mov         ecx,10h    ;串操作計數(shù),要填充那么多次
00401041   mov         eax,0CCCCCCCCh
00401046   rep stos    dword ptr [edi]    ;填充堆棧空間為0xCCCCCC,這就是為什么VC的局部變量默認是0xCC...
9:
10:       printf("Hello World1!\n");    //常規(guī)調(diào)用
00401048   push        offset string "Hello World1!\n" (0042001c)    ;字符串指針入棧
0040104D   call        printf (00401130)    ;調(diào)用PRINTF函數(shù)
00401052   add         esp,4    ;棧頂指針加一個DWORD,其實就是刪去這個字符串變量
11:       fnHelloWorld2();    //call function
00401055   call        @ILT+0(fnHelloWorld2) (00401005)    ;@ILT我還沒完全弄清楚,不敢亂說,個人認為是便宜常量(見下)
12:       fnHelloWorld3();    //call inline function
0040105A   call        @ILT+10(fnHelloWorld3) (0040100f)
13:       return 0;
0040105F   xor         eax,eax    ;XOR自身其實就是將自身清0
14:   }
00401061   pop         edi    ;壓入堆棧的寄存器,依次復原,又是一堆羅嗦的操作
00401062   pop         esi
00401063   pop         ebx
00401064   add         esp,40h
00401067   cmp         ebp,esp
00401069   call        __chkesp (004011b0)    ;堆棧檢查,你可以繼續(xù)跟蹤,好長...
0040106E   mov         esp,ebp
00401070   pop         ebp
00401071   ret   ;終于函數(shù)調(diào)用結(jié)束了

....

@ILT+0(?fnHelloWorld2@@YAXXZ):
00401005   jmp         fnHelloWorld2 (00401090)    ;跳轉(zhuǎn)到這里,相當于直接執(zhí)行PRINTF了,中間只經(jīng)歷了2個JMP無條件跳轉(zhuǎn)
@ILT+5(_main):
0040100A   jmp         main (00401030)
@ILT+10(?fnHelloWorld3@@YAXXZ):
0040100F   jmp         fnHelloWorld3 (004010e0)

 

現(xiàn)在,我們可以看到,內(nèi)聯(lián)的函數(shù)其實就是很卑鄙地給函數(shù)執(zhí)行部分加一個標簽,調(diào)用的時候直接跳轉(zhuǎn)到這里執(zhí)行.

省去了一大堆堆棧操作,然而是否可以任意使用inline呢?答案是否定的,再看一段代碼(原代碼捎加修改而已):

  1. // http://blog.csdn.net/prsniper  
  2.   
  3. #include "stdafx.h"  
  4.   
  5. inline void fnHelloWorld3();  
  6.   
  7. int main(int argc, char* argv[])    /*命令行argc:參數(shù)數(shù)量,argv[]參數(shù)數(shù)組,字符串形式*/  
  8. {   unsigned char byt[1024];  
  9.     void fnHelloWorld2(unsigned char *b);   //聲明函數(shù)  
  10.   
  11.     printf("Hello World1!\n");  
  12.     fnHelloWorld2(byt); //call function  
  13.     fnHelloWorld3();    //call inline function  
  14.     return 0;  
  15. }  
  16.   
  17. void fnHelloWorld2(unsigned char *b/*b[128]*/)  
  18. {   int i;  
  19.     unsigned char bv[128];  
  20.     printf("Hello World:");  
  21.     for(i = 0; i < 128; i++)  
  22.     {   printf("%d,", b[i]);  
  23.         bv[i] = b[i];  
  24.     }  
  25.     printf("\b!\n");  
  26. }  
  27.   
  28. inline void fnHelloWorld3()  
  29. {   printf("Hello World3!\n");  
  30. }  


我們在C/C++層面DEBUG,發(fā)現(xiàn)循環(huán)中bv[i] = b[i];沒有生效,實際執(zhí)行中也是,那我們就看看它更深層的機理吧:


17:   void fnHelloWorld2(unsigned char *b/*b[128]*/)
18:   {   int i;
0040B820   push        ebp    ;相同的地方就不贅述了
0040B821   mov         ebp,esp
0040B823   sub         esp,0C4h   ;這里用更大的堆棧空間0xC4=196,是為了能容下局部變量
0040B829   push        ebx
0040B82A   push        esi
0040B82B   push        edi
0040B82C   lea         edi,[ebp-0C4h]
0040B832   mov         ecx,31h
0040B837   mov         eax,0CCCCCCCCh
0040B83C   rep stos    dword ptr [edi]
19:       unsigned char bv[128];
20:       printf("Hello World:");
0040B83E   push        offset string "Hello World:" (00420034)
0040B843   call        printf (00401130)    ;先執(zhí)行一次PRINTF
0040B848   add         esp,4

21:       for(i = 0; i < 128; i++)    //下面就是循環(huán)編譯后的匯編代碼啦
0040B84B   mov         dword ptr [ebp-4],0    ;基址指針-4就是變量i,初始化為0
0040B852   jmp         fnHelloWorld2+3Dh (0040b85d)    ;轉(zhuǎn)到CMP
0040B854   mov         eax,dword ptr [ebp-4]    ;經(jīng)過循環(huán)來到這里,將i的值傳送到EAX累加器,自加(i++)
0040B857   add         eax,1
0040B85A   mov         dword ptr [ebp-4],eax    ;結(jié)果回到i的地址,完成i++
0040B85D   cmp         dword ptr [ebp-4],80h    ;i與0x80=128比較
0040B864   jge         fnHelloWorld2+72h (0040b892)    ;如果i大于或者等于128則跳轉(zhuǎn)到循環(huán)以后執(zhí)行源碼第25行
22:       {   printf("%d,", b[i]);    //打印沒有問題,就不解釋了,偷懶就是藝術
0040B866   mov         ecx,dword ptr [ebp+8]
0040B869   add         ecx,dword ptr [ebp-4]
0040B86C   xor         edx,edx
0040B86E   mov         dl,byte ptr [ecx]
0040B870   push        edx
0040B871   push        offset string "%d," (00420044)
0040B876   call        printf (00401130)
0040B87B   add         esp,8
23:           bv[i] = b[i];
0040B87E   mov         eax,dword ptr [ebp+8]   ;這里解釋一下EBP[=調(diào)用前ESP]是堆棧,-4是第一個局部變量,+8則是第一個參數(shù)
0040B881   add         eax,dword ptr [ebp-4]    ;第一個參數(shù)的地址+變量i的偏移得到b[i]
0040B884   mov         ecx,dword ptr [ebp-4]    ;一樣計算偏移,存儲在ECX中,下面將作為局部變量BV的索引
0040B887   mov         dl,byte ptr [eax]    ;EDX的低8位存儲參數(shù)1對應偏移的值 b[i]
0040B889   mov         byte ptr [ebp+ecx-84h],dl    ;EBP-i-0x80-4這樣好理解多(我看了下,如果字節(jié)倒序存儲是沒有問題的,但是內(nèi)存地址的值沒有被更改)
24:       }
0040B890   jmp         fnHelloWorld2+34h (0040b854)    ;跳轉(zhuǎn)回到累加操作,準備下一個循環(huán)
25:       printf("\b!\n", i);
0040B892   mov         eax,dword ptr [ebp-4]
0040B895   push        eax
0040B896   push        offset string "\x08!\n" (00420030)
0040B89B   call        printf (00401130)
0040B8A0   add         esp,8

26:   }
0040B8A3   pop         edi
0040B8A4   pop         esi
0040B8A5   pop         ebx
0040B8A6   add         esp,0C4h
0040B8AC   cmp         ebp,esp
0040B8AE   call        __chkesp (004011b0)
0040B8B3   mov         esp,ebp
0040B8B5   pop         ebp
0040B8B6   ret

代碼不能亂用,就像女人不能亂碰,否則在特定的情況下,讓你痛不欲生!

網(wǎng)上有資料說:不能包含循環(huán)、switch、if語句,我沒有完整實驗,不過循環(huán)是不行了,至于你信不信,反正我信了.

下面回到開頭,我們說的CPU緩存在改變這一點,做一個簡單的敘述:

直接的說,現(xiàn)在一般CPU的緩存比較大,如我的爛U:AMD雙核 6000+ 分別有64K的L1(Level1)數(shù)據(jù)和代碼緩存,好一點的可以到256K,我的L2有2M的數(shù)據(jù)代碼緩存.

通常來講,L1的存取速度直接就是寄存器的速度,理論可以達到CPU時鐘頻率,可以看作是不需要時間的,而內(nèi)存一般是每秒10G左右的讀寫速度.

CPU將一些代碼從內(nèi)存緩存,再需要的時候不再訪問內(nèi)存,如果內(nèi)聯(lián)函數(shù)過多,將導致緩存承受不了那么多的空間,而反復緩存,就像WEB開發(fā)中的緩存,重復緩存還不如直接從磁盤讀取..

 

inline函數(shù)有它強悍的地方,用于高效程序,加密等可以說是"牛X",然而鑒于上面的困境,有沒有什么好的方法呢?

方法當然是有:

1.自己把內(nèi)聯(lián)函數(shù)的過程再寫一遍(累,跟主調(diào)函數(shù)融合難)

2.改為非內(nèi)聯(lián)函數(shù)(吐了)

3.自己寫匯編代碼(這個是我想到的比較好的方法了 - -)

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
獻給匯編初學者-函數(shù)調(diào)用堆棧變化分析
【代碼真相】函數(shù)調(diào)用 堆棧 轉(zhuǎn)載 - liangxiufei - 博客園
C 語言函數(shù)返回結(jié)構(gòu)體匯編分析
C++中的成員函數(shù)調(diào)用原理及this指針的傳遞方式
棧溢出漏洞原理詳解與利用
C語言你沒搞清的東西——移位
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服