c语言的free函数与内存空间释放

2021年9月17日 9点热度 0条评论 来源: wangxiaokunNO1

目录

0 缘起

在回顾用C语言实现链表的过程中,发现

  • 每个节点在开辟空间之后,到下一个节点被开辟空间之前,没有free();
  • 只有在删除节点时才用free()

上述两点让我产生疑惑,为什么节点都创建完了还不立即删除,而是在明确有删除节点的地方才进行free()?
所以决定对动态分配内存空间进行深入地理解。

1 free() 使用前

- free前程序做了什么

  • 在free前,程序一定进行了内存空间的动态分配。
  • malloc函数开辟指定大小的内存空间,并返回指向这段空间的起始地址的指针。
    (为叙述方便,这里用malloc()为例分配空间,返回的指针设为ptr.)
  • The behavior is undefined if the value of ptr does not equal a value returned earlier by malloc(), calloc(), realloc(), or aligned_alloc() (since C11).
    如果free的参数——指针,与具有相同变量名的,离它最近的内存分配函数所返回的指针所指向的地址不同,则可能会发生错误。
    ——在malloc()和free()之间,不能对其指针表示的地址进行修改。

2 free 的作用

- free对内存做了什么

  • Deallocates the space previously allocated by malloc(), calloc(),
    aligned_alloc, (since C11) or realloc(). [1]
    free释放了 由动态分配内存函数返回的指针 对应的内存空间
    注意:free释放的是内存空间,而不是指针。
    ——释放,指针指向的内存空间可以被其他变量所占用,
    但被占用前,内存空间中的内容仍然存在。但是无法判定是否被占用
  • The behavior is undefined if the memory area referred to by ptr has
    already been deallocated, that is, free() or realloc() has already
    been called with ptr as the argument and no calls to malloc(),
    calloc() or realloc() resulted in a pointer equal to ptr afterwards.[^1]
    同一内存空间对应的指针,在一次free后没有再次被分配空间的情况下,不能再次被free。

- free对指针做了什么

指针指向的地址,在没有重新分配内存前,也没有发生变化。

  • If ptr is a null pointer, the function does nothing.[^1]
  • free的参数——指针——为空时,free函数什么也不做

3 free后怎样做

free后,指针仍然指向分配的内存空间,指针指向的地址没有变化,地址内的内容也没有发生变化。变化的是这块内存的

  • 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). [^1]
    在free后进行指针指向的地址访问——所谓的解引用(不是分配空间),可能出现错误。
  • 为了防止在释放内存空间后,紧接着访问内存空间带来的错误,需要在free后,将指针指向NULL
ptr=NULL;

ptr 指向地址为 0 的内存。NULL 其实就是 0x0。此处的地址,没有访问权限。
这就提醒别人不要对这个指针进行解引用的操作。这样一来,
指针ptr就与源来的分配空间不再关联。
而变量名为ptr的指针在此后可以再次通过动态分配内存指向新的内存空间。

#include<stdio.h>
int main(){ 
    int *x;
    x = (int*)malloc(sizeof(int)); 
    *x = 3; 
    printf("%d\n ", x); 
    printf("%d\n", *x); 
    
    free(x);
    if(x != NULL){ 
        printf("%d\n", x); 
        printf("%d\n", *x); 
    }
    
    x=NULL;
    printf("%d\n", x); 
    printf("%d\n", *x); 
    
    return 0;
}
最后运行输出的结果是
29540368
3
29540368
0
0
Segmentation fault (core dumpe

解释:
通过指针释放了动态分配的内存之后,指针还是指向原来的地址,
还可以访问原来的地址(不过原来的地址中的值可能变了),

而最后将指针置为 NULL 之后,显然指针不再指向原来的地址,
而且如果这时候再想通过指针访问对应的内存,就会报段错误。[^2]

4 Q&A

(1) Q:有malloc 一定有free吗?

A:是的
但并不是说一个malloc一定要对应一个free.要看具体的场景。
一旦free,就丧失了指针对这段内存空间的占有权和使用权。
比如物理结构是链表的实现场景,链表中的各个节点存储在当时分配的内存空间中。如果在构造链表的过程中,边申请存储节点的空间边释放节点空间,那么已经建好的结点的内容有可能被抹掉,所以只要有对该链表及其元素的操作,就不能free。

(2) Q:该free的地方没有free,会发生什么?

首先,什么时候该free?ptr对应的内存空间不再需要被访问或者被修改,那么,必须free.
否则,ptr对应的内存空间将不能被其他变量使用,造成内存泄漏(Memory Leak)

(3) 野指针和悬空指针?

野指针(wild pointer)
a pointer that has not been correctly initialized and therefore points to some random piece of memory. It is a serious error to have wild pointers.[3]
只是定义了指针指向的类型,而指针没有指向内存空间。把这样的指针叫野指针。

悬空指针(dangling pointer)
If a pointer still references the original memory after it has been freed, it is called a dangling pointer.
There is nothing wrong with having a dangling pointer unless you try to access the memory location pointed at by that pointer.[3]
若两个指针(p1和p2)指向同一块内存区域, 那么free(p1)后,p1和p2都成为悬空指针。
如果进一步将p1设置为NULL, 那么p2还是悬空指针。
使用 *p1 会导致非法内存访问(NULL对应的内存不允许程序访问),但是使用*p2却会出现无法预料的结果(可能是没有释放时该位置的原始指,也可能是其他值) [3]

悬空指针的处理——智能指针()待补充
基本思路:在释放一块内存时,将指向这块内存的指针变量设置为NULL。
访问指针变量前,先判断是否为NULL。

进阶:当有多个指针变量都指向同一块内存,释放这块内存时,需要将所有指针变量的值都置为NULL,这需要维护所有指向这块内存的指针变量的信息,
但是这种方式开销大,所以通常很少使用。
可以使用引用计数,只有当引用计数为0时,才释放内存,否则,只是引用计数减1.——引用计数法:如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法。

5 讲个故事

小明租住了西虹市A小区302房,他与房东签了6个月的租房合同,签完合同小明得到了房间的钥匙。这六个月内小明就有了302房间的占有权和使用权,他可以添置家具,邀请客人,但不能对房间搞破坏,也不能修改门牌号。
六个月过去了,小明不再租住这个房子,由于时间仓促,他没来得及把钥匙交回给房东。
302可能此后没有人来租住,房东也比较懒,没有对房内摆设进行改动。也可能改动了。但这跟小明都没有关系了。
此后,

  • 房东可能换了把新锁,
  • 也可能没换,
  • 过几年A小区可能拆迁了。

那有一天小明喝醉酒,鬼使神差地,小明带着这把钥匙再去西虹市A小区302,对用以上的三种情况,分别会以下三种情况:

  • 钥匙打不开302的门
  • 钥匙打开了门,但门上的监控触发报警,因为私闯民宅是犯法的。
  • 他找不到A小区302了,因为A小区已经不存在了。

6 回到开头的问题

为什么开辟了节点空间,也赋了值,但是不立马回收空间呢?
因为整个过程是在使用链表,而为节点开辟空间、赋值,是构造链表的过程。如果刚创建完节点,就立马free,节点就被摧毁了,那开辟空间,赋值也没了意义,链表也没法创建了。
所以,确定何时对指针指向的内存空间进行回收,是使用内存空间分配时,必须考虑的。
同时,手动进行内存空间分配,使得c语言更加偏向底层。为使用者带来了更加灵活自由的内存操作权利。当然,也带来了更大的不确定性。

7 参考文献

[1] 链接: free函数.
[2] 链接: 内存的分配与释放,内存泄漏.
[3] 链接: 野(wild)指针与悬空(dangling)指针.

    原文作者:wangxiaokunNO1
    原文地址: https://blog.csdn.net/wangxiaokunNO1/article/details/106797446
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。