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

注意:都是在没有优化的情况下编译的。因为只要开-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作用

这个,没看完,不太懂。贴个链接吧。

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

时间: 2024-11-09 02:17:34

c++ linux 下汇编分析传参以及返回值的相关文章

winform访问url传参有返回值

using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Net;using System.Text;using System.Threading.Tasks; namespace ConsoleApplication1{ class Program { static void Main(string[] args) { HttpWebRequest request =

javascript函数(声明,传参,返回值,递归)

javascript函数(声明,传参,返回值,递归) 1.函数的基本概念 函数:是由事件驱动的或者当他被调用时可执行的可重复使用的代码块. 空调是由遥控器控制或者当开关打开时,可运行的家用电器(工具) 特点: 封装代码----使代码更简洁 重复使用---在重复功能的时候直接调用就好 执行时机---随时可以在我们想要执行的时候执行 2.函数的创建和执行 1. 函数的创建 1.声明式 函数声明的关键字 : ==function== function 关键字 和 var 关键字的行为几乎一致,都会在内

深入理解PHP内核(六)函数的定义、传参及返回值

一.函数的定义 用户函数的定义从function 关键字开始,如下 function foo($var) { echo $var; } 1.词法分析 在Zend/zend_language_scanner.l中我们找到如下所示的代码: <ST_IN_SCRIPTING>"function" { return T_FUNCTION; } 它所表示的含义是function将会生成T_FUNCTION标记.在获取这个标记后,我们开始语法分析. 2.语法分析 在Zend/zend_

linux 下 php执行 exec 无反应 返回值为空

ps:请先确保 php.ini 的disable_functions 配置没有拦截  exec ,有请删掉,保存,重启php -------------------------------------------------------------------下面开始正文1.先找出是哪个用户在执行这个文件的 <?php$result = exec("whoami", $outcome, $status); var_dump($outcome); 我的是输出为 www 也有可能是

C#中的函数(二) 有参有返回值的函数

接上一篇 C#中的函数(-) 无参无返回值的函数 http://www.cnblogs.com/fzxiaoyi/p/8502613.html 这次研究下C#中的函数(二) 有参有返回值的函数 依然写一个小例子,用来测试 跟上一个例子差不多,区别就是MyFunction有二个参数a,b,返回二个数相加的值 F5调试运行,中断后转到反汇编 这里很明显看到不同了 这里就得讲到参数传递的方式,参数从左向右依次存入寄存器ecx edx 但是不同的编程语言有不同的传递参数的方式,有空再写一篇文章介绍下 要

Java 中带参带返回值方法的使用

如果方法既包含参数,又带有返回值,称为带参带返回值的方法. 例: 实现功能:将考试成绩排序并输出,返回成绩的个数 实现思路: 1. 定义一个包含整型数组参数的方法,用来传入成绩数组 2. 在方法体中使用 Arrays.sort( ) 方法对成绩数组进行排序,然后使用 Arrays.toString( ) 方法将数组转换为字符串并输出,最后使用 return 返回数组中元素的个数. 3. 调用方法时需要先创建对象,然后再调用.调用时为其传入成绩数组,并获取方法的返回值,保存在变量中,最后输出成绩的

Java 中无参带返回值方法的使用

如果方法不包含参数,但有返回值,我们称为无参带返回值的方法. 例如:下面的代码,定义了一个方法名为 calSum ,无参数,但返回值为 int 类型的方法,执行的操作为计算两数之和,并返回结果 在 calSum( ) 方法中,返回值类型为 int 类型,因此在方法体中必须使用 return 返回一个整数值. 调用带返回值的方法时需要注意,由于方法执行后会返回一个结果,因此在调用带返回值方法时一般都会接收其返回值并进行处理.如: 运行结果为: 两数之和为:17 不容忽视的“小陷阱”: 1. 如果方

&lt;10&gt; 无参无返回值+ 无参有返回值函数的定义+有参无返回值函数定义+ 有参有返回值函数定义+函数的参数+函数的参数传递过程

无参无返回值: 1 #include <stdio.h> 2 3 4 /** 5 * 定义一个无参无返值函数 6 * 7 */ 8 void print_line(){ 9 10 printf("-----------------\n"); 11 12 } 13 14 15 16 int main(int argc, const char * argv[]) { 17 //调用函数 18 print_line(); 19 20 return 0; 21 } 无参有返回值函数

java执行linux shell命令,并拿到返回值

1 package com.suning.spc.util; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.nio.charset.Charset; 6 7 import org.slf4j.Logger; 8 import org.slf4j.LoggerFactory; 9 10 import ch.ethz.ssh2.ChannelCondition; 11 import ch.eth