C++内存管理、野指针和指针使用注意点

目录

在c++中内存主要分为5个存储区:

栈:

堆:

全局/静态存储区:

文字常量区:

程序代码区:

使用存储区的三种方式:

1)静态存储区(Static Memory)

2)自动存储区(Autormatic Memory)

3)自由存储区(Free Store)

堆和栈的区别:

使用内存时几点注意事项

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

指针的注意点:


参考链接:https://www.cnblogs.com/mrlsx/p/5411874.html

在c++中内存主要分为5个存储区:

栈:

局部变量,函数参数等存储在该区,由编译器自动分配和释放.栈属于计算机系统的数据结构,进栈出栈有相应的计算机指令支持,而且分配专门的寄存器存储栈的地址,效率高,内存空间是连续的,但栈的内存空间有限

堆:

需要用户通过 (malloc/new)手动申请,手动释放(free/delete),属于动态分配方式。内存空间几乎没有限制,内存空间不连续,因此会产生内存碎片。操作系统有一个记录空间内存的链表,当收到内存申请时遍历链表,找到第一个空间大于申请空间的堆节点,将该节点分配给程序,并将该节点从链表中删除。一般,系统会在该内存空间的首地址处记录本次分配的内存大小,用于delete释放该内存空间。

全局/静态存储区:

全局变量,静态变量分配到该区,到程序结束时自动释放,包括DATA段(全局初始化区)与BBS段(全局未初始化段)。其中,初始化的全局变量和静态变量存放在DATA段,未初始化的全局变量和静态变量存放在BBS段。BBS段特点:在程序执行前BBS段自动清零,所以未初始化的全局变量和静态变量在程序执行前已经成为0.

文字常量区:

存放常量,而且不允许修改。程序结束后由系统释放。

程序代码区:

存放程序的二进制代码


使用存储区的三种方式:

1)静态存储区(Static Memory)

全局变量,静态变量及静态类成员存储在该区,在编译期间就进行分配,生存期到程序结束。存储在该区的对象只初始化一次,且在程序运行期间地址固定不变。

2)自动存储区(Autormatic Memory)

局部变量,函数参数等存储在该区,由编译器自动分配和释放

3)自由存储区(Free Store)

由程序员手动分配和释放内存(new,delete)


堆和栈的区别:

1)空间大小栈的内存空间是连续的,空间大小通常是系统预先规定好的,即栈顶地址和最大空间是确定的;而堆得内存空间是不连续的,由一个记录空间的链表负责管理,因此内存空间几乎没有限制,在32位系统下,内存空间大小可达到4G

2)管理方式:栈由编译器自动分配和释放,而堆需要程序员来手动分配和释放,若忘记delete,容易产生内存泄漏。

3)生长方向不同:对于栈,他是向着内存地址减小的方向生长的,这也是为什么栈的内存空间是有限的;而堆是向着内存地址增大的方向生长的

4)碎片问题:由于栈的内存空间是连续的,先进后出的方式保证不会产生零碎的空间;而堆分配方式是每次在空闲链表中遍历到第一个大于申请空间的节点,每次分配的空间大小一般不会正好等于申请的内存大小,频繁的new操作势必会产生大量的空间碎片

5)分配效率:栈属于机器系统提供的数据结构,计算机会在底层对栈提供支持,出栈进栈由专门的指令执行,因此效率较高。而堆是c/c++函数库提供的,当申请空间时需要按照一定的算法搜索足够大小的内存空间,当没有足够的空间时,还需要额外的处理,因此效率较低


使用内存时几点注意事项

1、用new和malloc申请内存时,在使用前要检查内存是否分配成功

char *p=new char[10];
if(p==NULL)
return;

2、使用内存之前要进行初始化

3、在对内存进行操作时,防止越界

4、对于动态分配的内存,一定要手动释放,否则程序每运行一次就会丢失一部分内存,造成内存泄漏

5、防止内存释放后继续使用它,主要有以下三种情况:

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

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

c.使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

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

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

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

char *p;   //此时p为野指针

(b)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针.

参考链接:https://blog.csdn.net/qq_36570733/article/details/80043321

删除完指针p之后,一定要把它变成空指针p=NULL,防止p成为野指针

char *p=new char[10];  //指向堆中分配的内存首地址
cin>> p;
delete []p; //p重新变为野指针
p=NULL;

(c)指针操作超越了变量的作用范围。

char *p=new char[10];  //指向堆中分配的内存首地址
cin>> p;
cout<<*(p+10);  //可能输出未知数据

指针的注意点:

参考链接:https://www.cnblogs.com/mrlsx/p/5419030.html

a.指针指向常量存储区对象

char *p="abc";

此时p指向的是一个字符串常量,不能对*p的内容进行写操作,如srtcpy(p,s)是错误的,因为p的内容为“abc”字符串常量,该数据存储在常量存储区,但可以对指针p进行操作,让其指向其他的内存空间。

b.资源泄漏

#include<iostream>
using namespace std;
void main()
{
    char *p=new char[3];  //分配三个字符空间,p指向该内存空间
    p="ab";             //此时p指向常量“ab”,而不再是new char分配的内存空间了,从而造成了资源泄漏
    delete []p;         //释放时报错
}

改进:

#include<iostream>
using namespace std;
void main()
{
    char *p=new char[3];  //分配三个字符空间,p指向该内存空间
    strcpy(p,"ab");      //将"ab"存储到p指向的内存空间
    delete []p;         //ok
}

c.内存越界

char *p=new char[3];  //分配三个字符空间,p指向该内存空间
strcpy(p,"abcd");  //将abcd存处在分配的内存空间中,由于strlen("abcd")=4>3,越界
strlen("abcd")=4>3,越界
delete []p;  //释放时出错

d.返回值是指针

问题:数组p[]中的内容为“hello world”,存储在栈区,函数结束时内容被清除,p变为野指针,可能导致乱码

#include<iostream>
using namespace std;
char *f()
{
    char p[]="abc";
    return p;
}
void main()
{
    cout<<f()<<endl;
}

改进:

1.加static限定,延长数组生存期

#include<iostream>
using namespace std;
char *f()
{
    static char p[]="abc";  //此时数组为静态数组,存储在全局/静态区,生存期到程序结束,因此函数结束时不会销毁p
    return p;
}
void main()
{
    cout<<f()<<endl;
}

2.定义成指针型数组

#include<iostream>
using namespace std;
char *f()
{
    char *p="abc";    //"abc"存储在文字常量区,p是指向常量的指针,生存期到程序结束
    return p;
}
void main()
{
    cout<<f()<<endl;
}

3.动态分配存储空间,存储在堆区

#include<iostream>
using namespace std;
char *f()
{
    char *p=new char[5];  //动态分配存储空间,p指向堆区
    strcpy(p,"abc");   // 这里不能用p="abc",前面已经说明
    return p;
}
void main()
{
    cout<<f()<<endl;
}

 e.指针做形参

即所谓的地址传递,我们都知道地址传递的方式,形参的改变会导致实参的改变,但要注意的是,这里的改变是指指针所指内容的改变,而不是指针值的改变。因此,当形参改变会导致实参改变时,指针所指的内容是非const类型的,否则会出错。

1.改变指针内容:

void swap(int *a,int *b)   //交换的是*a,*b,即指针的内容,而不是指针a,b
{
   int t;
   t=*a;
   *a=*b;
   *b=t;
}

2.改变指针值:

#include<iostream>
using namespace std;
void fun(char *p)
{
    p="cba";    //“cba”存放在文字常量区,让p指向常量"abc",这里改变的是指针值,实参并不会改变
}
void main()
{
    char *p="abc";  //“abc”存放在文字常量区,p指向常量"abc"
    fun(p);
    cout<<p<<endl;   //输出"abc",而不是"bca"
}

继续看下面的情况,修改指针的内容:

#include<iostream>
using namespace std;
void fun(char *p)
{
    p[0]='c';    //改变p的内容,即修改p[0]
}
void main()
{
    char *p="abc";  //p指向常量"abc"
    fun(p);
    cout<<p<<endl;   //error,p所指内容为常量,不能修改
}

注:p="ab"和strcpy(p,"ab"),含义不一样,前者指针p指向常量“ab”存储区域的首地址,改变了p最开始指向的new申请的内存空间;而后者是将“ab”分配到new申请的内存空间中;

 

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页