网站首页 > 技术文章 正文
一、进程的虚拟地址空间分布
代码在执行时就是系统当中的一个进程,对于一个32位操作系统来说,每一个系统进程拥有一个4G空间的虚拟内存。这些又分为内核空间和进程空间,低地址的0~3G给用户空间,高地址的3G~4G给内核空间。
1、内核空间
a、内核进程的数据:每个内核进程(线程)都有自己独特的PCB和页表,映射到不同的物理内存。以及进程的内核栈,这个是和PCB共享4K的大小空间;
b、内核代码段:所有进程的内核代码段都映射到同样的物理内存,并在内存中持续存在。
2、进程空间
a、环境变量:在最上面的是进程的命令行参数及环境变量;
b、用户栈:用于存储函数参数和局部变量,调用一个函数会将一个新的栈帧(stack frame)压入到栈中,这个栈帧会在函数返回时被清理掉;
c、内存映射段:常被用来加载共享库,有两种情况:
- 内存映射:将虚拟内存空间与磁盘上的文件关联起来,来初始化这个虚拟内存空间的内容,这个过程叫内存映射。
- 共享库:1)几乎每个程序都会用到如printf之类的标准I/O函数,如果只使用静态库,这些函数的代码将会被复制到正文段中,对于一个运行上百个进程的系统来说,这是一种对内存的浪费,所以提出共享库; 2)程序第一次执行时,用动态链接的方法将程序和共享库链接,减少了可执行文件的长度;
d、堆:用于运行时内存分配,但不同的是,堆用于存储那些生存期与函数调用无关的数据。在C语言中,堆分配的接口是malloc()函数。如果堆中有足够的空间来满足内存请求,它就可以被语言运行时库处理而不需要内核参与,否则,堆会被扩大,通过brk()系统调用来分配请求所需的内存块。
e、BSS段:存放未初始化的全局变量;
f、数据段:存放未初始化的全局变量;
g、代码段:存放代码和常量值(字面值常量);
二、寄存器传参方式
一般来说,参数传递有三种方式,1、全局变量;2、寄存器传参;3、栈传参;先分析前两种;
先看下我们编写的一个简单的C代码例子
由于后面我们后面还要研究按值传递和按引用传递的区别,所以,为了支持引用,需要采用g++ -S进行编译,生成的汇编代码如下:
可以看下main主函数里的栈数据分布:
可以看出局部变量的压栈顺序是,越先定义的变量,越是最后压栈,更靠近栈顶的位置,这个规则是约定俗成的,因为影响着被调用函数的取值顺序;
被调用函数my_add_by_value, 很明显是采用了esi->b, edi->a两个寄存器携带了两个变量值,传递给被调用函数;
如果进一步跟踪到最后的执行函数sub_add_11, 一样也是采用三个寄存器传参,如下:
奇怪的是,这个函数并没有扩充rsp的值,也就是直接在栈基址上减去偏移就使用了,我猜想是编译器能够识别是最后的函数,不需要考虑栈冲突的情况,如果你知道原因,可以在评论区留言讨论。
三、传值和传引用的区别
被调用函数先取传过来的地址值,再读取地址上的值即可;
说明一下的是函数最后的 leave 的作用相当:mov esp, ebp 和 pop ebp 这个是通用处理逻辑,与此类似的就是进入函数的通用指令 Enter 的作用相当 :push ebp 和 mov ebp, esp;
可以看出:无论是传值还是传地址,都是将调用函数中的实参拷贝一份传递给被调用函数的形参。只不过区别在于:
- 传值调用直接拷贝一份数值到被调用函数,被调用函数中的数值和调用函数中的数值在内存中是两份相互独立的;
- 传地址调用是将数值的地址拷贝一份到被调用函数中,数值在内存中只有一份,被调用函数通过该地址还能找到数值,可以修改这个数值。
四、栈传递参数
还是从源码开始理解:
编译后的汇编代码, 主调用函数:
被调用函数的汇编代码:
五、多函数参数情况
早期cpu的计算资源比较受限,当函数参数太多的时候,通用寄存器传参明显不够用,这时会转化成栈传参。但是当前的cpu大大扩展了各种丰富的寄存器,而且有些寄存器都有固定的用处,限于篇幅这里直接给出测试的结果:
1、参数个数少于7个:
f(a, b, c, d, e, f) :a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9
g(a, b):a->%rdi, b->%rsi
实际上将参数放入寄存器的语句是从右到左处理参数表的, 这点与32位的时候一致。
2、参数个数大于 7 个:
H(a, b, c, d, e, f, g);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%rax, g->8(%esp), f->(%esp)
易失寄存器:%rax, %rcx, %rdx, %rsi, %rdi, %r8, %r9 为易失寄存器, 被调用者不必恢复它们的值。显然,这里出现的寄存器大多用于参数传递了, 值被改掉也无妨。而 %rax, %rdx 常用于数值计算,%rcx 常用于循环计数,它们的值是经常改变的。其它的寄存器为非易失的,也就是 rbp, rbx, rsp, r10~r15 的值如果在汇编模块中被改变了,在退出该模块时,必须将其恢复。
- 上一篇: C++静态成员函数总结 Linux C++第62讲
- 下一篇: 详解Java 中 static 的作用
猜你喜欢
- 2025-05-24 高中数学解题分析方法及知识点
- 2025-05-24 C/C++编程笔记:无法在C++中重载的函数,六种方式
- 2025-05-24 面试与实战:什么是 Lambda?该如何使用?
- 2025-05-24 设计模式之单件模式
- 2025-05-24 Axon Framework - 模型- 聚合
- 2025-05-24 自动化利器Python类实例方法、静态方法和类方法的区别和用法
- 2025-05-24 嵌入式开发必看!面向过程VS面向对象,哪种更适合你的项目?
- 2025-05-24 Python:深度剖析实例方法、类方法和静态方法的区别
- 2025-05-24 避免踩坑,C++常见面试题的分析与解答
- 2025-05-24 一文掌握Python 中的类方法与静态方法
- 05-24高中数学解题分析方法及知识点
- 05-24C/C++编程笔记:无法在C++中重载的函数,六种方式
- 05-24面试与实战:什么是 Lambda?该如何使用?
- 05-24设计模式之单件模式
- 05-24Axon Framework - 模型- 聚合
- 05-24自动化利器Python类实例方法、静态方法和类方法的区别和用法
- 05-24嵌入式开发必看!面向过程VS面向对象,哪种更适合你的项目?
- 05-24Python:深度剖析实例方法、类方法和静态方法的区别
- 最近发表
- 标签列表
-
- cmd/c (64)
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- sqlset (64)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)