C語言匯編分析傳遞結(jié)構(gòu)體指針比傳遞結(jié)構(gòu)體變量高效的深層原因
前言
先聲明下觀點:當有少量結(jié)構(gòu)體成員時,傳遞結(jié)構(gòu)體指針和結(jié)構(gòu)體變量的差距不大;當有大量結(jié)構(gòu)體成員時,隨著成員越來越多,傳遞指針的效率也越來越高,與傳遞變量的差距也越來越大。
傳遞結(jié)構(gòu)體變量
直接看代碼:
測試程序demo01.cpp,如下:
#include <stdio.h> #include <Windows.h> struct st_info // 定義結(jié)構(gòu)體 { int x; int y; int m; int n; }; int retAddst(st_info stinfo) // 函數(shù)返回結(jié)構(gòu)體變量成員相加的值 { return stinfo.x+stinfo.y+stinfo.m+stinfo.n; } int main() { st_info stinfo = {1,2,3,4}; // 定義變量準備傳參。 int r = retAddst(stinfo); // 接收返回值,此處設(shè)置斷點查看反匯編 return 0; }
vs2010:斷點、F7編譯、F5調(diào)試、ALT+8轉(zhuǎn)到反匯編
如下:
當看到這段匯編代碼的實現(xiàn)的時候,可能對于新手都不太友好,因為之前對于函數(shù)的調(diào)用時,匯編代碼大多都是使用push進行傳參,但是這里調(diào)用函數(shù)卻沒有用到push,那他是怎么實現(xiàn)的呢?
我們畫堆棧圖逐步分析:
堆棧:
匯編:
堆棧:
匯編:
堆棧:
匯編:
堆棧:
匯編:
堆棧:
匯編:
堆棧:
然后下面就是調(diào)用函數(shù),讓我們看看函數(shù)中是怎么使用的
單步F11進入函數(shù)內(nèi)部:
匯編:
這里我直接給出堆棧結(jié)果,不懂得可以看我之前的文章《堆棧圖》
匯編:
對應(yīng)堆棧:
可以看出,雖然沒有使用push進行參數(shù)的傳遞,但是他還是使用堆棧,使用ebp尋址來實現(xiàn)的函數(shù)參數(shù)的查找。
為什么說傳遞結(jié)構(gòu)體變量性能不高?
我們來分析匯編:
為什么這叫拷貝?
拷貝的概念就是,在不影響原值的情況下,在另外一個地址中也存放一個同樣的值
我們可以發(fā)現(xiàn),我們mov指令并不會刪除我們之前定義在main函數(shù)局部變量區(qū)域中的1,2,3,4,并且還復(fù)制了一份到esp、esp+4...這些地址中,所以這就是拷貝。
一次拷貝需要從原地址中取一次值、然后放到寄存器、最終放到目標地址,是不是很麻煩?但是如果結(jié)構(gòu)體變量中需要用到四個成員,那么就需要進行四次拷貝,如果成員越來越多,拷貝的次數(shù)也就越來越多......
結(jié)構(gòu)體成員拷貝的壞處
隨著拷貝次數(shù)越來越多,不但會影響性能,也會使匯編代碼顯得非常臃腫。
解決方法就是傳指針。
傳遞結(jié)構(gòu)體指針
按照我們對傳遞指針的理解,我們認為傳遞變量的指針就是傳遞他的地址,那么既然有了這個變量的地址了,是不是就不需要拷貝了?
測試程序demo01,代碼如下:
#include <stdio.h> #include <Windows.h> struct st_info // 定義結(jié)構(gòu)體 { int x; int y; int m; int n; }; int retAddst(st_info* stinfo) // 函數(shù)返回結(jié)構(gòu)體變量成員相加的值 { return stinfo->x+stinfo->y+stinfo->m+stinfo->n; } int main() { st_info stinfo = {1,2,3,4}; // 定義變量準備傳參。 int r = retAddst(&stinfo); // 接收返回值,此處設(shè)置斷點查看反匯編 return 0; }
重新生成、調(diào)試、反匯編:
lea eax,[ebp-18h]
通過上面將1存入[ebp-18h]我們知道ebp-18h就是結(jié)構(gòu)體第一個成員的地址,也就是結(jié)構(gòu)體的首地址,所以這里我們僅僅是傳遞了結(jié)構(gòu)體的首地址
(注意:lea指令是將ebp-18h這個地址賦值給eax,而不是將地址中的1賦值給eax)
與傳遞結(jié)構(gòu)體變量的匯編對比:
1、首先我們一眼就能看出,匯編代碼變得整潔了。
2、傳遞結(jié)構(gòu)體變量的匯編中,雖然找不到push,但是我們進入函數(shù)中分析,發(fā)現(xiàn)它使用的依舊是堆棧、并且最后平衡堆棧的時候是add esp+10h,不算函數(shù)提升堆棧的使用,總共使用了16字節(jié)的堆棧;
然而對于傳遞指針,最終只是add esp,4;只使用了4個自己的堆棧。并且隨著結(jié)構(gòu)體的成員越來越多、差距會越來越大。
對于傳遞指針,函數(shù)內(nèi)部是如何使用的呢?
如下:
可能看到這里,會有人問:這不是和傳遞結(jié)構(gòu)體變量傳參的代碼差不多嗎?因為單從匯編代碼上來觀察,貌似都長得很像,但是還是有區(qū)別的。
傳遞變量時,我們是將原堆棧中的值取出放到寄存器、然后寄存器放到新的堆棧中
傳遞地址時,我們是將首地址放到寄存器中,然后取出該地址中的值又放到寄存器中
區(qū)別呢?
三種方法看出傳遞變量與傳遞指針的差距
<1>
傳遞變量:堆棧->寄存器->新堆棧
傳遞指針:堆棧->寄存器->寄存器
我們之前說過,使用內(nèi)存(堆棧)是絕對沒有使用cpu(寄存器)的效率高的,所以這也能看出傳遞地址是比傳遞變量效率高的。
<2>
傳遞變量:add esp,10h
傳遞指針:add esp,04h
當我們傳遞變量時,我們可以發(fā)現(xiàn),底層匯編是不斷的將源地址中的值取出放到堆棧中的,一個使用了16個字節(jié);但是傳遞地址只用到了四個字節(jié)的堆棧,就是用來存放結(jié)構(gòu)體的首地址。這樣一來,傳遞變量內(nèi)存使用比傳遞指針要多。當然,我們結(jié)構(gòu)體成員越多,傳遞變量使用到的堆棧就越多,而傳遞指針還是只是用4個字節(jié)堆棧存放結(jié)構(gòu)體首地址,二者的差距會越來越大。
<3>
傳遞變量與傳遞地址的時候,我們都是先將結(jié)構(gòu)體成員存放到main函數(shù)的局部變量區(qū)域中,也就是下面這一塊:
但是傳遞變量的時候,它是將這四個值1,2,3,4拿出來又放進去的,操作是很頻繁的。相反傳遞地址的時候只是把【ebp-18h】這個地址放進去。一個操作四次、一個操作一次,差距一眼就能看出來。
總結(jié)
通過上面的對比,我們可以看出傳遞指針的效率是比傳遞變量效率高的。這個差距會隨著結(jié)構(gòu)體成員個數(shù)的提升而提升。所以,建議傳遞結(jié)構(gòu)體指針。
到此這篇關(guān)于C語言匯編分析傳遞結(jié)構(gòu)體指針比傳遞結(jié)構(gòu)體變量高效的深層原因的文章就介紹到這了,更多相關(guān)C語言匯編分析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言數(shù)據(jù)結(jié)構(gòu)之二叉鏈表創(chuàng)建二叉樹
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)之?二叉鏈表創(chuàng)建二叉樹,下文我們?yōu)榱烁奖愕氖褂枚鏄浣Y(jié)構(gòu)體,可以使用?typedef?對結(jié)構(gòu)體進行命名,具體內(nèi)容需要的小伙伴可以參考一下2022-02-02