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尋址來實現的函數參數的查找。

為什麼說傳遞結構體變量性能不高?

我們來分析匯編:

為什麼這叫拷貝?

拷貝的概念就是,在不影響原值的情況下,在另外一個地址中也存放一個同樣的值

我們可以發現,我們mov指令並不會刪除我們之前定義在main函數局部變量區域中的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,但是我們進入函數中分析,發現它使用的依舊是堆棧、並且最後平衡堆棧的時候是add esp+10h,不算函數提升堆棧的使用,總共使用瞭16字節的堆棧;

然而對於傳遞指針,最終隻是add esp,4;隻使用瞭4個自己的堆棧。並且隨著結構體的成員越來越多、差距會越來越大。

對於傳遞指針,函數內部是如何使用的呢?

如下:

可能看到這裡,會有人問:這不是和傳遞結構體變量傳參的代碼差不多嗎?因為單從匯編代碼上來觀察,貌似都長得很像,但是還是有區別的。

傳遞變量時,我們是將原堆棧中的值取出放到寄存器、然後寄存器放到新的堆棧中

傳遞地址時,我們是將首地址放到寄存器中,然後取出該地址中的值又放到寄存器中

區別呢?

三種方法看出傳遞變量與傳遞指針的差距

<1>

傳遞變量:堆棧->寄存器->新堆棧

傳遞指針:堆棧->寄存器->寄存器

我們之前說過,使用內存(堆棧)是絕對沒有使用cpu(寄存器)的效率高的,所以這也能看出傳遞地址是比傳遞變量效率高的。

<2>

傳遞變量:add esp,10h

傳遞指針:add esp,04h

當我們傳遞變量時,我們可以發現,底層匯編是不斷的將源地址中的值取出放到堆棧中的,一個使用瞭16個字節;但是傳遞地址隻用到瞭四個字節的堆棧,就是用來存放結構體的首地址。這樣一來,傳遞變量內存使用比傳遞指針要多。當然,我們結構體成員越多,傳遞變量使用到的堆棧就越多,而傳遞指針還是隻是用4個字節堆棧存放結構體首地址,二者的差距會越來越大。

<3>

傳遞變量與傳遞地址的時候,我們都是先將結構體成員存放到main函數的局部變量區域中,也就是下面這一塊:

但是傳遞變量的時候,它是將這四個值1,2,3,4拿出來又放進去的,操作是很頻繁的。相反傳遞地址的時候隻是把【ebp-18h】這個地址放進去。一個操作四次、一個操作一次,差距一眼就能看出來。

總結

通過上面的對比,我們可以看出傳遞指針的效率是比傳遞變量效率高的。這個差距會隨著結構體成員個數的提升而提升。所以,建議傳遞結構體指針。

到此這篇關於C語言匯編分析傳遞結構體指針比傳遞結構體變量高效的深層原因的文章就介紹到這瞭,更多相關C語言匯編分析內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: