内存泄露的相关分析

摘自http://www.quanxue.cn/JC_CLanguage/CLang/Clang13.html

C语言程序员要严防内存泄漏,这个“内存泄漏”就是由动态内存分配引起的。指针是C语言和其它语言的最大区别,也是很多人不能跨入C语言的一道门槛。既然指针是这么一个“危险”的坏东西,干吗不取消它呢?

其实指针本身并没有好坏,它只是一种操作地址的方法,学会了便可以发挥其它语言难以匹敌的功能,没学会的话,只能做其它语言的程序员,也同样发挥你的光和热。小雅本人也在C语言门外徘徊多年,至今仍属于初学者。

一、变量和数组可以通过指针来转换

int *x”中的x究竟是不是数组?光看这一句小雅无法告诉你,因为它既可表示单个变量内容,也可表示数组。下面是小雅专门为你准备的例子,理解之后,对动态分配时长度计算有好处。

#include <stdio.h>

int main(void)
{
    int *num = NULL;
    int *x, y[] = {12, 22,32}, z = 100;

    //下面演示,指针既可充当变量、也可充当数组
    x=&z;      //整型变量的地址赋给x
    printf("*x=%d, x[0]=%d\n", *x, x[0]);

    x = y;     //数组的地址赋给x
    printf("*x=%d, x[ 0]=%d, x[ 1]=%d, x[2]=%d\n", *x, x[0], x[1], x[2]);

    x = y + 1; //数组的第二位地址赋给x
    printf("*x=%d, x[-1]=%d, x[ 0]=%d, x[1]=%d\n", *x, x[-1], x[0], x[1]);

    x = y + 2; //数组的第三位地址赋给x
    printf("*x=%d, x[-2]=%d, x[-1]=%d, x[0]=%d\n", *x, x[-2], x[-1], x[0]);

    return 0;
}

 

二、动态分配内存

前面讲到的指针,基本上将已经定义好的变量的地址赋给指针变量,现在要学的是向操作系统申请一块新的内存。申请到的内存,必须在某个地方手动释放,因此下面2个函数必须配对使用。malloc()和free(),都是标准函数,在stdlib.h中定义。

根据不同的电脑使用状况,申请内存有可能失败,失败时返回NULL,因此,动态申请内存时,一定要判断结果是否为空。malloc()的返回值类型是“void *”,因此,不要忘记类型转换。(许多人都省略了。)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char *p ;

    p = (char *)malloc(40 * sizeof(char)) ;
    if (p == NULL) {          //这个判断是必须的
        printf("内存分配出错!");
        exit(1);
    }
    strcpy(p,"这是劝学网C语言教程。\n");   //不要忘记给新内存赋值
    printf("%s", p);

    free(p);    //过河一定要拆桥
    p = NULL ;  //释放后的指针置空,这是非常好的习惯,防止野指针。

return 0;
}

 

三、隐蔽的内存泄漏

内存泄漏主要有以下几种情况:

  1. 内存分配未成功,却使用了它。
  2. 内存分配虽然成功,但是尚未初始化就引用它。
  3. 内存分配成功并且已经初始化,但操作越过了内存的边界。
  4. 忘记了释放内存,造成内存泄露。
  5. 释放了内存却继续使用它。

 

下面的程序造成内存泄漏,想想错在何处?如何修改?

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int *p, i;

    p = (int *)malloc(6 * sizeof(int)) ;
    if (p == NULL) {    //判断是否为空
        printf("内存分配出错!");
        exit(1);
    }

    for (i=0; i<6; i++) {
        p++;
        *p = i;
        printf("%2d", *p);

    }
    printf("\n");

    free(p);    //这句运行时出错

    return 0;
}

 

四、对动态内存的错误观念

有人对某一只在函数内使用的指针动态分配了内存,用完后不释放。其理由是:函数运行结束后,函数内的所有变量全部消亡。这是错误的。动态分配的内存是在“堆”里定义,并不随函数结束而消亡。

有人对某动态分配了内存的指针,用完后直接设置为NULL。其理由是:已经为NULL了,这就释放了。这也是错误的。指针可以任意赋值,而内存并没有释放;相反,内存释放后,指针也并不为NULL。

posted @ 2013-06-05 23:42 qingcheng奕 阅读(8) 评论(1)  编辑
 

 C/C++内存存储问题是笔试中必须掌握的。先看下面的程序:

复制代码
复制代码
#include <stdio.h>
#include "string.h"
#include "malloc.h"

void Swap(int a,int b)
{
    int temp;
    temp=a;
    a=b;
    b=temp;
}

int Get_Int(int a)
{
    int i=1+a;
    return i;
}

char* Get_Memory0()
{
    char* p=(char*)malloc(sizeof(char)* 20);
    strcpy(p,"hello world");
    return p;
}

char* Get_Memory1()
{
    char* p="hello world";
    return p;
}


char* Get_Memory2()
{
    char p[]="hello world";
    return p;
}

void main()
{
    int x=4,y=3;
    Swap(x,y);
    int z=x-y;
    printf("z=%d\n",z);

    z=Get_Int(z);
    printf("z=%d\n",z);

    char* c0=Get_Memory0();
    printf("c0=%s\n",c0);

    const char* c1=Get_Memory1();
    printf("c1=%s\n",c1);

    char* const c2=Get_Memory2();
    printf("c2=%s\n",c2);
}
复制代码
复制代码

基础知识

内存存储:

在C/C++中,通常可以把内存理解成4个分区:栈、堆、全局/静态存储区和常量存储区。

(1)栈:通常是用那些在编译期间就能确定其存储大小的变量的存储区,用于在函数作用域内创建、在离开作用域后自动销毁的变量的存储区。通常是局部变量、函数参数等的存储区。它的存储空间是连续的,两个紧挨着的定义的局部变量,它们的存储空间是紧挨着的。栈的大小是有限的,通常Visual C++编译器默认栈的大小是1M,所以不要定义int a[1000000]这样的超大数组。

(2)堆:通常是用于那些在编译期间不能确定存储大小的变量存储区,它的存储空间是不连续的,一般同malloc(或new)函数来分配内存块,并且需要要free(或delete)释放内存。如果程序员没有释放掉,那么就会出现常说的内存泄漏问题。需要注意的是,两个紧挨定义的指针变量,所指向的malloc出来的内存并不一定是紧挨着的。另外需要注意的一点是,堆的大小几乎是不受限制的,理论上每个程序最大可达4GB。

(3)全局/静态存储区:和“栈”一样,通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。

(4)常量存储区:和“全局/静态存储区”一样,通常是用于那些在编译期间就能确定存储大小的常量存储区,并且在程序运行期间,存储区内的常量也是全局可见的。这是一块比较特殊的存储区,它们里面放的是常量,不允许被修改。

程序解答:

(1)Swap函数:我们知道,所有函数都会在运行时从程序“栈”上得到分配给它的一块存储区(这里的“栈”就是前两面讲解内存存储时提到的“栈”,第个运行程序都拥有自己的运行时“栈”)。这块“栈”上的函数存储区随函数的开始面开始,随着函数的结束而结束。函数结束后,这块存储区就会自动释放,以供程序的其他用途使用。系统在函数运行时会为函数的每一个参数都提供存储区,参数在存储区中的存储长度由自身的类型决定。参数传递,就是系统用函数实参初始化函数参数存储区的过程。

        函数的参数有传值(传指针)和传址(传引用)两种。所谓参会传值,就是实参的值复制到函数运行时分配给函数的参数存储区中。参数在传值时,函数不会访问当前调用的实参,函数处理的是实参在本地的拷贝,这些拷贝被存储在函数的“栈”中,所以这些拷贝值的改变不会影响实参的值。

答案:

z=1

(2)问题:返回值i是一个局部变量,函数的返回参数怎么能是局部变量呢?局部变量在离开作用域后就自动被销毁了,还怎么能返回值给调用都呢?

       解答:函数的返回值有传值和传址两种。int Get_Int(int a)属于返回值是传值的函数,这就意味着函数int Get_Int(int a)会在函数返回处产生一个临时对象,用于存放局部变量i的值的一份拷贝(变量i的右值的拷贝),临时对象是没有名称的,这份没有名称的对象的值(右值)会存储在调用者的“栈”中。所以当i作为局部变量离开作用域后,虽然被销毁了,但它的拷贝仍然存在,并在函数返回时作为“右值”赋给“左值z”。

答案:

z=2

(3)此处考查对“堆”的理解:函数返回的是指向“堆”内存的指针。程序中malloc()是用于分配“堆”内存的库函数,而对于“堆”内存,只要程序中没有调用free()库函数去释放掉该“堆”内存,那么在程序运行期间,malloc()库函数分配的“堆”内存将一直存在。

     char* p=(char*)malloc(sizeof(char)*20)表示分配一块“堆”内存并使得变量p指向这块“堆”内存。

     strcpy(p,"hello world")表示往p指向的“堆”内存中复制字符串hello world。

     return p,变量p的左值是一个局部变量指针,存储于函数栈上,右值 是“堆”的地址(“堆”的值是hello world)。

     函数char* Get_Memory0()属于返回值是传指针的函数,这就意味着函数char* Get_Memory0()会在函数返回处产生一个对返回变量p的“左值”的拷贝,也就是在“左值”的拷贝中存储了指向“堆”的地址。作为局部变量的 p,在离开函数作用域的时候虽然被销毁了,但函数返回值珠“左值”的拷贝是存在的,该拷贝存储了指向“堆“的地址,而而该“堆”的值是hello world。

答案:

 c1=hello world

(4)这里考查对函数返回值和指针的理解:变量分为左值和右值,在char* p="hello world"中,左值是局部变量指针p,存储于函数栈上,右值是字符串常量hello world,存储于常量存储区。

        char* Get_Memory1()属于返回值是传指针的函数,这就意味着函数char* Get_Memory1()会在函数返回处产生一个对返回对象的“左值”的拷贝,也就是在“左值”拷贝中存储了指向字符串常量hello world的地址。作为局部变量的p,在离开函数作用域的时候虽然被销毁了,但函数 返回的“左值”拷贝仍然存储了指向常量存储区的字符串常量hello world的地址。

答案:

c1=hello world

(5)和问题(4)有些相似,考查的也是对函数返回值和指针的理解:返回值是传指针,会在函数返回时产生“左值”拷贝。可以看到,p[]不是指针,是一个数组变量。编译器根据右值“hello world”的长度(12个字符)在编译期间在函数的栈上为数组分配大小为12个字符的内存存储区,其值是“hello world”。

        通常,变量的意义在于,它给一块内存存储区提供名字,方便程序对这块内存进行读写。变量包含两个值:左值和右值。左值是内存存储区的名字,右值是存放存储区中的值。从程序中可以看到,函数返回的“左值”拷贝 指向的是局部变量数组p[12]的首地址。当局部数组p[12]作用域后会被自动销毁。这时,函数返回的“左值”拷贝指向的是一个被销毁的局部变量地址。

答案:

warning C4172: returning address of local variable or temporary

c2=未知

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值