内存的分配与释放,内存泄漏

xumenger

初始化的重要性

和在使用一个数据之前必须要对数据进行初始化一样,否则可能会使得数据的值不确定,那就会给程序埋下很大的隐患,在使用指针之前也必须要对指针进行”初始化“,参见下面的例程1:

#include<stdio.h>
int main(void)
{
    int *x;
    *x = 3;
    return 0;
}

这样的代码可能会出现段错误,因为x指针不知道会指向哪一块内存,使用*x=3来更改那块内存的数据有可能访问到非法内存导致段错误,当然也有可能因为没访问到非法内存而没有产生段错误,但是一个健壮的程序不允许存在这样的隐患。

再看下面的一个例程2:

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    int *x;
    x = (int *)malloc(sizeof(int));
    //上面一行代码相当于对指针的初始化,使得指针指向一个合法的内存区域
    //准确的表述应该是,使用malloc动态分配一块sizeof(int)大小的内存空间,然后让x指向这块内存
    *x = 3;    
    //上面这行代码的方式就不会有访问非法内存的可能,就不会产生段错误
    free(x);
    return 0;
}

指向非法内存的指针

通过指针释放内存后别忘了将指针置为NULL,或者将这个指针指向另一个合法的内存地址,总之不能再有指针指向被释放过了的非法的内存空间。

上面的例程2中直接调用free来释放内存,因为这是一个很简短的程序所以运行的效果会正如你希望的那样,但是假如在一个比较大型的项目中,这样的写法就存在着隐患,更为稳定的程序应该这样写,见例程3:

#include<stdio.h>
#include<stdlib.h>
int main(void)
{
    int *x;
    x = (int *)malloc(sizeof(int));
    //上面一行代码相当于对指针的初始化,使得指针指向一个合法的内存区域
    *x = 3;    
    //上面这行代码的方式就不会有访问非法内存的可能,就不会产生段错误
    free(x);
    x = NULL;
    return 0;
}

有人会说这不是多此一举吗,确实在这个例程里面是多此一举,因为这个程序太过简单,free(x) 之后,程序就运行结束了,不会存在再通过x访问被释放了的内存的非法访问内存的问题。

但是假如在一个比较大的程序中,在程序的某个地方使用free(x)释放了x所指向的内存空间(free操作只是释放该指针所指向的内存,并不会将指针置为NULL),但是忘记将x指针置为NULL,那么x将还会指向这块内存,假如后面通过if(x==NULL) 来进行判断,显然x不等于NULL,就可能出现非法访问x所指向的内存(但是这块内存之前已经被释放过了)的情况,显然会出现很严重的错误。

另外需要强调的一点,有可能有多个指针指向同一块内存,所以这种时候如果释放了内存空间的话,必须保证所有指向这块内存的指针都不再指向这块内存,可以是置为NULL,当然也可以使其指向其他合法的内存地址。

下面以一个简单的例程4展示free之后的指针将还指向原来的内存

#include<stdio.h>
int main(){
    int *x;
    x = (int*)malloc(sizeof(int)); //为x动态分配一块内存,内存大小为int型大小
    *x = 3; //将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); //将指针置为NULL之后再输出指针的值
    printf("%d\n", *x); //看看将指针置为NULL之后,再用*x会有什么效果
    
    return 0;
}

使用gcc编译的时候(我暂时将源文件命名为test1.c,使用gcc test1.c -o test1来编译),会有关于不正确使用指针的警告信息,但是并不是语法错误,所以还是可以编译通过,但是这就存在潜藏的大问题了。

最后运行输出的结果是:

29540368
3
29540368
0
0
Segmentation fault (core dumped)

所以可以清晰的看出,通过指针释放了动态分配的内存之后,指针还是指向原来的地址,还可以访问原来的地址(不过原来的地址中的值可能变了),而最后将指针置为NULL之后,显然指针不再指向原来的地址,而且如果这时候再想通过指针访问对应的内存,就会报段错误。

这个程序演示了几种不规范的使用指针的方法:

- 使用free释放了内存之后没有将指针置为NULL或者将指针再次指向另一个合法的内存而导致的再次访问到已经被释放了的内存的情况。Free之后而没有将指针置为NULL或者再次指向合法的地址,然后根据`if(pointer != NULL)`来进行判断指针是否合法其实是没有意义的。
- 指针置为NULL之后错误的通过*运算符取指针所指向内存的数据而导致的段错误。

另外,关于这个小的测试程序,有一个关于printf使用的问题,我已经在问答网站上问了,请参见:http://segmentfault.com/q/101...

补充:使用Delphi开发语言进行开发的时候,Delphi的类、对象名也需要注意释放的问题。直接以一个例程5讲解

    var
        objectA : ClassA;  {声明一个ClassA类型的变量}
    begin
        objectA := ClassA.Create;  {为objectA创建实体,要注意的是Delphi的对象的变量名其实就是一个指针}
                                  {所以这里会创建一个实体,objectA其实是指针,会指向这个实体}
                                  
        {使用对象进行一些操作}
        
        objectA.Free;    {最后释放变量}
        objectA:= nil;  {别忘了将变量置为nil,因为Delphi中的类的对象名就是指针}
    end;

说明:Delphi的面向对象编程中,一个对象名就相当于一个指针!

可能出现内存泄漏的几种情况

情况1

多次malloc但是没有对应的释放次

这样一个C语言的例子

#include<stdio.h>
int main(){
    int *x;
    int i;
    for(i=0; i<=100; i++)
        x = (int *)malloc(size(int));
    free(x);
    x = NULL;
    return 0;
}

在这个例子中,多次用malloc分配内存,然后每次将新分配的内存的地址赋值给x,但是x只能指向一个地址,所以最后x只能指向最后一次分配的内存的地址,所以也就只能释放最后一次分配的内存,而前99次分配的内存因为丢失了地址,所以没有办法释放,只能造成内存泄漏。

类似的dephi的例子可能是这样的:

var
    i: Integer;
    objectA: ClassA;
begin
    for i:=0 to 100 do
    begin
        objectA:= ClassA.Create;
    end;
    
    {...}
    objectA.Free;
    objectA:= nil;
end;

这个delphi的程序,每次ClassA.Create就在内存中创建一个实体,然后将objectA指向这个实体的内存,但是和上面的C程序类似,objectA只能指向一个地址,所以只能指向最后一次创建的对象实体的地址,所以前面99个创建的对象因为丢失了地址,所以没有办法释放,所以也就造成了内存泄露。

当然也有时候可以这样使用:就是在某种情况下,delphi的某个线程类是可以运行结束的(不是无限循环执行的),并且将FreeOnTerminate设为True(表示线程运行结束之后会自动释放线程的相关资源),这时候可以以这样的方式创建多个不使用指针保存其内存实体地址的线程类。但是这样的情况也实在是少,就算是可以采取这样的策略,我还是建议选择用个指针保存其地址以保证更为稳妥。总而言之,Delphi面向对象的一个好的编程规范是:创建类的对象实体,要有一个对象名(指针)保存这个实体的地址。

情况2

动态分配的内存还没有释放就直接将指向该内存的指针置为NULL

这样的一个C语言的例子

#include<stdio.h>
int main(){
    int *x;
    x = (int *)malloc(sizeof(int));
    //...
    x = NULL;
    //free(x);
    return 0;
}

这样的程序分配了内存空间,然后x指向这块内存(注意不要这样表述:使用malloc为x指针分配了空间,这样的表达方式是错误的,而应该是malloc分配了内存空间,然后x指针指向这块内存空间)。但是却将x置为NULL,这时候原来分配的内存空间的地址就丢失了,就没有办法对其释放,所以就会导致内存泄漏。

另外关于free的参数是空指针的问题(注意也不要表达成:free释放指针,应该是:释放指针所指向的内存空间),我在问答网站上提问了,请参见:http://segmentfault.com/q/101...

类似的Delphi的程序可能是这样的:

var
    ObjectA: ClassA;
begin
    ObjectA:= ClassA.Create;
    {...}
    ObjectA:= nil;
    //ObjectA.Free;    
end;

思考和总结

你可能会说,这种错误太明显了,我怎么可能会犯呢?

但是,当一个项目特别大的时候,有上万行,甚至数十万行的代码的时候,你还能保证不因为你的不细心或者各种意料之外的偶然而不出现这种情况吗?



阅读 6.9k

xumenger
我越来越感受到学习的快感,现在根本就停不下来!

小学一年级时候就学到了这样一句话“活到老学到老”,但是目前还没老的我们都还在学习吗?

4.6k 声望
588 粉丝
0 条评论

小学一年级时候就学到了这样一句话“活到老学到老”,但是目前还没老的我们都还在学习吗?

4.6k 声望
588 粉丝
文章目录
宣传栏