1.free 一段内存后,为什么还可以对这段内存进行读写。。。按照网上以及书上的说法,释放内存后,这段内存就不应该使用了,操作系统就可以分配给其他任务。。
我的疑问是,释放内存后,这段内存资源还是否属于当前进程???
如果属于当前进程,那么读写访问无可厚非。
可是,操作系统可以再次利用这段内存并分配给其他人,那么这里的 “其他”只是限定在当前进程中吗,只是给当前进程其他代码中的内存申请来使用么?
2.局部的数组,在函数运行完毕后,应该释放,但是为什么依然可以读写其中的数据 ?
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号
这是一个很复杂的问题,我谈谈自己的理解。
首先你需要知道,进程使用的都是虚拟地址空间,每个进程都有独立的,完整的4GB(32bit下)地址空间,未必每一块内存都会映射到物理内存,这个映射工作是操作系统完成的。如果你访问了地址空间里未映射的内存,或者写了只读的区域,操作系统就会报错(Segment Fault错误,呵呵~)并终止你的程序。
当一个进程开始运行时,会向操作系统申请一块“堆”内存,由程序自己对这块堆内存进行管理,malloc就是从这块内存中分配内存,在C语言中,这个申请和管理堆内存的工作是由运行库自动完成的。
当free一块内存后,free(即运行库)会将这块内存标记为未使用,下次有可能会将这块内存分配出去。但这块内存对进程来说仍然是可以读写的,因为运行库已经向操作系统申请,自己来管理这块内存了。
局部变量是分配在栈上的,进程开始运行时,操作系统会分配给进程一块固定大小(通常是1MB)的栈,所谓分配和释放局部变量,都只是在移动栈顶指针而已,只要没超过栈的这1MB内存区域,都还是可以读写的。
以上都是常见操作系统的典型行为,也许在某些操作系统和平台上并非这么工作,总之,使用已释放的内存是非常危险的行为。
有兴趣可以读这两本书《深入理解计算机系统》《链接装载与库》
十几年前上学的时候,计算机还是紧缺资源,在没有购买个人电脑之前,我们通常会去学校的计算机室上机。计算机室有专人管理,规矩诸多,其中包括“不要随意修改系统配置文件”,“不要做和学习无关的事情”等等。而我们经常会很开心地先把autoexec.bat/config.sys大改一气(很没出息是吧,不久几十K字节的事儿么,:)),然后再往机器上拷贝个金庸群侠传之类的,偷偷玩上半个小时。第二天再去上机的时候,直奔昨天保存文件的目录。运气好的时候,文件还在,于是大喜,接着进度继续玩;运气不好的时候,不但文件已经被删,而且还发现机器上新增的病毒好厉害,->_->。
说这个陈芝麻烂谷子,也许你已经明白我的意思。
其实我想说的就是两点:
1. 你可以不遵守规则,但不等于没有规则。
2. 不遵守规则而产生的后果是不可预测的(undefined)。
楼主没有明确说明为什么会认为free的内存仍然可用,以及为什么认为局部变量的数据仍然可用。
为了说明问题,我假设以下的程序:
编译运行这个程序的结果很可能是5。
所以函数结束后,局部变量的数据仍然可用是吗?
再来考虑下面这个程序:
这个程序运行的结果很可能是7。
显然,这个内存地址现在变得不那么可靠了。
(编译器还是会给出警告,比如gcc 4.8)
所以这个例子告诉我们,你能够访问到的内存空间并不总是安全的。
换句话说,你发现释放后的内存数据或者局部变量占用的内存仍然可以读写,只不过是偶然的情况 -- 刚好没有被别的程序动过而已。
C语言并不是一个内存安全的语言。
C++也不是,但C++11已经好很多了(接纳了smart pointer)。
补充
以上主要说明楼主描述的操作为什么从根本上是需要避免的。
再来补充回答一下楼主的疑问:
问题1:
是Heap(堆)的管理。
楼主的潜台词应该是“既然内存释放了,那么在访问的时候为什么不出现
segmentation fault
”。 回答是 -- 这是C运行库实施层面的问题。大多数运行库的实施不会试图去识别那些已经被"free"的内存块,并把它们退回系统(所谓退回系统,就是取消在进程地址空间上的映射)。因此,在访问这些地址的时候,segmentation fault
没有如预料中出现。 但并不全是这样,也有例外,比如说OpenBSD就是一个。访问wiki,你可以看到如下描述:这也从侧面证明了楼主观察到的现象是不可靠的。
(至于为什么多数运行库要采取这样的内存管理策略,又是另一个话题了)
问题2:
是Stack(栈)的管理。
@精英王子 已经说明了。
内存管理有以下几个层次(从高到低):C程序 - C库(malloc)- 操作系统 - 物理内存
首先,操作系统保证每个进程都有独立的虚拟内存空间(32bit上应该是4G吧,一般进程也用不了这么多)。当然实际上物理内存是所有进程共享的,所以当你需要动态内存时,需要向操作系统申请,这时候虽然从你程序的角度,内存是连续的,其实是被操作系统映射到某一块物理内存而已。程序用完内存归还后,实际归还的部分可能被操作系统分配给其他进程。
要注意,上面说的“归还”是malloc库的行为。malloc库会使用一些策略来提高内存使用的效率,比如程序需要使用10K内存时,malloc实际可能上会申请1M,因为一次系统调用开销很大;再比如即使你调用了
free
“归还“了程序使用的内存,malloc库也可能并未真正把这些内存归还给操作系统,因为将来程序可能还会再申请动态内存。malloc库有多种实现,我知道的一种是使用标记(tag)来存储内存的元信息。比如你申请了8个byte,得到的头指针地址是0x1001(实际内存为0x1001-0x1008),malloc会在0x1000(也就是头指针-1的位置)保存8,即这段内存的长度。等释放时,程序将头指针地址传给
free
,malloc库从头指针-1的位置发现需要释放的内存长度,释放内存(实际的操作可能只是将tag清空)。这就解释了:1. 为什么和malloc
不同,free
的参数只有一个头指针而不需要长度;2.free
后内存实际上可能并未归还给操作系统。所以,访问被(程序)释放的内存是一种undefined行为,就是说结果是不确定的。在malloc库未将此内存归还给操作系统也未进行下一次动态分配时,这块内存事实上仍属于程序。而当malloc库不清理归还的内存时(多数实现都是如此),你能访问到的值仍是原来的值。这和函数调用完毕而未清理栈帧、后续调用函数可以访问到之前已经设置的局部变量值是一个道理。
但是,当malloc库已经将内存归还给系统时,再去访问原来的地址(更别说写),由于这段地址已经不属于程序了,就会出现经典的segmentation fault。
说到底,这些现象还是C语言库为了更有效率的实现而妥协的结果。
ls 说的很详细了,在释放内存时一般建议把指针置空,这样来避免使用到已释放的内存。如:
free()和malloc()用一些数据结构(主要是链表)管理从堆中分配的内存,但它们只是库函数,真正改变堆边界的系统调用是sbrk()。malloc()发现当前内存不足够分配时,会先调用sbrk()扩大堆的边界。
free()释放内存实际是数据结构中把该部分标记为未使用,但它实际还在堆中,所以还能用。当有大量堆内存都没有使用,大部分的实现会收缩边界,这时,释放掉的内存就不能用了。
Linux下Glibc的内存管理机制大致如下:
从操作系统的角度看,进程的内存分配由两个系统调用完成:brk和mmap。brk是将数据段(.data)的最高地址指针edata往高地址推,mmap是在进程的虚拟地址空间中找一块空闲的。其中,mmap分配的内存由munmap释放,内存释放时将立即归还操作系统;而brk分配的内存需要等到高地址内存释放以后才能释放。也就是说,如果先后通过brk申请了A和B两块内存,在B释放之前,A是不可能释放的,仍然被进程占用,通过TOP查看疑似”内存泄露”。默认情况下,大于等于128KB的内存分配会调用mmap/mummap,小于128KB的内存请求调用sbrk(可以通过设置MMMAP_THRESHOLD来调整)。
转自:http://www.nosqlnotes.net/archives/105
以及:http://bbs.csdn.net/topics/330179712
http://blog.csdn.net/cinmyheart/article/details/38136375
去看这个,有快捷导航小标题的,只看9.9节即可,看完,你就知道malloc怎么管理虚拟内存了,god bless you.