打印
[C语言]

C内存分配简析

[复制链接]
1100|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
小将wzj|  楼主 | 2017-9-22 08:28 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
1:内存分配
32位操作系统支持4GB内存的连续访问,但通常把内存分为两个2GB的空间,每个进程在运行时最大可以使用2GB的私有内存(0x00000000—0x7FFFFFFF)。至于高端的2GB内存地址(0x80000000—0xFFFFFFFF),操作系统一般内部保留使用,即供操作系统内核代码使用。每个进程都能看到自己的2GB内存以及系统的2GB内存,但是不同进程之间是无法彼此看到对方的。
2:虚拟内存
虚拟内存的基本思想是:用廉价但缓慢的磁盘来扩充快速却昂贵的内存。所有进程共享机器的物理内存,当内存使用完时就用磁盘保存数据。在进程运行时,数据在磁盘和内存之间来回移动。在进程执行过程中,操作系统负责具体细节,使每个进程都以为自己拥有整个地址空间的独家访问权。这个幻觉是通过“虚拟内存”实现的。内存管理硬件负责把虚拟地址翻译为物理地址,并让一个进程始终运行于系统的真正内存中,应用程序员只看到虚拟地址,并不知道自己的进程在磁盘与内存之间来回切换。
3:内存的使用
一、栈区(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈。
二、堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
三、全局区(又叫静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
四、常量区(又叫文字常量区):常量字符串就放在这里。程序结束后由系统释放。
五、程序代码区:存放函数体的二进制代码。(请注意是函数体)
       有时候对一个进程的内存空间在逻辑上也可简单的划分为三个部分:代码区、静态数据区和动态数据区。
动态数据区一般就是“堆栈”。“栈”和“堆”虽然都属于动态数据区,但确是不同的东西,栈是一种线性结构,而堆是一种链式结构。进程中的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互补干扰的。堆和栈可以分别通过“基地址”和“栈顶”地址来描述。
关于堆的创建方式,大家可以看一下相关的C语言书籍,内容也通俗易懂,这里不再一一介绍
来看一个网上很流行的经典例子:
main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456\0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);  堆
p2 = (char *)mall0c(20); 堆
}
4:堆栈的相关特性
栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
实际上这些空闲内存碎片存在的方式有两种:a.内部碎片 b.外部碎片 。      

内部碎片的产生:因为所有的内存分配必须起始于可被 4、8 或 16 整除(视处理器体系结构而定)的地址或者因为MMU的分页机制的限制,决定内存分配算法仅能把预定大小的内存块分配给客户。假设当某个客户请求一个
43 字节的内存块时,因为没有适合大小的内存,所以它可能会获得 44字节、48字节等稍大一点的字节,因此由所需大小四舍五入而产生的多余空间就叫内部碎片。

外部碎片的产生: 频繁的分配与回收物理页面会导致大量的、连续且小的页面块夹杂在已分配的页面中间,就会产生外部碎片。假设有一块一共有100个单位的连续空闲内存空间,范围是0~99。如果你从中申请一块内存,如10个单位,那么申请出来的内存块就为0~9区间。这时候你继续申请一块内存,比如说5个单位大,第二块得到的内存块就应该为10~14区间。如果你把第一块内存块释放,然后再申请一块大于10个单位的内存块,比如说20个单位。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。现在整个内存空间的状态是0~9空闲,10~14被占用,15~24被占用,25~99空闲。其中0~9就是一个内存碎片了。如果10~14一直被占用,而以后申请的空间都大于10个单位,那么0~9就永远用不上了,变成外部碎片。



相关帖子

沙发
feelhyq| | 2017-9-22 08:56 | 只看该作者

使用特权

评论回复
板凳
ankeseng| | 2017-9-23 13:40 | 只看该作者
学习了。

使用特权

评论回复
地板
QuakeGod| | 2017-9-23 23:46 | 只看该作者
说错了。

使用特权

评论回复
5
QuakeGod| | 2017-9-23 23:51 | 只看该作者
4:堆栈的相关特性
栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

这个说错了。函数的返回地址是最后入栈的。
函数返回的时候,函数的返回地址先出栈。至于栈里面的参数,要看函数调用规则。如果是C调用规则,则由调用者负责清理。如果是Pascal调用规则,则由被调用函数在返回时,使用 Ret N 语句,让硬件自动清除。
C语言调用规则,可以支持可变参数的函数调用。(不是重载哦)

使用特权

评论回复
6
小将wzj|  楼主 | 2017-9-25 08:40 | 只看该作者
Pascal调用规则我没有了解过,关于函数调用的入栈顺序我觉得应该是被调用函数结束后主函数要执行的下一条指令先入栈,然后是被调用函数的参数入栈(不同的内核架构参数入栈的方式不一样,有的会把前几个参数放到寄存器中,之后多余的参数参会入栈),之后就是局部变量,函数体,函数返回值依次入栈,结束时出栈顺序刚好相反

使用特权

评论回复
7
linqing171| | 2017-9-25 14:12 | 只看该作者
小将wzj 发表于 2017-9-25 08:40
Pascal调用规则我没有了解过,关于函数调用的入栈顺序我觉得应该是被调用函数结束后主函数要执行的下一条指 ...

感觉错误。
你可以看一下ARM官方的ATCP。
也可以了解一下x86下的c调用,stdcall,fastcall等的区别,比如this用ecx传递,单返回值用EAX返回等。
也可以看一下keil的调用规则。
intel的x86和51是call指令自动压函数地址,ARM下自动放入R14,而参数,是c转成的汇编压的堆栈。

使用特权

评论回复
8
linqing171| | 2017-9-25 14:16 | 只看该作者
调用之前先准备参数到堆栈里。调用的指令把下条返回地址顺便压栈。返回刚好相反,返回只能有一个返回值,不占用堆栈,放入寄存器即可,最后弹出的返回地址。
x86的retn指令比较强大。复杂指令集的强大是risc无法比的。

使用特权

评论回复
9
cxjgreentree| | 2017-9-29 14:35 | 只看该作者
学习

使用特权

评论回复
10
舍恩| | 2017-9-29 15:47 | 只看该作者
学习

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

13

主题

44

帖子

1

粉丝