標籤:回退 不可 ref inux else fun alt 傳參 調用
注意:都是在沒有最佳化的情況下編譯的。因為只要開-O1或是-O2,那麼彙編代碼就少的可憐了,都被最佳化掉了
編譯器版本:x86-64 gcc 5.5
1 POD類型傳參1.1 一個pod參數,pod傳回值
int square(int num) { return num * num;}int main(){ int c=90; int a=square(c); ++a;}
對應彙編
1.2 兩個pod參數,pod傳回值
int mul(int v1,int v2) { return v1 * v2;}int main(){ int c=90; int a=mul(c,1); ++a;}
當第二個參數也傳入變數的時候,會使用edx,像eax一樣傳入。然後傳回值依然使用eax返回。
1.3 幾個參數才會動用到棧傳參,
int mul(int v1,int v2,int v3,int v4,int v5,int v6,int v7) { return v1 * v2*v3*v4*v5*v6*v7;}int main(){ int c1=90; int c2=10; int c3=10; int c4=10; int c5=10; int c6=10; int c7=10; int a=mul(c1,c2,c3,c4,c5,c6,c7); ++a; c1++;c2++;c3++;c4++;c5++;c6++;c7++; a=mul(a,c2,c3,c4,c5,c6,c7);}
從可以看到,是從第7個參數開始,使用棧傳遞參數。
並且之前都是使用edi作為第一個參數,但是當使用棧的時候就使用edi來倒騰資料了。
注意,棧先回退,然後再去儲存傳回值
再看函數調用:
2 結構體傳參
class A{public: A() :i1(1) {} public: int i1; char a; int i2; char ca; char c1; ~A() { i2=2; }};int func(A a1,A a2,A a3,A a4,A a5,A a6,A a7){ a1.a++; return 1;}void func1(int i){ if(i<10) { i++; A a1; A a2; A a3; A a4; A a5; A a6; A a7; int a=func(a1,a2,a3,a4,a5,a6,a7); // a1.a++; // int bb=func(a1,a2,a3,a4,a5,a6); // }else{ // return func1(i+1); // }}}
先看func1函數
傳參
加上一個拷貝建構函式:
A(A& a) { ca=++a.ca; }
然後看看拷貝建構函式的彙編:
對的。你有沒有發現,對象a,也就是參數所在位置的記憶體,就只有ca成員被初始化了,也就是說被修改了。其他的資料成員都沒有修改。
然後配合
這種,棧指標回退的做法,是不是就能夠明白。為什麼說函數內部的變數,如果沒有初始化,那麼值是未定義的。而不是說0
因為,之前使用這塊記憶體空間的函數,並沒有將那塊記憶體空間清0,而是直接sp+8這種形式退了回去。
因此後面函數再使用相同的記憶體,那麼就是未定義啊!!!未定義啊!
明白是怎麼來的了嗎?
為啥說函數外的變數就不會這樣。因為函數外變數佔用的記憶體就不會被回收,也就不存在被重用。一定是0.它內部的記憶體一定是0啊~~(那如果是別的進程使用了記憶體那?作業系統可能會清0吧。這個就真不清楚了)
然後繼續看函數調用
上面 為什麼要 先 rsp -8 ,命名push 的時候可以自動的做到rsp-8。
這裡為啥,我沒想明白。但是當壓入8個參數的時候,也就是說2個參數需要棧傳參,那麼會sp-8*2
再來看一下func函數。改一下,不然有些資訊看不出來。
int func(A a1,A a2,A a3,A a4,A a5,A a6,A a7,A b){ b.a++; a1.a++; return 1;}
雖然編譯器是gcc,但是因為有類,因此最終還是用的 g++ 來編譯。
因此,從上面可以看出來了吧,其實參數構造的地方,是在棧上,但是函數實際使用的是 lea 指令取的參數的有效地址,然後儲存在寄存器中,被調用函數通過寄存器訪問參數。
也就是說,並不存在什麼值傳遞。值傳遞的本意是參數使用拷貝建構函式複製了一份。然後取其有效地址通過寄存器傳入了被調用函數
那拷貝一份的意義在哪?在於不會更改原來的變數的值。應為傳入的參數是建構函式複製的那份。更改也無所謂。
如果第8個參數改為指標傳參會發生什麼:
首先發生的變化就是,拷貝建構函式的調用少了一次,也就是說第8個參數沒有被拷貝,但是第8個參數需要通過壓棧傳參了,因此可以發現,直接壓棧第八個參數實際值的有效地址
2.2 結構體類型傳回值
首先
class A{ public: A() : i(1) { } A(A& a) { } int i; // int i1; // int i2;};A func(){ A a; return a;}void func1(){ A b = func();}
這段代碼編譯不通過:
為什麼,因為sp指標在call結束以後直接回退,傳回值的處理是在sp指標回退以後才開始進行的。
那也就是說,如果這段代碼可以通過編譯,那麼就是說明,eax寄存器儲存的是傳回值的有效地址,而這個有效地址已經在sp指標之下了,也就是說不在棧內了。
換句話說,這個元素已經不可用了。既然已經不可用了,那怎麼還能用一個停用變數來構造值??
其實問題是在於,這個值是非const傳遞的,a在棧回退以後是一個匿名變數了,也就是說是一個右值了。她已經不再棧上了(sp之下),因此也就是無法被修改了。
而拷貝建構函式,不可避免的會攜帶之前變數的一些值,也就是說,是可以修改原來的變數的。但是a已經是一個右值了,(其實是在sp之下了),無法被修改。因此編譯器拒絕了這種構造。
但是如果改為
class A{ public: A() : i(1) { } A(const A &a) { } int i; // int i1; // int i2;};A func(){ A a; return a;}void func1(){ A b = func();}
編譯就可以通過,為什麼。因為傳入的是const A& 意味著使用這個值構造的對象,不會去修改這個值。或是說不能被修改。因此就可以使用這個值去構造其他對象了。
但是問題還是一個,a已經不再棧上了,怎麼去構造?
答案是先預留出傳回值的記憶體空間,然後將這個地址傳入,在被調用函數中構造。
3 nop是什麼
nop作用
這個,沒看完,不太懂。貼個連結吧。
c++ linux 下彙編分析傳參以及傳回值