c++ linux 下彙編分析傳參以及傳回值

來源:互聯網
上載者:User

標籤:回退   不可   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 下彙編分析傳參以及傳回值

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.