码迷,mamicode.com
首页 > 编程语言 > 详细

c++ linux 下汇编分析传参以及返回值

时间:2018-03-26 22:30:30      阅读:429      评论:0      收藏:0      [点我收藏+]

标签:回退   不可   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 下汇编分析传参以及返回值

标签:回退   不可   ref   inux   else   fun   alt   传参   调用   

原文地址:https://www.cnblogs.com/perfy576/p/8654158.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!