当前位置:首页 > 公众号精选 > 嵌入式微处理器
[导读] 先抛出几个问题: 进程虚拟地址空间是如何分布的? 函数调用的栈帧结构是什么样子? 函数调用涉及到的寄存器都起了什么作用? 函数参数是如何传递的?传递顺序如何? 函数的返回值是如何传递的? 如果您对上述问题有些困惑,请继续往下看吧! 进程的内存布局


 先抛出几个问题:

  • 进程虚拟地址空间是如何分布的?

  • 函数调用的栈帧结构是什么样子?

  • 函数调用涉及到的寄存器都起了什么作用?

  • 函数参数是如何传递的?传递顺序如何?

  • 函数的返回值是如何传递的?

如果您对上述问题有些困惑,请继续往下看吧!

进程的内存布局

如图:

高地址的一部分空间会分配给内核,称为内核空间,剩下的内存空间给用户使用,称为用户空间。

用户空间中有几个主要的内存区域

  • 栈:用于维护函数调用的上下文,离开了栈,函数调用就没法实现,栈通常在用户空间的最高地址处分配,通常有数兆字节的大小。

  • 堆:堆用来容纳程序动态分配的内存区域,程序中malloc或new分配的内存就来自堆里。堆通常存在于栈的下方(低地址方向),在某些时候,堆也可能没有固定统一的存储区域,堆一般比栈大很多,可以有百兆甚至几G的大小。

  • 动态链接库映射区:这个区域用于映射装载的动态链接库,Linux下如果可执行文件依赖其它共享库,那系统就会在这个区域分配相应空间,并将共享库装入该空间。

  • 可执行文件映像:存储着可执行文件在内存里的映像,由装载器在装载时将可执行文件的内存读取或映射到这里。

  • 保留区:保留区并不是一个单一的内存区域,而是堆内存中受到保护而禁止访问的内存区域的总称,例如在大多数操作系统里,极小的地址通常都是不允许访问的,如NULL,通常C语言将无效地址赋值为0也是出于这个考虑,因为0地址正常情况下不可能有有效的可访问数据。

函数调用的栈帧结构

我们都知道函数调用都是以栈帧为单位,机器通常用栈来传递函数参数、保存返回地址、保存寄存器(即函数调用的上下文)及存储本地局部变量等。

一个单独的栈帧结构如图所示:

为单个函数调用分配的那部分栈称为栈帧,栈帧的边界由两个指针界定:寄存器%ebp为帧指针,指向当前栈帧的起始处,通常较为固定;寄存器%esp为栈指针,指向当前栈帧的栈顶位置,当程序执行时,栈指针可以移动,因此大多数数据的访问都是相对于帧指针的。

一次函数调用的栈帧图如下:

寄存器使用约定

直接看图:


图片来源于网络,侵权删

上图表达的应该已经很清楚啦,简单示例解释一下,函数调用需要传递参数时,第一个参数存到%edi里,第二个参数会存到%esi里,如果有返回值会存到%eax里,这里如果是64位的返回值,会使用%rax。

函数的调用约定

这里主要涉及三种约定:

  • 函数参数的传递顺序和方式:这里可以有很多参数传递方式,栈传递和寄存器传递,函数的调用方将参数压入栈中,函数自己再从栈中将参数取出,需要规定压栈的顺序,是从左到右,还是从右到左,有的也使用寄存器传递,这都需要约定好。

  • 栈的维护方式:在函数将参数压栈后,函数体会被调用,此后需要将被压入栈中的参数全部弹出,以使得栈在函数调用前后保持一致,这个弹出的工作可以是由函数的调用方完成还是函数本身来完成需要约定好。

  • 名字修饰策略:为了链接的时候对调用约定进行区分,需要对函数本身的名字进行修饰,不同的调用约定有不同的名字修饰策略。一般都是前面加个下划线。

C语言默认的调用约定是cdecl方式,可以通过__attribute__((cdecl))标明使用cdecl约定,其实还有其它一些调用约定,如图:


函数的返回值传递

这里有几种情况:

4字节:当函数返回值是4个字节会通过%eax寄存器作为通道,函数将返回值存储在%eax中,返回后函数的调用方再读取%eax。

5-8个字节:通过rax寄存器作为通道。

大于8个字节:以如下代码举例:

struct A {// ...大于8字节 };A func() {A b;return b;}A x = func();

返回值传递方式如图:


  1. 调用函数首先在栈上额外开辟一片空间,作为临时对象(temp)

  2. temp作为隐藏参数传递给被调用函数

  3. 函数将数据拷贝给temp,同时%eax为指向temp的指针

  4. 返回返回后将%eax指向的temp拷贝回被赋予的对象

返回值类型的尺寸太大导致函数返回时,会开辟一段区域作为中介,返回值对象会被拷贝两次,而C++在有些情况下会做返回值优化,减少拷贝的次数,具体可以看我之前的文章:左值引用、右值引用、移动语义、完美转发,你知道的不知道的都在这里

参考资料

https://blog.csdn.net/slvher/article/details/8831885
https://blog.csdn.net/slvher/article/details/8831983
https://www.cnblogs.com/alantu2018/p/8465904.html
https://mp.weixin.qq.com/s/fpf4qRRLN3wVDUrWka3HfQ
https://mp.weixin.qq.com/s/j7SKtrMCmYs6g8yH75OH4A
https://www.sec4.fun/2018/05/29/stack/
https://murphypei.github.io/blog/2019/01/linux-heap
https://cloud.tencent.com/developer/article/1515763
《程序员的自我修养:链接装载与库》


本文授权转载自公众号“程序喵大人” ,作者程序喵大人

-END-




推荐阅读



【01】带你全面认识 Linux
【02】嵌入式必看:Linux内存管理那些事儿
【03】如何替换一个Linux内核函数的实现-热补丁原理
【04】10 大白帽黑客专用的 Linux 操作系统
【05】STM32好找工作,所以学linux终究是错付了吗?


免责声明:整理文章为传播相关技术,版权归原作者所有,如有侵权,请联系删除

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

嵌入式ARM

扫描二维码,关注更多精彩内容

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

Python由荷兰数学和计算机科学研究学会的吉多·范罗苏姆于1990年代初设计,作为一门叫做ABC语言的替代品。 Python提供了高效的高级数据结构,还能简单有效地面向对象编程。

关键字: python 函数 对象编程

测试数据综合分析的绝佳工具,深受工程师和研究员欢迎

关键字: 后处理分析软件 向导 函数

由上图中可以知道进程地址空间中最顶部的段是栈,代码中调用函数、定义局部变量(但不包含static修饰的变量)或声明的类的实例等等都要使用栈空间,当函数执行完(也就是程序执行超过了这个函数的作用范围的时候),操作系统会把该...

关键字: 进程地址 局部变量 函数

星标/置顶 公众号,硬核文章第一时间送达!链接| https://zhuanlan.zhihu.com/p/274473971题很多,先上题后上答案,便于大家思考问题点:1、C和C的特点与区别?2、C的多态3、虚函数实现...

关键字: 腾讯 函数 进程 AI

程序接口是操作系统为用户提供的两类接口之一,编程人员在程序中通过程序接口来请求操作系统提供服务。面向过程语言最基本的单元是过程和函数。

关键字: 程序接口 过程 函数

星标「嵌入式大杂烩」,一起进步!链接:https://www.cnblogs.com/jozochen/p/8541714.html一、问题复现稳定复现问题才能正确的对问题进行定位、解决以及验证。一般来说,越容易复现的问...

关键字: 嵌入式开发 函数 代码 寄存器

基本上,没有人会将大段的C语言代码全部塞入main()函数。更好的做法是按照复用率高、耦合性低的原则,尽可能的将代码拆分不同的功能模块,并封装成函数。C语言代码的组合千变万化,因此函数的功能可能会比较复杂,不同的输入,常...

关键字: 函数 PEN C语言代码 C语言程序

Part1一、让自己习惯C条款01:视C为一个语言联邦C并不是一个带有一组守则的一体语言:他是从四个次语言(C、Object-OrientedC、Template、STL) 组成的联邦政府,每个次语言都有自己的规约。记住...

关键字: 函数 ASPECT 编译器

为什么会写篇栈变化的文章?做系统分析的话你肯定遇到过一些crash,oops等棘手问题,一般大家都会用gdb,objdump或者addr2line等工具分析pc位置来定位出错的地方。但是这些分析工具背后的本质原理就不见得...

关键字: 函数 ARM C语言 AI

前言:一转眼从事前端已经6年了,从当时的小白到如今大厂的技术专家,中间也走过不少弯路,从今天开始我会持续更新前端技术文章,并且整体的文章会进行体系梳理,整个知识体系分为:基础精讲,框架讲解,框架及工具原理,前端面试题精讲...

关键字: 函数 GE FUNCTION APP
关闭
关闭