C語言匯編分析傳遞結構體指針比傳遞結構體變量高效的深層原因
前言
先聲明下觀點:當有少量結構體成員時,傳遞結構體指針和結構體變量的差距不大;當有大量結構體成員時,隨著成員越來越多,傳遞指針的效率也越來越高,與傳遞變量的差距也越來越大。
傳遞結構體變量
直接看代碼:
測試程序demo01.cpp,如下:
#include <stdio.h>
#include <Windows.h>
struct st_info // 定義結構體
{
int x;
int y;
int m;
int n;
};
int retAddst(st_info stinfo) // 函數返回結構體變量成員相加的值
{
return stinfo.x+stinfo.y+stinfo.m+stinfo.n;
}
int main()
{
st_info stinfo = {1,2,3,4}; // 定義變量準備傳參。
int r = retAddst(stinfo); // 接收返回值,此處設置斷點查看反匯編
return 0;
}vs2010:斷點、F7編譯、F5調試、ALT+8轉到反匯編
如下:

當看到這段匯編代碼的實現的時候,可能對于新手都不太友好,因為之前對于函數的調用時,匯編代碼大多都是使用push進行傳參,但是這里調用函數卻沒有用到push,那他是怎么實現的呢?
我們畫堆棧圖逐步分析:

堆棧:

匯編:

堆棧:

匯編:

堆棧:

匯編:

堆棧:

匯編:

堆棧:

匯編:

堆棧:

然后下面就是調用函數,讓我們看看函數中是怎么使用的
單步F11進入函數內部:
匯編:

這里我直接給出堆棧結果,不懂得可以看我之前的文章《堆棧圖》

匯編:

對應堆棧:

可以看出,雖然沒有使用push進行參數的傳遞,但是他還是使用堆棧,使用ebp尋址來實現的函數參數的查找。
為什么說傳遞結構體變量性能不高?
我們來分析匯編:

為什么這叫拷貝?
拷貝的概念就是,在不影響原值的情況下,在另外一個地址中也存放一個同樣的值
我們可以發(fā)現,我們mov指令并不會刪除我們之前定義在main函數局部變量區(qū)域中的1,2,3,4,并且還復制了一份到esp、esp+4...這些地址中,所以這就是拷貝。
一次拷貝需要從原地址中取一次值、然后放到寄存器、最終放到目標地址,是不是很麻煩?但是如果結構體變量中需要用到四個成員,那么就需要進行四次拷貝,如果成員越來越多,拷貝的次數也就越來越多......
結構體成員拷貝的壞處
隨著拷貝次數越來越多,不但會影響性能,也會使匯編代碼顯得非常臃腫。
解決方法就是傳指針。
傳遞結構體指針
按照我們對傳遞指針的理解,我們認為傳遞變量的指針就是傳遞他的地址,那么既然有了這個變量的地址了,是不是就不需要拷貝了?
測試程序demo01,代碼如下:
#include <stdio.h>
#include <Windows.h>
struct st_info // 定義結構體
{
int x;
int y;
int m;
int n;
};
int retAddst(st_info* stinfo) // 函數返回結構體變量成員相加的值
{
return stinfo->x+stinfo->y+stinfo->m+stinfo->n;
}
int main()
{
st_info stinfo = {1,2,3,4}; // 定義變量準備傳參。
int r = retAddst(&stinfo); // 接收返回值,此處設置斷點查看反匯編
return 0;
}重新生成、調試、反匯編:

lea eax,[ebp-18h]
通過上面將1存入[ebp-18h]我們知道ebp-18h就是結構體第一個成員的地址,也就是結構體的首地址,所以這里我們僅僅是傳遞了結構體的首地址
(注意:lea指令是將ebp-18h這個地址賦值給eax,而不是將地址中的1賦值給eax)
與傳遞結構體變量的匯編對比:
1、首先我們一眼就能看出,匯編代碼變得整潔了。
2、傳遞結構體變量的匯編中,雖然找不到push,但是我們進入函數中分析,發(fā)現它使用的依舊是堆棧、并且最后平衡堆棧的時候是add esp+10h,不算函數提升堆棧的使用,總共使用了16字節(jié)的堆棧;
然而對于傳遞指針,最終只是add esp,4;只使用了4個自己的堆棧。并且隨著結構體的成員越來越多、差距會越來越大。
對于傳遞指針,函數內部是如何使用的呢?
如下:

可能看到這里,會有人問:這不是和傳遞結構體變量傳參的代碼差不多嗎?因為單從匯編代碼上來觀察,貌似都長得很像,但是還是有區(qū)別的。
傳遞變量時,我們是將原堆棧中的值取出放到寄存器、然后寄存器放到新的堆棧中
傳遞地址時,我們是將首地址放到寄存器中,然后取出該地址中的值又放到寄存器中
區(qū)別呢?
三種方法看出傳遞變量與傳遞指針的差距
<1>
傳遞變量:堆棧->寄存器->新堆棧
傳遞指針:堆棧->寄存器->寄存器
我們之前說過,使用內存(堆棧)是絕對沒有使用cpu(寄存器)的效率高的,所以這也能看出傳遞地址是比傳遞變量效率高的。
<2>
傳遞變量:add esp,10h
傳遞指針:add esp,04h
當我們傳遞變量時,我們可以發(fā)現,底層匯編是不斷的將源地址中的值取出放到堆棧中的,一個使用了16個字節(jié);但是傳遞地址只用到了四個字節(jié)的堆棧,就是用來存放結構體的首地址。這樣一來,傳遞變量內存使用比傳遞指針要多。當然,我們結構體成員越多,傳遞變量使用到的堆棧就越多,而傳遞指針還是只是用4個字節(jié)堆棧存放結構體首地址,二者的差距會越來越大。
<3>
傳遞變量與傳遞地址的時候,我們都是先將結構體成員存放到main函數的局部變量區(qū)域中,也就是下面這一塊:

但是傳遞變量的時候,它是將這四個值1,2,3,4拿出來又放進去的,操作是很頻繁的。相反傳遞地址的時候只是把【ebp-18h】這個地址放進去。一個操作四次、一個操作一次,差距一眼就能看出來。
總結
通過上面的對比,我們可以看出傳遞指針的效率是比傳遞變量效率高的。這個差距會隨著結構體成員個數的提升而提升。所以,建議傳遞結構體指針。
到此這篇關于C語言匯編分析傳遞結構體指針比傳遞結構體變量高效的深層原因的文章就介紹到這了,更多相關C語言匯編分析內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
VisualStudio2022不支持.NET Framework 4.0項目解決辦法
本文主要介紹了VisualStudio2022不支持.NET Framework 4.0項目解決辦法,文中通過圖文的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-09-09

