关于C/C++内存问题(个人笔记,提供分享)

C/C++程序编译的时内存分为5大存储区栈区(stack):该区是由编译器自动分配释放,一般存放的是函数的参数值和局部变量等,它的操作方法类似数据结构中的栈。堆区(heap):该区一般由程序员分配与释放,但它与数据...

C/C++程序编译的时内存分为5大存储区

  1. 栈区(stack):该区是由编译器自动分配释放,一般存放的是函数的参数值和局部变量等,它的操作方法类似数据结构中的栈。
  2. 堆区(heap):该区一般由程序员分配与释放,但它与数据结构中的堆毫无关系,它的分配方式类似于链表的分配方式。
  3. 静态区/全局区(static):该区存储的是全局变量和静态变量(静态全局和静态局部),在程序编译的时候分配,默认初始值为0或者NULL。
  4. 文字常量区:该区存放字符串常量。
  5. 程序代码区:该区存放函数体的二进制代码,其中函数体可以是类的成员函数和全局函数。
int a = 0; //全局变量,存储在全局初始化区
int b; //全局变量,存储在全局未初始化区
static int c = 0; //静态全局变量,存储在全局区
int main()
{
	int d = 0; //局部变量 ,存储在栈区
	char buf[] = “”; //局部变量,存储在栈区
	char *p1; //局部指针变量,存储在栈区
	char *p2 = "abcdefg"; //其中p2在栈区,“abcdefg”在文字常量区
	static int e = 0; //静态局部变量,存储在全局区
	p1 = (char *)malloc(10); //动态开辟内存,10个字节区域都存储在堆区
	strcpy(p1,"123"); //"123"在文字常量区,p1指向p2指向的区域
	free(p1); //释放动态开辟的内存
	retrun 0;
}

C/C++内存分配有三种方式

  1. 栈分配:函数执行的时候,函数内部定义的局部变量存储单元都可以在栈上创建,函数执行结束后,栈上分配的存储单元都会自动释放,栈内存分配运算内置在处理器的指令集中,因此执行效率高,但栈的资源有限。
  2. 堆分配:在堆上分配空间又可称为动态内存分配,可由程序员调用malloc/new来申请内存,只要不调用free/delete释放内存,这些申请的内存在程序整个运行期都存在,在程序结束前,需要程序员在适当的位置调用free/delete来手动释放申请的内存空间。
  3. 静态区分配:该区存放的是全局变量或者静态变量,这些变量的内存在程序编译的时候就已经分配好,而且在程序整个运行期都会存在。

关于内存池
堆分配的内存是动态的,动态内存的生存期由程序员决定,使用十分灵活,但是频繁的分配和释放不同的堆空间会产生堆内碎块。
解决堆内碎块的方法就是使用内存池,内存池的使用不仅能减少内存碎片的产生,还能提高内存使用效率,唯一确定就是往往申请的内存池空间大于用户使用的内存,导致部分资源浪费,但该问题是可控的。
关于内存池的使用:
(1)创建内存池,在C++中使用内存池的时候,最好使用map<int, deque< pair<char *, bool> > >结构来管理这内存池中的这些内存,在该结构中,键为内存块的大小,值为所有大小为键的内存块的一个双向队列。该队列中,每个元素有两部分组成,第一部分是指向该内存块的指针,第二部分是该内存块是否空闲的标志。
(2)分配内存,当用户申请内存的时候,先计算用户需要的内存大小,然后到内存池中寻找符合用户需要的内存块,如果内存池有对应空闲的内存块,便返回给用户,如果没有,则需要调用malloc或者new去分配一块内存,然后将该内存添加到内存池中,同时标记内存状态已用。需要注意的是:尽可能减少直接调用malloc或new的次数,否则内存池在效率上的优势就体现不出来了。内存池设计的优劣很大程度上也是在这里决定的。好的设计,可以保证miss率很低,这样就很好地发挥了内存池的优势。
(3)释放内存,内存的释放,不是真正地调用free或是delete的过程,而是把内存放回内存池的过程,在把内存放入内存池的同时,要把标志位置为空闲。
(4)释放内存池,要把内存池销毁。这里主要做的工作就是把内存池中的每一块内存释放。

关于局部变量
在嵌入式开发中,尽量限制函数内部循环所用的局部变量的数目,最多不超过12个,以便编译器能把变量分配到寄存器。
一般情况下,限制函数内部可使用的局部变量不超过12个,这样,编译器就可以把这些变量都分配给ARM寄存器;编译器会试图对C函数中的每一个局部变量分配一个寄存器。如果几个局部变量不会交迭使用,那么编译器会对它们分配同一个寄存器。当局部变量多于可用的寄存器时,编译器会把多余的变量存储到堆栈。由于这些变量被写入了存储器,所以被称为溢出或者替换(swapped out)变量,就像虚拟存储器的内容被替换到硬盘一样。与分配在寄存器中的变量相比,对溢出变量的访问要慢得多。

关于new/delete与malloc/free
1.首先先简单的介绍下new/delete与malloc/free。
(1)malloc和free是C/C++的标准库函数,而new和delete是C++的运算符,它们的作用都是用来动态申请内存和释放内存。
(2)在C++中,由于类的原因,malloc/free无法满足动态对象的要求,因为对象的创建需要调用构造函数,对象的销毁需要调用析构函数,因此只能使用C++的运算符new/delete实现。
(3)C++中经常通过extern去调用c的函数,而c的程序只能通过malloc/free去申请与释放内存。
(4)new/free实际上是基于malloc/free为基础的,因此可以理解为new是malloc加构造函数的执行,delete是free加析构函数的执行,new出来的指针是直接带类型信息的,而malloc返回的都是void指针。
2.接下来深入了解下new/delete与malloc/free。
new/delete与malloc/free都是动态分配内存区域,而由于C++概念和C是有区别的,从技术上来说,堆是C语言和操作系统的属于,而不能属于C++定义的范畴,C++中有新的定义名,称之为自由存储,自由存储是基于堆实现的,但是不等价于堆,具体区别,小编建议你们详细阅读书籍《exceptional C++》。
有了以上的概念,因此C++的内存布局应该分为以下五个区域:
堆,栈,自由存储区,全局/静态区,常量区
由于C++中会调用C程序,而C程序动态分配释放内存只能用malloc/free,而C语言中动态分配内存的区域只有堆,因此malloc申请的区域在堆中。
栈还是存储的局部变量,全局/静态区存储的还是全局变量或静态变量,常量区存放的还是字符串常量。
自由存储区是C++通过new与delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,例如全局变量做的对象池,这时自由存储区就区别于堆了。
3.最后总结.
(1)自由存储是C++中通过new与delete动态分配和释放对象的抽象概念,而堆(heap)是C语言和操作系统的术语,是操作系统维护的一块动态分配内存。
(2)new所申请的内存区域在C++中称为自由存储区。藉由堆实现的自由存储,可以说new所申请的内存区域在堆上。
(3)堆与自由存储区还是有区别的,它们并非等价。

常见的内存错误及对策
(1)使用未分配的内存
这个错误很常见,很多程序员在开发的时候,没有注意内存是否已分配,特别是没注意内存是否分配成功,然后使用出现内存错误。
解决办法:在使用内存的时候加上判断指针是否为NULL。
如果是函数入口,可以通过assert(p != NULL)进行判断。
如果是通过new或者malloc申请的内存,可以通过if(p != NULL)判断。
(2)使用已分配却未初始化的内存
有部分程序员在使用内存的时候,没有初始化的概念,误以为内存的缺省初值全为零,导致引用初值错误,最常见是数组。
解决办法:由于内存的缺省初值并没有什么统一的标准,因此在创建数组的时候,一定要赋初值。
(3)内存越界
最常见的就是数组越界了,这个很容易看出来,细心写程序。
(4)没释放内存导致内存泄漏
在使用new或者malloc申请内存的时候,没有通过delete或者free释放内存,刚开始内存还够,申请过多就会出现内存耗尽的情况。
(5)使用已释放的内存
常见的三种情况:
1.程序中的对象调用关系过于复杂,难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
2.函数return语句写错了,注意不要返回指向“栈内存”的指针或者引用,因为该内存在函数体结束时理论上被自动销毁。
3.使用free或者delete释放了内存后,没有将指针设置为null,导致产生野指针。
这些都比较容易解决。
(6)多次释放同一块内存
多次释放同一块的申请的内存,也会导致内存错误,因此在写代码的时候一定要细心规范。

内存管理需要遵循的规则
(1)用malloc 或者 new 申请内存之后,应该立即检查指针值是否为 NULL ,防止使用指针值为NULL的内存;
(2)不要忘记数组和动态内存赋初值,防止未被初始化的内存作为右值使用;
(3)避免数组或者指针下标越界,特别要当心“多1”或者“少1”的操作;
(4)动态内存的申请与释放必须配对,防止内存泄露;
(5)用free或者delete释放了内存之后,立即将指针设置为NULL,防止产生“空悬指针/野指针”;

关于空悬指针和野指针
空悬指针:指向的空间已被释放
野指针:指针未被初始化(赋值)

空悬指针有以下三种情况:
情况一:

int main(){
   char a;
   char *p = &a;
   
   free(p); //变量p指向的空间被释放,p变成空悬指针
   return 0; 
}

情况二:

int main()
{
    char *p = (char *)malloc(10);
    free(p);         //p变成一个空悬指针
    p = NULL;        //p不再是空悬指针
    return 0;
}

情况三:

int * func ( void )
{
    int num = 1234;
    /* ... */
    return &num;
    //num是基于栈的变量,当func函数返回,变量的空间将被回收,
    //此时获得的指针指向的空间有可能被覆盖。
}

野指针实例:

int main()
{
    char *p1;//野指针,没有初始化
    static char *p1;//非野指针,因为静态变量会默认初始化为0
   return 0;
}

防止空悬指针解决办法就是指针用完以后,一定要指向空。
防止野指针的解决办法就是定义指针的时候,记得初始化为空,或者定义成静态变量。

关于C/C++的存问题先整理到这里了,本文借鉴了部分博文,希望对你有所关注,不懂得可私信小编。

本文标题为:关于C/C++内存问题(个人笔记,提供分享)

上一篇: c++内存释放

基础教程推荐