内存管理

 以下内容摘自高质量C++/C编程指南(作者 rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cjialiang%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml"> 林锐),之所以放在这里,只是为了以后查找方便。

rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cjialiang%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">

内存分配方式有三种:

(1)      从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

(2)      在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

(3)      从堆上分配,亦称动态内存分配。程序在运行的时候用mallocnew申请任意多少的内存,程序员自己负责在何时用freedelete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cjialiang%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">


常见的内存错误及其对策

       发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。

常见的内存错误及其对策如下:

@      内存分配未成功,却使用了它。

编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用mallocnew来申请内存,应该用if(p==NULL) if(p!=NULL)进行防错处理。

 

@     内存分配虽然成功,但是尚未初始化就引用它。

犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。

内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

 

@       内存分配成功并且已经初始化,但操作越过了内存的边界。

例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

 

@    忘记了释放内存,造成内存泄露。

含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。

动态内存的申请与释放必须配对,程序中mallocfree的使用次数一定要相同,否则肯定有错误(new/delete同理)。

 

@      释放了内存却继续使用它。

有三种情况:

1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

3)使用freedelete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cjialiang%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">


 我们发现指针有一些“似是而非”的特征:

1)指针消亡了,并不表示它所指的内存会被自动释放。

2)内存被释放了,并不表示指针会消亡或者成了NULL指针。

这表明释放内存并不是一件可以草率对待的事。也许有人不服气,一定要找出可以草率行事的理由:

    如果程序终止了运行,一切指针都会消亡,动态内存会被操作系统回收。既然如此,在程序临终前,就可以不必释放内存、不必将指针设置为NULL了。终于可以偷懒而不会发生错误了吧?

    想得美。如果别人把那段程序取出来用到其它地方怎么办?


rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cjialiang%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">

指针与数组的对比

       C++/C程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。

       数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。

下面以字符串为例比较指针与数组的特性。

 

7.3.1 修改内容

       示例7-3-1中,字符数组a的容量是6个字符,其内容为hello/0a的内容可以改变,如a[0]= ‘X’。指针p指向常量字符串“world”(位于静态存储区,内容为world/0),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。

 

char a[] = hello;

a[0] = X;

cout << a << endl;

char *p = world;     // 注意p指向常量字符串

p[0] = X;             // 编译器不能发现该错误

cout << p << endl;

示例7-3-1 修改数组和指针的内容

 

7.3.2 内容复制与比较

    不能对数组名进行直接复制与比较。示例7-3-2中,若想把数组a的内容复制给数组b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy进行复制。同理,比较ba的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。

    语句p = a 并不能把a的内容复制指针p,而是把a的地址赋给了p。要想复制a的内容,可以先用库函数mallocp申请一块容量为strlen(a)+1个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。

 

        // 数组

        char a[] = "hello";

        char b[10];

        strcpy(b, a);                   // 不能用       b = a;

        if(strcmp(b, a) == 0)   // 不能用  if (b == a)

        // 指针

        int len = strlen(a);

        char *p = (char *)malloc(sizeof(char)*(len+1));

        strcpy(p,a);                    // 不要用 p = a;

        if(strcmp(p, a) == 0)   // 不要用 if (p == a)

示例7-3-2 数组和指针的内容复制与比较

 

 

7.3.3 计算内存容量

    用运算符sizeof可以计算出数组的容量(字节数)。示例7-3-3a)中,sizeof(a)的值是12(注意别忘了/0)。指针p指向a,但是sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。示例7-3-3b)中,不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)

 

        char a[] = "hello world";

        char *p  = a;

        cout<< sizeof(a) << endl;       // 12字节

        cout<< sizeof(p) << endl;       // 4字节

示例7-3-3a 计算数组和指针的内存容量

             

        void Func(char a[100])

        {

                cout<< sizeof(a) << endl;       // 4字节而不是100字节

}

示例7-3-3b 数组退化为指针


rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cjialiang%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">

杜绝“野指针”

“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。

“野指针”的成因主要有两种:

1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如

        char *p = NULL;

        char *str = (char *) malloc(100);

 

2)指针pfree或者delete之后,没有置为NULL,让人误以为p是个合法的指针。参见7.5节。

 

3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:

        class A

{  

public:

    void Func(void){ cout << Func of class A << endl; }

};

        void Test(void)

{

    A  *p;

            {

                    A  a;

                    p = &a; // 注意 a 的生命期

}

                p->Func();              // p是“野指针”

}

 

函数Test在执行语句p->Func(),对象a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。


rel="File-List" href="file:///C:%5CDOCUME%7E1%5Cjialiang%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">

引用与指针的比较

引用是C++中的概念,初学者容易把引用和指针混淆一起。一下程序中,nm的一个引用(reference),m是被引用物(referent)。

    int m;

    int &n = m;

n相当于m的别名(绰号),对n的任何操作就是对m的操作。例如有人名叫王小毛,他的绰号是“三毛”。说“三毛”怎么怎么的,其实就是对王小毛说三道四。所以n既不是m的拷贝,也不是指向m的指针,其实n就是m它自己。

引用的一些规则如下:

1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。

2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。

3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。

        以下示例程序中,k被初始化为i的引用。语句k = j并不能将k修改成为j的引用,只是把k的值改变成为6。由于ki的引用,所以i的值也变成了6

    int i = 5;

    int j = 6;

    int &k = i;

    k = j;  // ki的值都变成了6;

    上面的程序看起来象在玩文字游戏,没有体现出引用的价值。引用的主要功能是传递函数的参数和返回值。C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。

    以下是“值传递”的示例程序。由于Func1函数体内的x是外部变量n的一份拷贝,改变x的值不会影响n, 所以n的值仍然是0

    void Func1(int x)

{

        x = x + 10;

}

int n = 0;

    Func1(n);

    cout << “n = ” << n << endl;  // n = 0

       

以下是“指针传递”的示例程序。由于Func2函数体内的x是指向外部变量n的指针,改变该指针的内容将导致n的值改变,所以n的值成为10

    void Func2(int *x)

{

        (* x) = (* x) + 10;

}

int n = 0;

    Func2(&n);

    cout << “n = ” << n << endl;          // n = 10

 

    以下是“引用传递”的示例程序。由于Func3函数体内的x是外部变量n的引用,xn是同一个东西,改变x等于改变n,所以n的值成为10

    void Func3(int &x)

{

        x = x + 10;

}

int n = 0;

    Func3(n);

    cout << “n = ” << n << endl;          // n = 10

 

    对比上述三个示例程序,会发现“引用传递”的性质象“指针传递”,而书写方式象“值传递”。实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用”这东西?

答案是“用适当的工具做恰如其分的工作”。

    指针能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。就象一把刀,它可以用来砍树、裁纸、修指甲、理发等等,谁敢这样用?

如果的确只需要借用一下某个对象的“别名”,那么就用“引用”,而不要用“指针”,以免发生意外。比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如果把取公章的钥匙交给他,那么他就获得了不该有的权利。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园的建设目标是通过数据整合、全面共享,实现校园内教学、科研、管理、服务流程的数字化、信息化、智能化和多媒体化,以提高资源利用率和管理效率,确保校园安全。 智慧校园的建设思路包括构建统一支撑平台、建立完善管理体系、大数据辅助决策和建设校园智慧环境。通过云架构的数据中心与智慧的学习、办公环境,实现日常教学活动、资源建设情况、学业水平情况的全面统计和分析,为决策提供辅助。此外,智慧校园还涵盖了多媒体教学、智慧录播、电子图书馆、VR教室等多种教学模式,以及校园网络、智慧班牌、校园广播等教务管理功能,旨在提升教学品质和管理水平。 智慧校园的详细方案设计进一步细化了教学、教务、安防和运维等多个方面的应用。例如,在智慧教学领域,通过多媒体教学、智慧录播、电子图书馆等技术,实现教学资源的共享和教学模式的创新。在智慧教务方面,校园网络、考场监控、智慧班牌等系统为校园管理提供了便捷和高效。智慧安防系统包括视频监控、一键报警、阳光厨房等,确保校园安全。智慧运维则通过综合管理平台、设备管理、能效管理和资产管理,实现校园设施的智能化管理。 智慧校园的优势和价值体现在个性化互动的智慧教学、协同高效的校园管理、无处不在的校园学习、全面感知的校园环境和轻松便捷的校园生活等方面。通过智慧校园的建设,可以促进教育资源的均衡化,提高教育质量和管理效率,同时保障校园安全和提升师生的学习体验。 总之,智慧校园解决方案通过整合现代信息技术,如云计算、大数据、物联网和人工智能,为教育行业带来了革命性的变革。它不仅提高了教育的质量和效率,还为师生创造了一个更加安全、便捷和富有智慧的学习与生活环境。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值