C语言中内存分配那些事儿
C语言的之所以复杂,首先它的内存模型功不可没。不像某些那样的高级语言只需要在使用对象的时候,用new创建。所有之后的事情,你不需要操心。对于C语言,所有与内存相关的东西,都需要熟悉,否则,时间一久,总会踩着雷。下图是典型的一个C程序的内存结构,当然还有一个重要的前提,这样的一种布局是在虚拟内存中的:

关于虚拟内存内核维护了一个页表(page table),用来表示虚拟内存对物理内存地址或者磁盘(交换区,swap area)间的一种映射关系。并非所有的虚拟地址都需要在物理内存上映射,不然不管多大的内存条的计算机,多开几个进程,内存就消耗没了。当你需要使用内存时再去向操作系统申请,如果请求合法,那么内核为页表添加表项,建立一种虚地址对物理地址间的映射关系。同理,当需要释放时,就解除映射,把资源还回去。
关于虚拟内存的优点《the linux programming interface》给出了比较完善的回答:
Processes are isolated from one another and from the kernel, so that one process can’t read or modify the memory of another process or the kernel. This is accomplished by having the page-table entries for each process point to distinct sets of physical pages in RAM (or in the swap area).
Where appropriate, two or more processes can share memory. The kernel makes this possible by having page-table entries in different processes refer to the same pages of RAM. Memory sharing occurs in two common circumstances:
– Multiple processes executing the same program can share a single (readonly) copy of the program code. This type of sharing is performed mplicitly when multiple programs execute the same program file (or load the same shared library).
– Processes can use the shmget() and mmap() system calls to explicitly request sharing of memory regions with other processes. This is done or the purpose of interprocess communication.The implementation of memory protection schemes is facilitated; that is, pagetable entries can be marked to indicate that the contents of the corresponding page are readable, writable, executable, or some combination of these protections. Where multiple processes share pages of RAM, it is possible to specify that each process has different protections on the memory; for example, one process might have read-only access to a page, while another has read-write access.
Programmers, and tools such as the compiler and linker, don’t need to be concerned with the physical layout of the program in RAM.
Because only a part of a program needs to reside in memory, the program loads and runs faster. Furthermore, the memory footprint (i.e., virtual size) of a process can exceed the capacity of RAM.
One final advantage of virtual memory management is that since each process uses less RAM, more processes can simultaneously be held in RAM. This typically leads to better CPU utilization, since it increases the likelihood that, at any moment in time, there is at least one process that the CPU can execute.
本文主要就是为了讨论heap区内存分配的一些细节。
通常我们称堆的当前边界为“program break”。分配堆区的内存,就是将program break向高地址移动的过程。UNIX系统中有两个系统调用与这个program break关系最密切:
int brk(void *addr);
void *sbrk(intptr_t increment);
sbrk()是通过把program break移动increment的长度实现内存的增加和释放。由于虚拟内存按页分配,所以increment的值并不是实际分配的结果。只要不是页的大小的整数倍,就会多给一个页,不是四舍五入,永远只会多给,除非内存不足。sbrk(0)显示当前的program break。当试图访问program break之外的内存时会产生SIGSEV信号,发生段错误。brk() 函数和sbrk()用法大同小异。
int main()
{
int* p = sbrk();
*(p+) =;
printf("**\n");
*(p+) =;
}
这样一段代码,向内核申请100字节的内存,实际上映射的是一个内存页,行4访问内存页的最后4个字节并且改写,行6访问映射关系之外的内存显然是非法的,程序的运行结果如下:
$a.out
**
Segmentation fault
用brk()/sbrk()释放内存时,也不定会立即解除映射关系。当program break 下降超过一个页时,才有可能将申请的物理内存返还给内核。当然释放之后所有的对这块内存的操作都是未定义的,与玩火无异。同时program break移动还要注意的一点就是,program break的位置不能移动到heap区之外的地方,比如bss区,数据区等等,这样的行为基本也属于作死的行为之中。
malloc()/free() 绝对是C语言中使用最广泛的函数之一了。相比brk()/sbrk()他接口更加简单,也允许随意释放内存。(brk()/sbrk() 不能随意释放是由于program break往下移动的释放内存的时候,会把顶部“无辜”的元素也释放了。)例如这样的情况(这里内存映射解除了):

而free()释放并没有这样的“坑”,因为free释放内存不一定会移动program break。如果要free() 释放的内存上方(高内存地址处)仍然有没有释放的内存,那么program break就不会移动,因此也不会解除映射关系,也就是说这块内存并没有返还给内核。而是作为空闲的内存交给free维护去了,待下次malloc申请时,再返回这块内存(如果够用的话)给malloc返回。那么free又如何知道释放内存的大小的呢?这是由于malloc返回的内存拥有一个比较特殊的结构:

在这块内存的前面记录着这块内存的大小。当回收这块内存时,就会记录下他的长度和地址。当再次malloc时就会比较空闲内存列表是否有符合要求的内存,交给程序“二次使用”(或者N次使用)。当然至于用不用空闲内存列表的内存还要取决于具体情况:
1.如果空余的内存比malloc申请的大,那么就切割一部分给malloc返回,剩余的部分再看做是一块空闲的内存,留给下次的malloc使用。
2.如果malloc时没有合适的空闲的内存,那么就会像普通情况那样移动program break,或许申请新的内存(可能上回映射的时候会有富余,就不需要重新映射)。
知道了这些基本的实现之后,我们却发现malloc()、free()是比较危险的函数了,使用申请的内存时一定要小心,特别是边界的情况,否则结果可能是灾难性的。比如这样的一种情况,使用分配的内存后,仅仅越界了1个字节,而这一个字节恰恰记录着另一块内存的长度,当释放这块内存的时候,free维护了错误的长度,而下回有申请内存时把这块内存交给malloc那么一场“灾难”便到来了。
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
calloc()与malloc类似,分配nmemb个大小为size的对象,但是与malloc不同的是:calloc会把分配的内存初始化为0.
realloc() 正如名字那样是“重新分配”的意思,用来调整已经分配内存ptr的大小,如果ptr之后的内存不够就会申请一块新的区域,将原有内存原样复制过去,新增加的内存不作初始化。因此返回的结果可能与ptr不同,实际上不部分时候都是不相同的。因此realloc效率是不够高的。万不得已的时候,建议不要使用。
void *alloca(size_t size);
作用是在栈上分配内存。manual上是这样描述的:
The alloca() function allocates size bytes of space in the stack frame of the caller. This temporary space is automatically freed when the function that called alloca() returns to its caller.
在栈上分配内存的需要的场景不多,比如setjmp,longjmp执行非局部跳转的时候需要使用分配的内存时,就应该考虑alloca,因为他申请的内存会自动的释放,所以不会出现longjmp“回跳”时候,内存泄露的情况。这样的函数偶尔用一用还是有利于身心健康的。
C语言中内存分配那些事儿的更多相关文章
- C语言中内存分配 (转)
在任何程序设计环境及语言中,内存管理都十分重要.在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的.因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题. 第1节主要介绍内存管理基本概念,重 ...
- 【转】C语言中内存分配
原文:C语言中内存分配 在任何程序设计环境及语言中,内存管理都十分重要.在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的.因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题. 第1节主要 ...
- C语言中内存分配
C语言中内存分配 在任何程序设计环境及语言中,内存管理都十分重要.在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的.因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题. 第1节主要 ...
- C语言中内存分配问题:
推荐: C语言中内存分配 Linux size命令和C程序的存储空间布局 本大神感觉,上面的链接的内容,已经很好的说明了: 总结一下: 对于一个可执行文件,在linux下可以使用 size命令列出目标 ...
- (十一)C语言中内存堆和栈的区别
在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念. 堆栈:一种数据结构.一个在程序运行时用于存放的地方,这可能是很多初学者的认 ...
- C语言中内存对齐规则讨论(struct)
C语言中内存对齐规则讨论(struct) 对齐: 现代计算机中内存空间都是按着byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地 ...
- java中内存分配策略及堆和栈的比较
Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...
- DLL函数中内存分配及释放的问题
DLL函数中内存分配及释放的问题 最近一直在写DLL,遇到了一些比较难缠的问题,不过目前基本都解决了.主要是一些内存分配引起问题,既有大家经常遇到的现象也有特殊的 情况,这里总结一下,做为资料. 错误 ...
- C++中内存分配、函数调用和返回值问题
转载博客:http://blog.csdn.net/q_l_s/article/details/52176159(源地址找不到,就贴了这位大神的博客地址,他也是转载的,不过要是学习的话,他的博客很不错 ...
随机推荐
- css知多少(2)——学习css的思路
两周之前写过该系列的第一篇,其实当时只是一个想法,这段时间迟迟未更新,是在思考一个解决过程.现在初有成效,就开更吧. 1. 一个段子 开题不必太严肃,写博客也不像写书,像聊天似的写东西是最好的表达方式 ...
- CSS3 使用自定义字体
CSS3 @font-face 规则 在 CSS3 之前,web 设计师必须使用已在用户计算机上安装好的字体.通过 CSS3,web 设计师可以使用他们喜欢的任意字体.当您您找到或购买到希望使用的字体 ...
- 【初窥javascript奥秘之事件机制】论“点透”与“鬼点击”
前言 最近好好的研究了一番移动设备的点击响应速度,期间不断的被自己坑,最后搞得焦头烂额,就是现在可能还有一些问题,但是过程中感觉自己成长不少, 最后居然感觉对javascript事件机制有了更好的认识 ...
- 当jquery ajax遇上401请求
jquery ajax是个很常用接口,而在请求时候,可能存在响应401的情况(身份认证过期或未登录),比较容易出现在混合应用上,如何进行身份认证,重发失败请求,还是值得注意的. ajax请求有两种方式 ...
- 一步一步教你如何解锁被盗的iPhone 6S
即使你的iPhone6S设置了六位数的密码,甚至还设置了touch ID,但我要告诉你的是:你的手机仍然能被犯罪分子解锁. 事件背景 三天前,一位苹果用户的iPhone6S被偷了.随后,小偷重置了该用 ...
- ORACLE -- ArcSDE Lock request conflicts with an established lock【转】
具体的解决办法有如下三种 1.多半情况下关闭数据库连接可以解决这个问题,但有时候问题依然存在. 2. >1.关闭所有的ArcMap和ArcCatalog session. >2.开始——运 ...
- Day Tips:Remote 服务器黑屏
如果是台式机:CTRL+ALT+End 笔记本一般情况下End建和其他的键混合使用,所以是Fn+CTRL+ALT+End
- Android之手机向导以及设置中心模块的开发
当我们使用的新的软件的时候,我们首先需要教用户如何使用我们的软件,当用户学习完使用教程,下次再登录的时候,我们应该直接跳到我们的功能界面,下面我来展示一下我学习视频做的效果图:手机防盗这个功能模块就是 ...
- Android之Splash页面
在继上个任务没有解决之后,心灰意冷之后,现在的我在跟着视频学习开发一个手机卫士的软件.在写自己的笔记之前,我先来展示一下我的结果. 下面我来总结一下我跟随视频学习到的知识点: 一.代码的组织结构: 1 ...
- Android项目实战(二十):浅谈ListView悬浮头部展现效果
先看下效果:需求是 滑动列表 ,其中一部分视图(粉丝数,关注数这一部分)在滑动到顶端的时候不消失,而是停留在整个界面头部. 我们先分析要解决的问题: 1.如何实现列表ListView顶部视图跟随Lis ...