x86-64 寄存器只有6个寄存器来存参数,那 C 函数为什么还能超过6个参数

简介: x86-64 寄存器只有6个寄存器来存参数,那 C 函数为什么还能超过6个参数
#include <stdio.h>

int test(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10) {
    return arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10;
}

int main() {
    printf("%d", test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    return 0;
}
000000000040052d <test>:
  40052d:    55                       push   %rbp
  40052e:    48 89 e5                 mov    %rsp,%rbp
  400531:    89 7d fc                 mov    %edi,-0x4(%rbp)
  400534:    89 75 f8                 mov    %esi,-0x8(%rbp)
  400537:    89 55 f4                 mov    %edx,-0xc(%rbp)
  40053a:    89 4d f0                 mov    %ecx,-0x10(%rbp)
  40053d:    44 89 45 ec              mov    %r8d,-0x14(%rbp)
  400541:    44 89 4d e8              mov    %r9d,-0x18(%rbp)
  400545:    8b 45 f8                 mov    -0x8(%rbp),%eax
  400548:    8b 55 fc                 mov    -0x4(%rbp),%edx
  40054b:    01 c2                    add    %eax,%edx
  40054d:    8b 45 f4                 mov    -0xc(%rbp),%eax
  400550:    01 c2                    add    %eax,%edx
  400552:    8b 45 f0                 mov    -0x10(%rbp),%eax
  400555:    01 c2                    add    %eax,%edx
  400557:    8b 45 ec                 mov    -0x14(%rbp),%eax
  40055a:    01 c2                    add    %eax,%edx
  40055c:    8b 45 e8                 mov    -0x18(%rbp),%eax
  40055f:    01 c2                    add    %eax,%edx
  400561:    8b 45 10                 mov    0x10(%rbp),%eax
  400564:    01 c2                    add    %eax,%edx
  400566:    8b 45 18                 mov    0x18(%rbp),%eax
  400569:    01 c2                    add    %eax,%edx
  40056b:    8b 45 20                 mov    0x20(%rbp),%eax
  40056e:    01 c2                    add    %eax,%edx
  400570:    8b 45 28                 mov    0x28(%rbp),%eax
  400573:    01 d0                    add    %edx,%eax
  400575:    5d                       pop    %rbp
  400576:    c3                       retq

0000000000400577 <main>:
  400577:    55                       push   %rbp
  400578:    48 89 e5                 mov    %rsp,%rbp
  40057b:    48 83 ec 20              sub    $0x20,%rsp
  40057f:    c7 44 24 18 0a 00 00     movl   $0xa,0x18(%rsp)
  400586:    00
  400587:    c7 44 24 10 09 00 00     movl   $0x9,0x10(%rsp)
  40058e:    00
  40058f:    c7 44 24 08 08 00 00     movl   $0x8,0x8(%rsp)
  400596:    00
  400597:    c7 04 24 07 00 00 00     movl   $0x7,(%rsp)
  40059e:    41 b9 06 00 00 00        mov    $0x6,%r9d
  4005a4:    41 b8 05 00 00 00        mov    $0x5,%r8d
  4005aa:    b9 04 00 00 00           mov    $0x4,%ecx
  4005af:    ba 03 00 00 00           mov    $0x3,%edx
  4005b4:    be 02 00 00 00           mov    $0x2,%esi
  4005b9:    bf 01 00 00 00           mov    $0x1,%edi
  4005be:    e8 6a ff ff ff           callq  40052d <test>
  4005c3:    89 c6                    mov    %eax,%esi
  4005c5:    bf 70 06 40 00           mov    $0x400670,%edi
  4005ca:    b8 00 00 00 00           mov    $0x0,%eax
  4005cf:    e8 3c fe ff ff           callq  400410 <printf@plt>
  4005d4:    b8 00 00 00 00           mov    $0x0,%eax
  4005d9:    c9                       leaveq
  4005da:    c3                       retq
  4005db:    0f 1f 44 00 00           nopl   0x0(%rax,%rax,1)

关于函数的调用栈,https://www.cnblogs.com/clover-toeic/p/3755401.html 这篇文章写得挺好。

push %rbp

把 caller(相对于当前函数的调用函数)的函数栈的栈底的内存地址(存在 rbp 寄存器中)压栈(写入到栈上),注意会占用8字节的内存长度。
因为C程序的入口函数是_start,然后再经过一系列初始化函数,才到main,所以这里main里面也需要执行push %rbp,又比如test函数里面的push %rbp就是main函数的%rbp里记录的地址。
栈是由高位向低位生长,所以此时是在调用函数的rsp 基础上向低位移动,又因为x86-64机器上,地址占8字节,所以push %rbp的背后还有一个隐含的操作就是sub $0x8 %rsp(如果是32机器就是向下移动4字节)。

mov    %rsp,%rbp

把 caller 的栈顶的内存地址(存在 rsp 寄存器中)赋值到 rbp 寄存器。

sub    $0x20,%rsp

把rsp再减去0x20,也就是地址再向下移动32个单元(字节),用于main函数局部变量和参数的存储。这里预留少空间,在编译期就计算好了。当前实验是10个参数,如果我们改为13个参数,则会是sub $0x50,%rsp,所以生成的汇编代码,在运行之前就已经确定了rsp偏移量。

movl   $0xa,0x18(%rsp)
movl   $0x9,0x10(%rsp)
movl   $0x8,0x8(%rsp)
movl   $0x7,(%rsp)

参数的压栈是从右向左的,最后面的参数最先处理,我们知道寄存器里存储函数参数的只有6个,所以还需要使用栈上的空间来存储其他参数。把0xa放在rsp+0x18的地址上,然后把0x9放在rsp+0x10的地址上,以此类推。

mov    $0x6,%r9d
mov    $0x5,%r8d
mov    $0x4,%ecx
mov    $0x3,%edx
mov    $0x2,%esi
mov    $0x1,%edi

把参数从右向左进行处理,剩余的6个则有专门的寄存器来存储。

callq  40052d <test>

调用test函数,背后隐含的一个操作是把调用test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)下一条指令mov %eax,%esi的地址压栈,这样rsp的地址会向下偏移8字节。

mov    %eax,%esi

把test的返回值,放入esi作为printf的第二个参数

mov    $0x400670,%edi

把 0x400670 作为第一个参数传入printf,0x400670 则是%d字符串常量,在只读取的地址

具体的之前在 https://mengkang.net/1383.html 的实验中做过说明

leaveq

这个指令是函数开头的push %rbp和mov %rsp,%rbp的逆操作。把rbp赋值给rsp,然后把rsp+0x8

retq

retq指令,它是callq指令的逆操作。在上面执行leaveq之后,再把0x8(rsp)里保存的调用函数的下一行代码的地址取出来赋值给rip(rip程序计数器,负责从哪里开始执行代码),然后再次rsp+0x8,就从test恢复到main里面了。

test函数的操作就比较简单了,就是把寄存器的值压栈,然后相加计算,返回。

WX20190914-071635@2x.png

目录
相关文章
|
Linux 虚拟化
VMware虚拟机 用共享文件夹方式 与主机传输文件(图文)
VMware虚拟机 用共享文件夹方式 与主机传输文件(图文)
VMware虚拟机 用共享文件夹方式 与主机传输文件(图文)
|
10月前
|
人工智能 运维 达摩院
2025 必看!5 款主流 Agent 工具,让重复工作效率提升 25 倍
2025年,Agent智能体已成职场“数字同事”,85%企业用于自动化办公。相比普通AI,Agent能自主拆解任务、跨系统操作,真正实现“听懂目标就执行”。本文盘点五大主流Agent工具:实在智能·实在Agent(企业办公)、亚马逊客服Agent、达摩院“达医智影”、工业智能体、个人助理Agent,覆盖办公、医疗、制造等核心场景,并提供选型五大标准,助你高效解放双手,抢占智能工作先机。
4251 16
|
前端开发 网络协议 Dubbo
超详细Netty入门,看这篇就够了!
本文主要讲述Netty框架的一些特性以及重要组件,希望看完之后能对Netty框架有一个比较直观的感受,希望能帮助读者快速入门Netty,减少一些弯路。
97799 33
超详细Netty入门,看这篇就够了!
|
机器学习/深度学习 数据采集 人工智能
Phi-3 技术报告:手机本地运行的高能力语言模型
Phi-3系列模型通过高质量数据训练与架构创新,实现小体积、高性能。38亿参数的phi-3-mini在手机端可达GPT-3.5水平,支持长上下文、多模态与高效推理,推动AI普惠化。
1241 1
|
9月前
|
人工智能 JavaScript API
零门槛部署 Windows 系统本地及云上 AI 助手:OpenClaw(原 Clawdbot/Moltbot)保姆级教程
2026年初,一款名为OpenClaw的AI助手在开发者社区迅速走红,它前身为Clawdbot、Moltbot,经过品牌整合后统一命名为“OpenClaw”。这款AI助手并非传统意义上的聊天工具,而是具备“动手执行”能力的智能体——既能读写本地文件、执行代码、操控命令行,又能联网搜索、分析网页内容,还可接入Qwen、OpenAI等云端API或利用本地GPU运行模型,像“私人AI员工”般处理各类事务。
7883 9
|
设计模式 前端开发 调度
前端必须掌握的设计模式——工厂模式
工厂模式是一种创建型设计模式,通过工厂媒介提供统一接口来创建对象,客户端只需告知创建需求,具体逻辑由工厂处理。工厂模式分为简单工厂、标准工厂和抽象工厂,分别适用于不同场景下的对象创建需求。简单工厂利用静态方法创建对象,标准工厂通过具体工厂类减少耦合,抽象工厂则用于创建一系列相关或依赖对象的家族。
前端必须掌握的设计模式——工厂模式
|
机器学习/深度学习 人工智能 自然语言处理
《鸿蒙Next:让人工智能语音交互听懂每一种方言和口音》
鸿蒙Next系统通过丰富方言语音数据、优化语音识别模型、引入语音合成技术及用户反馈机制,大幅提升对不同方言和口音的识别能力。具体措施包括多渠道收集方言数据、建立动态数据库、采用深度学习算法、实现多任务学习与对抗训练、生成标准方言样本,并结合硬件如麦克风阵列技术优化语音输入质量。这些综合手段确保了语音交互的准确性和实时性,为用户提供更智能、便捷的服务。
1139 16
|
数据采集
IQR法(四分位距法)
IQR法(四分位距法)
|
Linux 容器 Kubernetes
环境异常解决方案-CentOS 7 网络异常【Failed to start LSB: Bring up/down networking】
问题呈现 今天启动Kubernetes集群的时候发现部署的zookeeper集群有一个节点始终启动不成功,然后查看了一下日志发现报错信息是【getsockopt: no route to host】: error 根据以前的经验,这多半是...
10751 0