關於返回結構體的函式

2021-10-11 02:08:35 字數 2763 閱讀 1638

本文的範例**取材於 《彙編中函式返回結構體的方法》一文,並在此基礎上進行修改和試驗。要研究的第乙份**如下,定義乙個不超過 8 bytes 的小結構體,不超過 8 bytes 是因為這個結構體能夠用 edx:eax 容納,我們之後將看到在 release 編譯時,編譯器能夠向返回普通基礎型別那樣進行返回。

#include //不超過 8 bytes 的「小結構體」

struct a

;//返回結構體的函式

struct a add(int x, int y)

int main()

首先,我們需要解決乙個常見困惑,就是要明確這段**和下面的典型錯誤**的區別:

char* get_buffer()

char buf[8];

return buf;

上面的 get_buffer 返回的是棧上的臨時變數空間,在函式返回後,其所在的空間也就被「**/釋放」了,也就是說函式返回的位址位於棧的增長方向上,是不穩定和不被保證的。

那麼返回結構體的函式則不同,你可以發現返回結構體的函式是工作正常有效的。在 add 函式中有乙個臨時性結構體 t,毫無疑問,t 將在 add 函式返回時被釋放,但由於 t 被當做「值」進行返回,因此編譯器將保證 add 的返回值對於 add 的呼叫者(caller)來說是有效的。

另外需要明確的一點是,我個人覺得,現實裡這種返回結構體的方式比較少見,後面將會看到這樣做會產生臨時物件和多餘拷貝過程,效率不高。常 見方法是傳遞結構體指標。但作為語言上允許的方式,有必要弄清楚編譯器如何實現這種方式,而要弄清楚這個問題,需要檢視彙編**。使用 vc6 輸入上述**,下面分別給出其彙編**。

下面是實現方式的棧示意圖:

總結:(1.1)用 edx:eax 傳遞返回值。呼叫方不需要在棧上向 add 函式傳遞接受返回值的位址。

(2.2)debug 版本在呼叫方生成臨時物件返回值,然後再把臨時物件拷貝到 main 臨時變數所在位址。效率低。

總結:(2.1)同(1.1),用 edx:eax 傳遞返回值,不需要傳遞接收返回值的位址。

(2.2)release 版本呼叫方沒有臨時物件,效率基本等同於傳結構體指標。

(2.3)release 版本優化的太厲害,甚至都沒有把返回值完整的拷貝到臨時變數 t (只拷貝了結構體中的成員t.b,t.a 的拷貝被認為沒有存在價值而被優化掉了,因為 t.a 的值存於 eax),和高階語言有較大差別。

如果是超過 8 bytes 的結構體,edx:eax 將容納不下,這時就需要呼叫方提供接受返回值的位址,即呼叫方在棧上分配臨時物件,並把其位址通過棧傳遞給函式(先 push 引數,最後 push 用於設定返回值的結構體位址)。

把上述**中的結構體定義增加乙個 int 成員即可令結構體超過 8 bytes,即調整上述**的 struct 定義:

struct a

;使用 vc6 編譯後產生的彙編**如下:

debug 版本:

release 版本:

上述兩種編譯結果,實現的模型基本相同。因此在這裡以debug版本**為主,一併分析,其棧示意圖如下,下圖左側為 debug 版本,右側是 release 版本:

總結:(1)當結構體超過 8 bytes,不能用 edx:eax 傳遞,這時呼叫方在棧上保留有乙個用於填充返回值的結構體,其位址在入棧引數後 push 到棧上。函式將會根據這個位址,把返回值設定到這個位址。

(2)在 main 函式中,debug 版本比 release 版本還多了乙個臨時物件,效率低。而 release 版本中只有返回值和臨時變數 t,效率略高於 debug。但兩者模型基本一致,總體效率低於傳結構體指標。

(3)release 版本同樣優化比較厲害,main 函式中對 t 的賦值是不完整的,因為編譯器認為沒有必要,只要滿足**等效即可。

最後我們總結針對較大結構體(超過 8 bytes)時,返回結構體的函式的實現方式的基本模型:

(1)呼叫方在棧上分配用於接收返回值的臨時結構體,並把位址通過棧傳遞給函式。

(2)函式根據返回值的位址,設定返回值。

(3)呼叫方根據需要,把返回值再賦值給需要的臨時變數。

(4)返回時,eax 儲存的是返回值的那個位址。

因此,從上面的過程可以看到,由於存在臨時物件和拷貝操作,其效率比傳遞結構體指標的函式低。

由於不管 debug 還是 release,對於「大結構體」都會在棧上傳遞返回值的位址,所以我們可以通過下面的**,來測試出這樣的結論:函式 add 的返回值(臨時結構體)的位址和 main 中的變數 t 的位址是不同的。原理是,第乙個形參的棧頂方向的相鄰元素就是返回值的位址,因此用乙個指標指向第乙個形參,然後向棧頂移動一格,取出其值,就是返回值的 位址。

#include struct a

;struct a add(int x, int y)

int main(int argc, char* ar**)

上面的**中,有一點需要注意,返回值的位址和 t 的位址的關係是依賴編譯器的,也就是說,沒有任何保證,兩者之間是否相鄰以及它們之間的大小關係。但你可以通過嘗試移動上面的指標 p1,試圖將 p1 指向返回值,但這並不是乙個簡單容易的事情(因為編譯器的行為效果是盡量避免讓這個返回值被其他指標指到)。

c 函式返回結構體陣列的神奇之處

眾所周知,如果我們想用函式返回乙個陣列,那麼我們應該用new來分配記憶體,而不是單純地申明陣列區域性變數,否則函式體結束後區域性變數被釋放,陣列也就丟失了 申明區域性變數 int test0 return a 用new分配記憶體 int test1 return a 列印兩種申明方式地結果 intm...

C 語言函式返回結構體彙編分析

為檢驗vc預設設定下結構的對齊情況,特定義結構如下 1 typedef struct ctest 2 ctest,pctest 9 getdata 函式返回上面定義的結構,由此可觀察 c 語言中返回結構時的細節 10 ctest getdata 11 0040104a 8be5 mov esp,eb...

關於結構體的sort函式的用法

引入乙個問題 題目分析 判斷2件商品商品先買哪一件,這兩件商品買完後對剩餘商品的折扣不便,所以這2商品的先後順序不會對後面有所影響,所以我們只需根據當前的匯率進行判斷先買哪乙個產品 當前匯率en 第1件產品的 a1,折扣b1 第2件產品的 a2,折扣b2 先買1,再買2,sum1 a1 en a2 ...