C/C++ 函数原理传参示例详解

目录
  • x84-64的寄存器
  • 函数是个什么东西?
    • 一个简单的函数
  • 传参姿势
    • 入栈规则
    • 看看汇编
  • 全都存寄存器吗?
  • 传对象呢?

x84-64的寄存器

本文所用gccx86-64 gcc 10.1

wiki.cdot.senecacollege.ca/wiki/X86_64…

rax - register a extended

rbx - register b extended

rcx - register c extended

rdx - register d extended

rbp - register base pointer (start of stack)

rsp - register stack pointer (current location in stack, growing downwards)

rsi - register source index (source for data copies)

rdi - register destination index (destination for data copies)

其他寄存器: r8 r9 r10 r11 r12 r13 r14 r15

函数是个什么东西?

一个简单的函数

int func(){}
int main() {
    int x = 2;
    func();
}
main:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        movl    $2, -4(%rbp)
        call    func()
        movl    $0, %eax
        leave
        ret

分配空间动作如下所示:

这里加了个函数调用是因为在有些时候,没有函数调用,就不会使用subq $16, %rsp 这一条指令,我的猜想是既然你都是栈顶的,并且不会再有rbp的变化,那么栈顶以上的元素我都可以随便用。
并且我们观察可以得知,分配栈空间时,他是分配的16个字节,也就是说,有对齐
返回时,弹出栈顶,就可以恢复到上一个栈帧的状态了。

传参姿势

入栈规则

c/c++ 中规定的函数压栈顺序是从右到左,当然,如果你是 Visual C/C++的话,它们有更多的玩法 比如:

template<typename T>
T val(T t) {
  cout << t << endl;
  return t;
}
signed main() {
  printf("%d%d%d", val(1), val(2), val(3));
  return 0;
}

结果

3
2
1
123

看看汇编

int func(int x, int y, int z) {
  return 0;
}
int main() {
  func(1, 2, 3);
}

生成的汇编

func(int, int, int):
        pushq   %rbp
        movq    %rsp, %rbp
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        movl    %edx, -12(%rbp)
        movl    $0, %eax
        popq    %rbp
        ret
main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $3, %edx
        movl    $2, %esi
        movl    $1, %edi
        call    func(int, int, int)
        movl    $0, %eax
        popq    %rbp
        ret

上文中可以看出,也证实了我们所观察到的,首先把3传给了edx,2传给了esi,1传给了edi

全都存寄存器吗?

寄存器毕竟少,当然,还可以存在栈上嘛

int fun() {return 0;}
int func(int x, int y, int z, int a, int b, int c, int d, int e, int f){
    fun();
    return e;
}
int main() {
    func(1, 2, 3, 4, 5, 6, 7, 8, 9);
    return 0;
}
fun():
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $0, %eax
        popq    %rbp
        ret
func(int, int, int, int, int, int, int, int, int):
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $24, %rsp
        movl    %edi, -4(%rbp)
        movl    %esi, -8(%rbp)
        movl    %edx, -12(%rbp)
        movl    %ecx, -16(%rbp)
        movl    %r8d, -20(%rbp)
        movl    %r9d, -24(%rbp)
        call    fun()
        movl    24(%rbp), %eax
        leave
        ret
main:
        pushq   %rbp
        movq    %rsp, %rbp
        pushq   $9  // 24+%rbp
        pushq   $8  // 16+%rbp
        pushq   $7  // 8 +%rbp
        movl    $6, %r9d
        movl    $5, %r8d
        movl    $4, %ecx
        movl    $3, %edx
        movl    $2, %esi
        movl    $1, %edi
        call    func(int, int, int, int, int, int, int, int, int)
        addq    $24, %rsp
        movl    $0, %eax
        leave
        ret

主函数中的这三条语句

pushq   $9
pushq   $8
pushq   $7

说明了,当函数入栈放寄存器放不下时,会放在栈上,放在栈顶之上,等函数调用执行完成后,rbp取出回到当前位置之后,再去addq $24, %rsp 把栈弹出这些元素。

并且func函数中的movl 24(%rbp), %eax也证明了,传的参数是在栈顶的上面(自上向下增长) 24 + %rbp 刚好是 $9, 也就是局部变量f的位置

传对象呢?

在这里,暂且不谈内存布局,把一个对象看成一块内存对于的位置
这里用一个结构体做示例

struct E {int x, y, z;};
E func(E e){
    e.x = 2;
    return e;
}
int main() {
    E e = {.x = 1, .y = 2, .z = 3};
    e = func(e);
    return 0;
}
func(E):
        pushq   %rbp
        movq    %rsp, %rbp
        // 将rdi 和 esi 取出来 放到 rdx 和 eax 中
        movq    %rdi, %rdx
        movl    %esi, %eax
        // 存放到开辟好的空间中 {x = rbp - 32, y = rbp - 28, z = rbp - 24}
        movq    %rdx, -32(%rbp)
        movl    %eax, -24(%rbp)
        // 更改 x
        movl    $2, -32(%rbp)
        // 将值移动到寄存器上,从返回寄存器上移动到局部返回出去的变量
        movq    -32(%rbp), %rax
        movq    %rax, -12(%rbp)
        movl    -24(%rbp), %eax
        movl    %eax, -4(%rbp)
        // 将返回值值移动到寄存器上 rax rdx 上
        movq    -12(%rbp), %rax
        movl    -4(%rbp), %ecx
        movq    %rcx, %rdx
        popq    %rbp
        ret
main:
        // 压栈保存现场 没什么好说的
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        // 内存布局
        rbp
        | z  rbp - 4
        | y  rbp - 8
        | x  rbp - 12
        movl    $1, -12(%rbp)
        movl    $2, -8(%rbp)
        movl    $3, -4(%rbp)
        // 移动 x 和 y 到 rdx 寄存器中
        movq    -12(%rbp), %rdx
        // 移动 z 到 eax中
        movl    -4(%rbp), %eax
        // 再将 rdx 和 eax 分别移动到rdi 和 esi中
        movq    %rdx, %rdi
        movl    %eax, %esi
        call    func(E)
        // 从rax 中取出x y
        movq    %rax, -12(%rbp)
        // 从rdx中取出z
        movl    -4(%rbp), %eax
        andl    $0, %eax
        orl     %edx, %eax //
        movl    %eax, -4(%rbp)
        movl    $0, %eax
        leave
        ret

以上就是C/C++ 函数原理传参示例详解的详细内容,更多关于C/C++ 函数原理传参的资料请关注我们其它相关文章!

(0)

相关推荐

  • C/C++ extern关键字用法示例全面解析

    目录 前言 一般用法 在本模块中使用: 跨模块中 extern 使用过程中的一些注意事项 数组与指针的区别 extern 声明全局变量的内部实现 extern "C" C和C++互相调用 C++的编译和链接 C的编译和连接 C++中调用C的代码 C中调用C++的代码 总结 前言 extern 是C/C++语言中表明全局变量或者函数作用范围(可见性)的关键字,编译器收到extern通知,则其声明的变量或者函数可以在本模块或者其他模块使用. 对于函数而言,由于函数的声明如“extern i

  • C/C++哈希表优化LeetCode题解997找到小镇的法官

    目录 方法一.哈希表 方法二.优化 方法一.哈希表 今天这道题比较简单,我们可以统计每个人信任别人的数量和被信任的数量,如果存在某个人信任别人的数量为0,且被信任的数量为 n-1,那么,这个人就是法官. 因为本题的数据范围为 [1,1000],数据范围比较小,所以,直接使用数组作为哈希表来使用. 请看代码: class Solution { public int findJudge(int n, int[][] trust) { // 不信任任何人的人 & 被所有人信任的人 // 计算每个人信任

  • C/C++自主分配出现double free or corruption问题解决

    目录 引言 怎么分配和释放内存? 出现 double free or corruption Error 内存被释放之后会发生什么? 常见的触发情形 如何避免 引言 写过 C/C++ 的都知道,内存允许程序员自主分配,用完了这些资源也得释放出来,这种在系统运行过程中动态申请的内存,称为动态内存. 常言道,借东西好借好还,下次再借也不难,但是有的人有时候还真的忘了还回去.这要是发生在程序运行时,申请的内存没正常释放,没管理好,就避免不了会面对内存报错的问题. 内存都允许你自由操纵了,灵活性是真的大,

  • C/C++题解LeetCode1295统计位数为偶数的数字

    目录 题目描述 思路分析 AC 代码 将int转为String 代码 3种方法 - 统计位数为偶数的数字 题目描述 1295. 统计位数为偶数的数字 - 力扣(LeetCode) 给你一个整数数组 nums,请你返回其中位数为 偶数 的数字的个数. 示例 1: 输入:nums = [12,345,2,6,7896] 输出:2 解释: 12 是 2 位数字(位数为偶数) 345 是 3 位数字(位数为奇数) 2 是 1 位数字(位数为奇数) 6 是 1 位数字 位数为奇数) 7896 是 4 位数

  • C/C++中的OpenCV读取视频与调用摄像头

    目录 OpenCV读取视频与调用摄像头 读取视频 播放视频 调用摄像头 这是读取文件然后进行播放 下面是打开摄像头的代码 Opencv读取视频以及打开摄像头以及视频读取失败原因 1.打开摄像头 2.视频读取 3.视频读取失败原因 OpenCV读取视频与调用摄像头 读取视频 1.先实例化再初始化 VideoCapture capture; Capture.open("1.avi"); 2.实例化的同时进行初始化 VideoCapture capture("1.avi"

  • 详解C/C++实现各种字符转换方法合集

    目录 一.std::string 和 std::wstring 互转 1.直接声明std::wstring 2.wstring_convert 3.WideCharToMultiByte和MultiByteToWideChar 二.winrt::hstring 和 std::string 互转 三.const char* 和 char* 互转 1.const char*转char* 2. char*转const char* 四.QString 和 std::string 互转 补充 CStrin

  • Python脚本开发中的命令行参数及传参示例详解

    目录 sys模块 argparse模块 Python中的正则表达式 正则表达式简介 Re模块 常用的匹配规则 sys模块 在使用python开发脚本的时候,作为一个运维工具,或者是其他工具需要在接受用户参数运行时,这里就可以用到命令行传参的方式,可以给使用者一个比较友好的交互体验. python可以使用 sys 模块中的 sys.argv 命令来获取命令行参数,其中返回的参数是一个列表 在实际开发中,我们一般都使用命令行来执行 python 脚本 使用终端执行python文件的命令:python

  • axios封装与传参示例详解

    1.开发环境 vue+typescript 2.电脑系统 windows10专业版 3.在开发的过程中,我们会经常使用到 axios进行数据的交互,下面我来说一下,axios封装和传参! 4-1:下面结构如下: 4-2:request.js代码如下: import axios from 'axios' import qs from 'qs' axios.defaults.timeout = 2000000; //响应时间 axios.defaults.headers.post['Content-

  • 微信小程序实现页面导航与传参功能详解

    目录 一.页面导航 概述 分类 声明式导航 导航到tabBar页面 导航到非tabBar页面 后退导航 编程式导航 导航到tabBar页面 导航到非tabBar页面 后退导航 导航传参 声明式导航传参 编程式导航传参 一.页面导航 概述 顾名思义,页面导航指的是页面之间的相互跳转, 而页面传参就是在加载页面时将特定的参数传递过去从而成为该页面的参数. 分类 声明式导航:在页面上声明一个<navigator>导航组件,通过点击该组件实现页面跳转 编程式导航:通过调用小程序专门的导航API,实现页

  • 微信小程序 动态传参实例详解

    微信小程序 动态传参实例详解 在微信小程序的开发过程中经常会用到动态传参,比如根据某一页面传参的不同,加载不同的新的页面.接下来介绍下如何实现. 上一篇博客中介绍了如何用wx:for循环显示数组,一般情况下我们要实现的功能是点击不同的元素进入不同的页面,比如在另一个页面加载某个元素的详细信息. 跳转这里采用navigator跳转,在navigator跳转的链接上将参数加上去: index.wxml(根据点击页面的不同传递参数) <view class="item" wx:for=

  • 对python实现二维函数高次拟合的示例详解

    在参加"数据挖掘"比赛中遇到了关于函数高次拟合的问题,然后就整理了一下源码,以便后期的学习与改进. 在本次"数据挖掘"比赛中感觉收获最大的还是对于神经网络的认识,在接近一周的时间里,研究了进40种神经网络模型,虽然在持续一周的挖掘比赛把自己折磨的惨不忍睹,但是收获颇丰.现在想想也挺欣慰自己在这段时间里接受新知识的能力.关于神经网络方面的理解会在后续博文中补充(刚提交完论文,还没来得及整理),先分享一下高次拟合方面的知识. # coding=utf-8 import

  • Go语言基础函数基本用法及示例详解

    目录 概述 语法 函数定义 一.函数参数 无参数无返回 有参数有返回 函数值传递 函数引用传递 可变参数列表 无默认参数 函数作为参数 二.返回值 多个返回值 跳过返回值 匿名函数 匿名函数可以赋值给一个变量 为函数类型添加方法 总结 示例 概述 函数是基本的代码块,用于执行一个任务 语法 函数定义 func 函数名称( 参数列表] ) (返回值列表]){ 执行语句 } 一.函数参数 无参数无返回 func add() 有参数有返回 func add(a, b int) int 函数值传递 fu

  • React.memo函数中的参数示例详解

    目录 React.memo?这是个啥? React.memo的第一个参数 父组件 子组件 React.memo优化 React.memo的第二个参数 父组件 子组件 React.memo优化 父组件 子组件 小结 React.memo?这是个啥? 按照官方文档的解释: 如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现.这意味着在这种情况下,React 将跳过渲染组件的操作并直

  • Node.js实现分片上传断点续传示例详解

    目录 正文 文件的分片与合并 并发控制 使代码可复用 服务端接口实现 正文 大文件上传会消耗大量的时间,而且中途有可能上传失败.这时我们需要前端和后端配合来解决这个问题. 解决步骤: 文件分片,减少每次请求消耗的时间,如果某次请求失败可以单独上传,而不是从头开始 通知服务端合并文件分片 控制并发的请求数量,避免浏览器内存溢出 当因为网络或者其他原因导致某次的请求失败,我们重新发送请求 文件的分片与合并 在JavaScript中,FIle对象是' Blob '对象的子类,该对象包含一个重要的方法s

  • Python函数定义及传参方式详解(4种)

    一.函数初识 1.定义:将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可. 2.好处:代码重用:保持一致性:可扩展性. 3.示例如下: # -*-coding:utf-8-*- def sayHello(): print('Hello world!') sayHello() 二.函数传参方式 如上面的实例是函数中最基础的一种,是不传参数的,说到这里,我们有必要了解一下何为函数参数: 1.函数参数: 形参变量: 只有在被调用时才分配内存单元,调用结束时,即刻释

  • Vue + Axios 请求接口方法与传参方式详解

    目录 一.Get请求: 二.Post请求: 三.拓展补充 使用Vue的脚手架搭建的前端项目,通常都使用Axios封装的接口请求,项目中引入的方式不做多介绍,本文主要介绍接口调用与不同形式的传参方法. 一.Get请求: Get请求比较简单,通常就是将参数拼接到url中 用? &连接或者用下面这种方式: this.axios.get(this.getWxQyUserInfoUrl, { params: { agentid: this.doLoginParams.agentid, code: this

随机推荐