CPU阿甘:函数调用的秘密

半小时漫画计算机 以漫画的形式来讲解计算机的基础知识, 主要涉及到CPU、内存、网络的原理; 操作系统的“一切皆文件”宗旨; 函数调用的秘密; 数据库帝国; 各大编程语言的对比; 整体来书还是不错的,但是原理性的东西还是比较强, 虽然以漫画形式写出来,语言也更通俗易懂, 但是如果要面向小学生的话举例可以更丰富些, 当讲解到复杂的原理时可以多去考虑小学生遇到的问题加以佐证, 让他们了解到的知识再去丰富。 阅读详情

我是CPU阿甘, 上次我给大家承诺过,要讲一讲函数调用的秘密, 这个确实有点复杂, 想透彻的理解机器代码层面的函数调用不容易。


我也是从无数的指令中悟出这个函数调用的秘密的,  所以慢慢来,不要急。 放松心情, 慢慢的品味, 你可能需要多看几遍才能明白。

但是你一旦理解了,绝对物超所值,因为你会了解到汇编,寄存器,指针,以及他们在一起到底是怎么工作的。

首先, 一个程序一条一条的指令都的老老实实的放在内存的一个地方,这个地方是Linux老大分配的, 我干涉不了, 但是这些指令都是我打电话给硬盘, 让他给运输到内存的。 


然后Linux老大就会告诉我程序的入口点, 其实就是第一条指令的存放地址,  我就打电话问内存要这个指令, 取到指令以后就开始执行。

这些指令当中无非有这么几类:

1. 把数据从内存加载我的寄存器里

什么? 你不知道啥是寄存器?  寄存器就是我内部的一个临时的数据存储空间了

2. 对寄存器的数据进行运算, 例如把两个寄存器的数加起来

3. 把我寄存器的数据再写到内存里


但是我一旦遇到像这样的指令。 

"把寄存器ebp的值压到栈里去“

我就知道好戏要上场了, 函数调用就会开始。 


我们这些x86体系的机器有个特点,就是每个函数调用都会创建一个所谓的“帧”


哈哈, 不要被这些术语吓坏, 其实帧也就是我哥们内存中的一段连续的空间而已。

像这样: 

0?wx_fmt=png

多个函数帧在内存里排起来, 就像一个先进后出的栈一样, 不过,这个栈不像我们常见的栈, 栈底在下面。

相反,这个栈的栈底在上面, 是从上往下生长的 (或者说是从高地址向低地址生长的)


内存经常向我抱怨: "阿甘,你知道吗, 每次我看到这个栈, 都有一种真气逆行的感觉, 半天都调整不过来 " 


但内存不知道, 我有一个叫ebp的特殊寄存器, 一直会指向当前函数在一个栈的开始地址。 

我还有另外一个特殊寄存器,叫做esp , 他会随着指令的运行,指向函数帧的最后的地址, 像这样:

0?wx_fmt=png

现在这个指令来了:

"把寄存器ebp的值压到栈里去“

"把esp的值赋给ebp"


0?wx_fmt=png

你看看, 是不是新的函数帧生成了?

只不过现在只有一行数据。 ebp和esp指向同一地址。

函数帧的第一行的地址是800,  里边的内容是1000, 也就是上个函数帧的地址


注意, 我们每次操作的是4个字节,所以原来esp 的地址是804, 现在变成了800

我又问内存要下一条指令:

"把esp 的值减去24”


0?wx_fmt=png

下面几条指令是这样的:

“把10放到ebp 减去4的地址” (其实就是796嘛)

“把20放到ebp减去8的地址” (其实就是792嘛)


0?wx_fmt=png

你们知道这是干什么吗? 

我想了好久才明白这是干嘛, 这其实就是在分配函数的局部变量啊

我猜源代码应该是这样的:

int x = 10;

int y = 20;

在我看来, x, y 只是变量, 他们叫什么根本不重要, 重要的是他们的值和地址!

下面几条指令很有意思:

" 把地址796作为数据放到 esp指向的地址“ (其实就是776嘛)

" 把地址792作为数据放到 esp+4指向的地址" (其实就是780嘛)

0?wx_fmt=png

这又是在干嘛?

这其实就相当于把 x 的指针 &x和 y 的指针 &y ,放到了特定的地方, 准备着要做什么事情 , 可能要调用函数了。

所以,所谓的指针就是地址而已。

我猜程序员写的代码应该是这样:

int x = 10;

int y = 20;

int sum= add(&x, &y); 

接下来的指令是这样:

“调用函数 add”

我看到这样的函数就需要特别小心, 因为我必须要找到 add函数返回以后的那条指令的地址, 把它也压到栈里去。

int x = 10;

int y = 20;

int sum = add(&x, &y); 

printf("the sum is %d\n",sum); 假设这条指令的地址是100

0?wx_fmt=png

注意啊, 把函数调用结束的以后的返回地址100压入栈以后, esp 也发生变化了, 指向了772的位置

我会找到函数Add 的指令,继续执行

"把寄存器ebp的值压到栈里去“

"把esp的值赋给ebp"

"把寄存器ebx的值压入栈”

你看每个函数的开始指令都是这样, 我猜这应该是一种约定吧

这里额外把ebx这个寄存器压入栈, 是因为ebx可能被上个函数使用, 但是在add函数中也会用 , 为了不破坏之前的值, 只有先委屈一下暂时放到内存里吧。

0?wx_fmt=png

接下来的指令是:

“把ebp 加8的数据取出来放到 edx 寄存器” (ebp+8 不就是地址776嘛, 其中存放的是&x的地址, 这就是取参数了)


“把ebp 加12的数据取出来放到 ecx 寄存器” (ebp+12 不就是地址780嘛, 其中存放的是&y的地址)


注意啊, 现在edx的值是796, ecx的值是792 , 但他们仍然不是真正的数据, 而是指针(地址)!


“把edx 指向的内存地址(796)的数据取出来,放到ebx 寄存器”


“把ecx 指向的内存地址(792)的数据取出来,放到eax寄存器” 


此时此刻, 终于取到了真正的值, ebx = 10, eax = 20

你晕了没有?  

如果你到此已经晕了, 建议你再读一遍。  

我想源代码应该非常的简单,就是这样:

int add(int *xp , int *yp){

    int x = *xp;

    int y = *yp;

    ....

}

“把ebx 和 eax 的值加起来,放到 eax寄存器中” 

这个指令我最擅长做了。

接下来的指令也很关键, add 函数已经调用完成, 准备返回了 

 “把esp 指向的数据弹出的ebx寄存器”

“把esp 指向的数据弹出到ebp寄存器”


0?wx_fmt=png

你看add 函数帧已经消失了, 或者换句话说, add 函数帧的数据还在内存里, 只是我们不在关心了!

接下来的指令非常的关键:

"返回"

我就会取出那个返回地址, 也就是 100, 去这里找指令接着执行

其实就是这条语句: printf("the sum is %d\n",sum);

问你一个问题, sum的值在那里保存着呢? 

对, 是在eax寄存器里 !


搞定了,看着很复杂, 其实看透了也挺简单吧。 函数调用,关键就是

(1)把参数和返回地址准备好, 

(2)然后大家都遵循约定, 每次新函数都要建立新的函数帧:

   "把寄存器ebp的值压到栈里去“

    "把esp的值赋给ebp"

(3) 函数调用完了, 重置 ebp 和esp ,让他们重新指向调用着的栈帧。


好了,今天就到此为止 , 把我也累坏了,  主人又要关机了, 留一个问题吧: 


C语言编译,链接以后直接就是机器码, 那函数调用的操作都是上面讲的。 

但是对于Python, Ruby 这样的解释型语言, 或者对于java 这样的有虚拟机的语言, 他们的函数调用是什么样的?  和上面讲的有什么关系?


0?wx_fmt=jpeg

公众号:码农翻身

“码农翻身”公众号由工作15年的前IBM架构师创建,分享编程和职场的经验教训。

什么是信噪比?信噪比越大好还是越小好? 在进行声音增强的时候,往往需要考虑声音信号和噪声的信噪比,那么信噪比是什么呢?越大越好还是越小越好,此处引入百度百科的解读: 信噪比,英文名称叫做SNR或S/N(SIGNAL-NOISE RATIO),又称为讯噪比。是指一个电子设备或者电子系统中信号与噪声的比例。这里面的信号指的是来自设备外部需要通过这台设备进行处理的电子信号,噪声是指经过该设备后产生的原信号中并不存在的无规则的额外信号(或信息),并且该种信号并不随原信号的变化而变化。 同样是“原信号不存在”还有一种东西叫“失真”,失真和噪声实际上有一 阅读详情

相关推荐

基于CNN的表情识别(FER)pytorch实现

Report for FER Jian Tang Abstract 我们使用卷积神经网络(CNN)实现了baby的面部表情识别(FER)。 数据上,我们获取了开源FER数据集FER2013[1],besides我们使用爬虫获取了baby相关的人脸图片,进行了手工分类标注以形成一个全新的baby FER数据集。 模型上,我们首先复现了Luan[2]的工作,即参考Kuo[3]在CVPR 2018上的工作并实现一个轻量的CNN网络,在FER2013上进行训练并得到了XXXX%的平均分类准确率(avg a

qq_20241587的博客 3683

漫画 | CPU就是一个悲惨的搬运工

在一个庞大机房的角落里,存放着一台普普通通的服务器。每天一开机,里边的居民就开始忙活起来了。大家都要在操作系统老大的指挥下干活。日子一天天地过去,这一天,大家正在干活,外边突然传来了咔嗒一...

码农翻身 3811

《Hadoop大数据技术原理与应用》(第2版)黑马程序员的课后习题答案

《Hadoop大数据技术原理与应用》(第2版)黑马程序员的课后习题答案

qq_36773158的博客 4646

CPU阿甘

1、CPU视角看计算机启动过程(见 CPU阿甘——码农翻身) 2、CPU视角看程序装载运行过程(见 CPU阿甘之烦恼——码农翻身)     批处理系统:可以理解为只能串行执行每个程序的系统。“批”体现在很多操作封装在一个程序里,然后提交给系统去运行。(尼玛取这名真误导人,咋看以为“批”多个程序能并发处理,还不如叫串行执行系统..) 多道程序处理系统:就是能提交多道程序且让它们并发运行的系...

weixin_33976072的博客 163

STM32F407+LAN8720网口不通?别慌,手把手教你用CubeMX和LWIP搞定RMII以太网(附复位与PHY地址避坑指南)

本文详细解析了STM32F407与LAN8720通过RMII接口实现以太网通信的完整流程,重点解决了网口不通的常见问题。从硬件连接到CubeMX配置,再到LWIP网络参数设置,提供了复位时序、PHY地址配置等关键细节的避坑指南,帮助开发者快速排查和解决以太网通信故障。

weixin_33695082的博客 419

eNSP中玩转Python自动化——解锁网工新姿势(一)

Python自动化一、安装Paramiko模块二、搭建实验环境1、桥接电脑网卡2、配置交换机IP地址交换机配置测试ensp的交换机与电脑的连通性:交换机配置SSHXshell验证三、Python脚本1、执行python脚本2、同步成功 一、安装Paramiko模块 首先安装Python3 然后安装pip解释器 最后安装Paramiko模块 Windows+R打开Cmd,输入命令: pip3 install paramiko 进入python,导入 paramiko模块: import paramiko

LiBai'S BLOG 2万+

函数栈帧详解

函数栈帧详解

m0_74268677的博客 2563

OMAPL138使用UPP读写测试

UPP测试代码 #include "hw_types.h" // 宏命令 #include "hw_syscfg0_OMAPL138.h" // 系统配置模块寄存器 #include "soc_OMAPL138.h" // DSP C6748 外设寄存器 #include "psc.h" // 电源与睡眠控制宏及设备抽象层函数声明 #include "interrupt.h" // DSP C6748 中断相关应用程序接...

wangjie36的博客 1193

函数栈帧(函数调用执行过程剖析)

函数栈帧,(函数调用执行过程剖析)

weixin_65743593的博客 1856

华为OD机试真题 新系统 9月16号 【园区基站节能部署规划】

华为OD机试真题 新系统【园区基站节能部署规划】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题 【园区基站节能部署规划】多语言题解。

qq_45776114的博客 356

LeetCode 热题 100 回顾

力扣最受刷题发烧友欢迎的 100 道题 LeetCode 热题 100 : 题目回顾、解题思路、复杂度分析、代码实现、验证算法

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 9万+

浅析Intel处理器体系结构:函数调用实现

函数调用是现代绝大多数编程语言实现所依赖的基础抽象机制。函数调用通过使用一组指定的参数和可选的返回值实现了某种功能,然后,可以在程序的不同地方进行调用这个过程以实现特定的需求。为了实现函数调用,各个硬件体系都约束了在函数调用过程需要遵循的一系列规则,包括控制转移、硬件寄存器使用、参数传递以及返回值处理等,本文着重关注于Intel体系结构下的函数调用机制实现。

Aspiresky的专栏 895

WblockCloneObjects克隆一个图纸内的实体包括块参照,同名块参照的处理

public static void HeBingTu2(double lieJu, List<string> filePaths) { Document doc = Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; using (DocumentLock docLock = doc.Lock...

1108

码农翻身之大话编程篇:11 CPU阿甘:函数调用的秘密

      我是CPU阿甘,上次我给大家承诺过,要讲一讲函数调用的秘密,这个确实有点复杂,想透彻的理解机器代码层面的函数调用不容易。       我也是从无数的指令中悟出这个函数调用的秘密的, 所以慢慢来,不要急。放松心情,慢慢的品味, 你可能需要多看几遍才能明白。但是你一旦理解了,绝对物超所值,因为你会了解到汇编,寄存器,指针,以及他们在一起到底是怎么工作的。       首先,一个程序一条一条...

希望变强的小菜鸟的博客 330

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

码农翻身 各章节链接

大话编程 我是一个线程 我是一个Java class Javascript: 一个屌丝的逆袭 Java:一个帝国的诞生 JSP:一个装配工的没落 TCP/IP 之 大明王朝的邮差 TCP/IP 之 大明内阁 TCP/IP 之 蓟辽督师 CPU 阿甘 CPU 阿甘之烦恼 CPU 阿甘:函数调用的秘密 我是一个网卡 我是一个路由器 我是一个进程 我是一块硬盘(上) 我是...

wudinaniya的博客 4万+

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

《码农翻身》

大话编程 我是一个线程 我是一个Java class Javascript: 一个屌丝的逆袭 Java:一个帝国的诞生 JSP:一个装配工的没落 TCP/IP 之 大明王朝的邮差 TCP/IP 之 大明内阁 TCP/IP 之 蓟辽督师 CPU 阿甘 CPU 阿甘之烦恼 CPU 阿甘:函数调用的秘密 我是一个网卡 我是一个路由器 我是一个进程 我是一块硬盘(上) 我...

风不止步的博客 8739

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

《码农翻身》各章节阅读连接

大话编程 我是一个线程 我是一个Java class Javascript: 一个屌丝的逆袭 Java:一个帝国的诞生 JSP:一个装配工的没落 TCP/IP 之 大明王朝的邮差 TCP/IP 之 大明内阁 TCP/IP 之 蓟辽督师 CPU 阿甘 CPU 阿甘之烦恼 CPU 阿甘:函数调用的秘密 我是一个网卡 我是一个路由器 我是一个进程 我是一块硬盘(上) 我是...

sinat_35960326的博客 9116

操作系统是个大骗子?

&#13; &#13; &#13; &#13; &#13; &#13; &#13; 1聊天我们这些程序都安安静静地躺在硬盘的某个角落中,满心期待地等待被主人使用,被操作系统装载, 然后...

Linux阅码场 1599

码农翻身讲操作系统2:进程,线程与操作系统那些事

我听说我的祖先们生活在专用计算机里, 一生只帮助人类做一件事情,比说微积分运算 了、人口统计了 、生成密码、甚至通过织布机印花 ! 如果你想在这些专用“计算机”上干点别的事儿,例如安装个游戏玩玩, 那是绝对不可能的, 除非你把它拆掉, 然后建一个全新的机器。 而我这些祖先们勉强可以称为“程序”。 后来有个叫冯诺依曼的人, 非常了不起, 他提出了“存储程序”的思想, 并且把计算机分为五大部...

Java技术江湖 2036

《码农翻身》各章节阅读链接

大话编程 我是一个线程 我是一个Java class Javascript: 一个屌丝的逆袭 Java:一个帝国的诞生 JSP:一个装配工的没落 TCP/IP 之 大明王朝的邮差 TCP/IP 之 大明内阁 TCP/IP 之 蓟辽督师 CPU 阿甘 CPU 阿甘之烦恼 CPU 阿甘:函数调用的秘密 我是一个网卡 我是一个路由器 我是一个进程 我是一块硬盘(上) 我是一块硬盘(下) 我是一个键盘 ...

Ryze 2016

看半小时漫画,通晓计算机基础

转眼间,距离《码农翻身》的出版已经过去三年了,很多读者读完后意犹未尽,经常催促作者:下一本书什么时候出啊? 现在它终于来了,和第一本书不同的是,这本书完成了一次华丽的升级: 从用故事讲解技术,升级为用漫画讲解技术。 为了更好的阅读体验,本书排版设计了近10个版本,设计师换了3个,反复修改,就为了实现好的阅读效果。 为了防止纸质效果和电脑上看的效果不一致,还特意快印了四五次。 相比于文字,漫画形式更有视觉冲击力,更加有趣,受众也更广,连作者上小学的女儿也会被其中的一些章节逗得哈哈大笑——虽然她看不懂其中的内

博文视点(北京)官方博客 1890

我是一个进程

我听说我的祖先们生活在专用计算机里, 一生只帮助人类做一件事情,比说微积分运算 了、人口统计了 、生成密码、甚至通过织布机印花 ! 如果你想在这些专用“计算机”上干点别的事儿,例如安装个游戏玩玩, 那是绝对不可能的, 除非你把它拆掉, 然后建一个全新的机器。 而我这些祖先们勉强可以称为“程序”。后来有个叫冯诺依曼的人, 非常了不起, 他提出了“存储程序”的思想, 并且把计算机分为五大部件: 运算

在努力! 2017
上一篇: 让自己与众不同 - 提升工作的价值
下一篇: Node.js : 我只需要一个店小二
码农翻身
码农翻身 《码农翻身》作者 《码农翻身》作者
博客等级 码龄8年 3万+粉丝 1084原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值