0)内存分配方式
内存分配方式有三种:
-
从静态存储区域分配:
内存在程序编译的时候就已经分配好了,这块内存在程序的整个运行期间都存在。
-
在栈上创建:
执行函数时,函数内部变量的存储单位可以在栈上创建。
- 函数执行结束时,这些存储单元自动释放。
- 栈内存分配运算置于处理器的指令集中,效率很高,但是分配的内存容量有限。
-
在堆上分配:
也称为动态内存分配。
- 程序在运行的时候用malloc或new申请任意多少内存,程序员自己负责在何时用free或delete来释放这块内存。
- 动态内存的生命周期由程序员决定,使用非常灵活。
- 但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。也就是我们常说的内存碎片。
*程序内存空间
一个程序将操作系统分配给其运行的内存分为5个区域:
-
栈区(stack area):由编译器自动分配释放,存放为函数运行的局部变量,函数参数,返回数据,返回地址等。操作方式与数据结构中的类似。
-
堆区(heap area):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
-
全局数据区(data area):也叫做静态区,存放全局变量,静态数据。程序结束后由系统释放。
-
程序代码区(code area):存放函数体的二进制代码。但是代码段中也分为代码段和数据段。
- 文字常量区:可以理解为常量区,常量字符串存放这里。程序结束后由系统释放。字常量是不可寻址的。
1)C语言内存分配方式
在C语言中,对象可以使用静态或动态的方式分配内存空间。
-
静态分配:编译器在处理程序源代码时分配。
- 静态内存分配是在程序执行之前进行的因而效率比较高。
-
动态分配:程序在执行时调用malloc库函数申请分配。
*静态与动态内存分配区别
- 静态对象是有名字的变量,可以直接对其进行操作;动态对象是没有名字的一段地址,需要通过指针间接地对它进行操作。
- 静态对象的分配与释放由编译器自动处理;动态对象的分配与释放必须由程序员显式地管理,它通过malloc()和free两个函数来完成。
1.1 静态分配方式
int a = 100;
- 此行代码指示编译器分配足够的存储区以存放一个整型值,该存储区与名字a相关联,并用数值100初始化该存储区。
1.2 动态分配方式
动态分配内存的定义是这样的,指在程序运行过程中,要申请内存,系统会根据程序的实际情况来分配,分配空间的大小是由程序的需求来决定的。
p1 = (char *)malloc(10*sizeof(int));
在C语言下面,举个例子,定义一个指针,int *p;
- 此时指针 i 是一个野指针,是一个指向不确定位置的指针,对它进行操作是很危险的,此时我们需要动态分配内存空间,让 i 指向它。
- 而有一种形式是这样的,
int *p=&b
,这并非是一种动态内存分配方式,而是一种指针的初始化,把变量b的首地址给了指针p。
C语言下供了几个函数来实现动态内存分配,分别是malloc()、calloc()、realloc(),而释放内存的函数为free()。
1、malloc函数
函数原型为void *malloc(unsigned int size);
- 在内存的动态存储区中分配一块长度为"size" 字节的连续区域。
- 函数的返回值为该区域的首地址。
- “类型说明符”表示把该区域用于何种数据类型。
- (类型说明符*)表示把返回值强制转换为该类型指针。
- “size”是一个无符号数。
例如: pc=(char *) malloc (100);
表示:
- 分配100个字节的内存空间
- 并强制转换为字符数组类型
- 函数的返回值为指向该字符数组的指针
- 把该指针赋予指针变量pc
- 若size超出可用空间,则返回空指针值NULL。
2、calloc 函数
函数原型为void *calloc(unsigned int num, unsigned int size)
- 按所给数据个数和每个数据所占字节数开辟存储空间。
- 其中num为数据个数,size为每个数据所占字节数,故开辟的总字节数为num*size。
- 函数返回该存储区的起始地址。
-
calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。
例如: ps=(struct stu*) calloc(2,sizeof (struct stu));
- 其中的sizeof(struct stu)是求stu的结构长度。
- 因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。
3、realloc函数
函数原型为void *realloc(void *ptr, unsigned int size)
-
重新定义所开辟内存空间的大小。
- 其中ptr所指的内存空间是用前述函数已开辟的,size为新的空间大小,其值可比原来大或小。
- 函数返回新存储区的起始地址(该地址可能与以前的地址不同)。
例如p1=(float *)realloc(p1,16);
将原先开辟的8个字节调整为16个字节。
4、free函数
函数原型为void free(void *ptr)
-
将以前开辟的某内存空间释放。
- 其中ptr为存放待释放空间起始地址的指针变量,函数无返回值。
- 应注意:ptr所指向的空间必须是前述函数所开辟的。
- 注意:动态申请的内存空间要进行手动用free()函数释放。
- malloc、calloc、realloc都是在堆上分配的,堆上分配的空间必须由用户自己来管理。
例如free((void *)p1);
将上例开辟的16个字节释放。
- 可简写为
free(p1);
,由系统自动进行类型转换。
2)C++语言动态内存分配
C++语言中用new和delete来动态申请和释放内存。
2.1 申请
new 是个操作符。
运算符new使用起来要比函数malloc简单得多。
- 这是因为new内置了sizeof、类型转换和类型安全检查功能。
- 对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。
-
申请单个对象:
int *p;
p=new int; //或者 p=new int(value);
new内部的调用顺序:(初始化一个对象时):new-->operator new-->malloc-->构造函数
-
动态申请数组:
int *p;
p=new int [100]; //这样可以申请长度为100的数组,但是不能进行初始化。
new内部的调用顺序:(初始化若干个对象时):new-->operator new[]-->operator new-->malloc-->构造函数
2.2 释放
int *p, *q;
p=new int;
q=new int[10];
delete p;
delete []q;
- delete单个对象时,调用顺序为:
delete-->析构函数-->operator delete-->free
- delete多个对象时,调用顺序为:
delete-->析构函数-->operator delete[]-->operator delete-->free
3)new/delete与malloc/free
联系:
- 它们都是动态管理内存的入口。
- new/delete的底层调用了malloc/free。
区别:
- malloc/free是C/C++标准库的函数,new/delete是C++操作符。
- new 建立的是一个对象,会调用构造函数,malloc分配的是一块内存。
- malloc/free只是动态分配内存空间/释放空间。
- 而new/delete除了分配空间,还会调用构造/析构函数进行初始化与清理(清理成员)。
- malloc/free需要手动计算类型大小且返回值为void*,new/delete可自己计算类型的大小对应类型的指针。
- malloc/free申请空间后得判空,new/delete则不需要。
- new直接跟类型,malloc跟字节数个数。
*有了malloc/free为什么还要new/delete?
malloc与free是C /C语言的标准库函数,new/delete是C 的运算符。它们都可用于申请动态内存和释放内存。
- 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。
- 对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
- 由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。
4)常见的内存错误及其对策
发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。
5)指针与数组的对比
-
数组:数组是用于储存多个相同类型数据的集合。
-
指针:指针相当于一个变量,但是它和不同变量不一样,它存放的是其它变量在内存中的地址。
1、赋值
- 同类型指针变量可以相互赋值。
- 数组不行,只能一个一个元素的赋值或拷贝
2、存储方式
3、求sizeof
数组:
- 数组所占存储空间的内存:sizeof(数组名)
- 数组的大小:sizeof(数组名)/sizeof(数据类型)
指针:
- 在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4。
- 在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。
4、初始化
数组:
(1)char a[]={"Hello"};//按字符串初始化,大小为6
(2)char b[]={'H','e','l','l'};//按字符初始化(错误,输出时将会乱码,没有结束符)
(3)char c[]={'H','e','l','l','o','\0'};//按字符初始化
指针:
//(1)指向对象的指针:(()里面的值是初始化值)
int *p=new int(0) ; delete p;
//(2)指向数组的指针:(n表示数组的大小,值不必再编译时确定,可以在运行时确定)
int *p=new int[n]; delete[] p;
//(3)指向类的指针:(若构造函数有参数,则new Class后面有参数,否则调用默认构造函数,delete调用析构函数)
Class *p=new Class; delete p;
//(4)指针的指针:(二级指针)
int **pp=new (int*)[1];
pp[0]=new int[6];
delete[] pp[0];
【部分内容参考自】
- 深入理解C语言内存管理:https://www.cnblogs.com/jack-hzm/p/11545026.html
- C+±内存管理:https://blog.csdn.net/skrskr66/article/details/92769994
- c++详解【new和delete】:https://blog.csdn.net/xxpresent/article/details/53024555
- C和C++ 语言动态内存分配:https://www.cnblogs.com/zhj202190/archive/2011/05/11/2043620.html
- 数组和指针的区别与联系(详细):https://blog.csdn.net/cherrydreamsover/article/details/81741459
- C 内存管理详解:https://blog.csdn.net/ysdaniel/article/details/6643689
-