C 语言:释放动态分配的内存,为何还能访问?

2017-06-23 17:06:21 +08:00
 NullMan
#include <stdio.h>
#include <stdlib.h>

#define N 50

int main(void) {
    int *pi;
    pi = (int *)malloc(N * sizeof(int));
    if (pi == NULL) {
        printf("Out of memory!\n");
        exit(1);
    }
    for (int i = 0; i < N; i++) {
        *(pi + i) = 100;
    }
    free(pi);
    pi[10] = 200;
    printf("%d\n", pi[10]); // 输出 200
    return 0;
}

执行 free(pi) 了,没道理 pi 还能访问。我看了下 c 标准,发现有这么一句话:

The behavior is undefined if after free() returns, an access is made through the pointer ptr (unless another allocation function happened to result in a pointer value equal to ptr)

这到底释放了内存没有?我是这么猜想的,这块内存其实回归了内存池,如果有其他的内存分配,将有可能复用这free 过的内存块。先前代码输出的200, 其实等同于垃圾值,就像声明了一个int i但未初始化而直接访问i将会得到上次使用过i内存的垃圾值。

不知我理解是否正确?

编译器版本如下:

Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

本人初学 C,望指点!多谢多谢!

4886 次点击
所在节点    C
67 条回复
ETiV
2017-06-24 01:15:31 +08:00
你得把内存拔了才能访问不了
NullMan
2017-06-24 02:28:17 +08:00
@shuax 哈哈,这也许就是硬盘数据恢复可行的依据之一。
NullMan
2017-06-24 02:38:20 +08:00
@ogfa 但是会报 warning: incompatible integer to pointer conversion assigning to 'int *' from 'int' [-Wint-conversion] 警告,这对于致力于编写零警告程序的我来说,不够完美。 哈哈。
di94sh
2017-06-24 03:25:09 +08:00
实际上 free 玩了要赋值 null。
ogfa
2017-06-24 03:29:45 +08:00
@NullMan
(someptr=nullptr)++;
msg7086
2017-06-24 05:18:58 +08:00
@NullMan ptr = (int *)1;?
chinawrj
2017-06-24 07:59:26 +08:00
既然是超级系统下 C 代码,我说一下 Linux 层面的原因吧。内存释放之后,如果运气好,对应的内存 Page 依然处在有效映射,并且 MMU 标志还是可写访问的话,那么恭喜你你还可以访问的。不过什么时候不可以访问就看系统时候时候收回那段内存的映射了。有可能那块内存被你程序其他代码申请,于是乎你还可以继续用你现在的指针访问,但是结果是什么就可想而知了。
nevin47
2017-06-24 08:06:33 +08:00
我滴妈,楼上大神们的讨论刷新了我对 free 后 null 的认识,收藏一记一会儿和组里讨论一下
aheadlead
2017-06-24 09:10:12 +08:00
这问题太复杂了 mm 是个很庞大的话题

楼主位的问题可能只是那块内存所在的页面还在使用
也有可能是内核还没来得及回收罢了
bp0
2017-06-24 11:45:34 +08:00
@geelaw 如果你不关心 page 怎么被 decomitted 的,又如何保证你说的方法一定有可移植性呢?到其他系统中 page 没有被 decomitted,这个时候应该怎么办?

而且我从没有说过要掩盖双重释放的问题。就像你提供的资料上说的,这种问题是逻辑错误。这种逻辑错误我们有很多方法去检查,编写代码时要求调用统一的释放接口,代码提交之前进行 review,CI 流程中进行的静态检查,单元测试。还有我上面提到的使用 Valgrind 工具 。甚至通过 C 实现 auto_ptr 这种类似的智能指针。这些都可以帮助我们避免包括双重释放在内的众多问题。

但是你却说让他 crash。问题是,如果它能马上 crash 还好。如果他没有马上 crash,或者一段时间以后在其他线程中 crash 了。你应该如何处理?当然我相信你们也会类似的流程保证代码质量,而不是简单的让它 crash。

问题是,你给人一句将 free 后的指针设置成 NULL 不好,我们不用。就像跟人说 goto 不好,我们不用是一样的。goto 真的不好吗?新手用 goto 可能出问题,大神用 goto 优化代码。用不用的好,全看你自己对系统的理解了。
simpx
2017-06-24 12:37:37 +08:00
@icedx #40 我自己的代码里,原因是因为我自己精确知道哪些变量需要初始化,哪些不需要

所以不用 calloc 一股脑初始化
gnaggnoyil
2017-06-24 13:50:31 +08:00
@msg7086 intptr_t
gnaggnoyil
2017-06-24 13:58:57 +08:00
另外楼上那些又是扯分页又是扯 Glibc 的纯属离题万里.C 标准里规定了 free 后 access 这个指针就是未定义行为.而未定义行为的意思是编译器碰到这种情况有权力执行任何操作,包括但不限于生成语义面目全非的编译产物.所以对于一个未定义行为讨论其实际意义是没有意义的,因为未定义行为本来就不蕴含任何意义.
u5f20u98de
2017-06-24 14:01:23 +08:00
在安全角度上看这大概就是漏洞了
https://cwe.mitre.org/data/definitions/416.html
johnlui
2017-06-24 14:24:00 +08:00
@NullMan 看来你对编译器有误解,它只是帮忙把你的字符串描述的行为翻译成了汇编代码而已。
acros
2017-06-24 14:24:32 +08:00
让一让,专业配图师来了。
wohenyingyu02
2017-06-24 14:29:53 +08:00
难道 free 完这个内存会从机箱里掉出来么
acros
2017-06-24 14:30:46 +08:00
上面的解释很详细了····
好像也详细过头了,估计新手都会看怕了,一般了解到 free 后指针没置空,仍然存在访问风险就好了。

具体谈论这些内容的,我记得《 C 专家编程》好像不错。
oroge
2017-06-24 14:54:33 +08:00
还可以推荐 operating system concepts ...
VYSE
2017-06-24 15:09:04 +08:00
争啥争把 free 后指针置为 0xdeadbeef !
要知道当年置为 0,可以 mmap 0 地址的哦。
其实 UAF 修复关键是 free 后杜绝 use 啊。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/370623

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX