c++ - free释放内存后,内存空间的疑问
怪我咯
怪我咯 2017-04-17 11:13:04
0
7
744

1.free 一段内存后,为什么还可以对这段内存进行读写。。。按照网上以及书上的说法,释放内存后,这段内存就不应该使用了,操作系统就可以分配给其他任务。。

我的疑问是,释放内存后,这段内存资源还是否属于当前进程???

如果属于当前进程,那么读写访问无可厚非。

可是,操作系统可以再次利用这段内存并分配给其他人,那么这里的 “其他”只是限定在当前进程中吗,只是给当前进程其他代码中的内存申请来使用么?

2.局部的数组,在函数运行完毕后,应该释放,但是为什么依然可以读写其中的数据 ?

怪我咯
怪我咯

走同样的路,发现不同的人生

reply all(1)
PHPzhong

这是一个很复杂的问题,我谈谈自己的理解。

首先你需要知道,进程使用的都是虚拟地址空间,每个进程都有独立的,完整的4GB(32bit下)地址空间,未必每一块内存都会映射到物理内存,这个映射工作是操作系统完成的。如果你访问了地址空间里未映射的内存,或者写了只读的区域,操作系统就会报错(Segment Fault错误,呵呵~)并终止你的程序。

当一个进程开始运行时,会向操作系统申请一块“堆”内存,由程序自己对这块堆内存进行管理,malloc就是从这块内存中分配内存,在C语言中,这个申请和管理堆内存的工作是由运行库自动完成的。

当free一块内存后,free(即运行库)会将这块内存标记为未使用,下次有可能会将这块内存分配出去。但这块内存对进程来说仍然是可以读写的,因为运行库已经向操作系统申请,自己来管理这块内存了。


局部变量是分配在栈上的,进程开始运行时,操作系统会分配给进程一块固定大小(通常是1MB)的栈,所谓分配和释放局部变量,都只是在移动栈顶指针而已,只要没超过栈的这1MB内存区域,都还是可以读写的。

以上都是常见操作系统的典型行为,也许在某些操作系统和平台上并非这么工作,总之,使用已释放的内存是非常危险的行为。


有兴趣可以读这两本书《深入理解计算机系统》《链接装载与库》

伊谢尔伦

十几年前上学的时候,计算机还是紧缺资源,在没有购买个人电脑之前,我们通常会去学校的计算机室上机。计算机室有专人管理,规矩诸多,其中包括“不要随意修改系统配置文件”,“不要做和学习无关的事情”等等。而我们经常会很开心地先把autoexec.bat/config.sys大改一气(很没出息是吧,不久几十K字节的事儿么,:)),然后再往机器上拷贝个金庸群侠传之类的,偷偷玩上半个小时。第二天再去上机的时候,直奔昨天保存文件的目录。运气好的时候,文件还在,于是大喜,接着进度继续玩;运气不好的时候,不但文件已经被删,而且还发现机器上新增的病毒好厉害,->_->。

说这个陈芝麻烂谷子,也许你已经明白我的意思。
其实我想说的就是两点:
1. 你可以不遵守规则,但不等于没有规则。
2. 不遵守规则而产生的后果是不可预测的(undefined)。

楼主没有明确说明为什么会认为free的内存仍然可用,以及为什么认为局部变量的数据仍然可用。
为了说明问题,我假设以下的程序:

#include
#include 

int *lvret(void) {
    int ret = 5;
    return &ret;
}

int main(void) {
    int *p = lvret();
    printf("%d\n",*p);
}    

编译运行这个程序的结果很可能是5。
所以函数结束后,局部变量的数据仍然可用是吗?

再来考虑下面这个程序:

#include
#include 

int *lvret(void) {
    int ret = 5;
    return &ret;
}

void mod(void) {
    int a = 7;
}

int main(void) {
    int *p = lvret();
    mod();
    printf("%d\n",*p);
}

这个程序运行的结果很可能是7。
显然,这个内存地址现在变得不那么可靠了。

(编译器还是会给出警告,比如gcc 4.8

warning: function returns address of local variable [-Wreturn-local-addr] return &ret;

所以这个例子告诉我们,你能够访问到的内存空间并不总是安全的。
换句话说,你发现释放后的内存数据或者局部变量占用的内存仍然可以读写,只不过是偶然的情况 -- 刚好没有被别的程序动过而已。

C语言并不是一个内存安全的语言。
C++也不是,但C++11已经好很多了(接纳了smart pointer)。

补充

以上主要说明楼主描述的操作为什么从根本上是需要避免的。
再来补充回答一下楼主的疑问:

问题1:
是Heap(堆)的管理。
楼主的潜台词应该是“既然内存释放了,那么在访问的时候为什么不出现segmentation fault”。 回答是 -- 这是C运行库实施层面的问题。大多数运行库的实施不会试图去识别那些已经被"free"的内存块,并把它们退回系统(所谓退回系统,就是取消在进程地址空间上的映射)。因此,在访问这些地址的时候,segmentation fault没有如预料中出现。 但并不全是这样,也有例外,比如说OpenBSD就是一个。访问wiki,你可以看到如下描述:

On a call to free, memory is released and unmapped from the process address space using munmap. This system is designed to improve security by taking advantage of the address space layout randomization and gap page features implemented as part of OpenBSD's mmap system call, and to detect use-after-free bugs—as a large memory allocation is completely unmapped after it is freed, further use causes a segmentation fault and termination of the program.

这也从侧面证明了楼主观察到的现象是不可靠的。
(至于为什么多数运行库要采取这样的内存管理策略,又是另一个话题了)

问题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语言库为了更有效率的实现而妥协的结果。

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!