1. 程式人生 > >c++ linux 下匯編分析傳參以及返回值

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

傳遞 {} UNC 而不是 pub x86 target 訪問 開始

註意:都是在沒有優化的情況下編譯的。因為只要開-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 下匯編分析傳參以及返回值