1.动态内存分配存在的原因
在一般的内存开辟方式中有两个缺点:
1.空间开辟的大小是固定的
2.数组在申明的时候,数组的长度已经指定了,数组空间大小不能够调整了
而动态内存分配可以自己申请和释放空间,比较灵活。
2.malloc和free
2.1malloc
malloc是c语言提供的一个动态内存开辟的函数,其形式如下:
void * malloc ( size_t size);
这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。
•
如果开辟成功,则返回⼀个指向开辟好空间的指针。
•
如果开辟失败,则返回⼀个
NULL
指针,因此malloc的返回值⼀定要做检查。
•
返回值的类型是
void*
,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃
⼰来决定。
•
如果参数
size
为0,malloc的⾏为是标准是未定义的,取决于编译器。
2.2 free
C语言提供了另外一个函数free,专门用来做动态内存释放和回收的,函数原型如下:
void free ( void * ptr);
free函数用来释放动态开辟的内存
•
如果参数
ptr
指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
•
如果参数
ptr
是NULL指针,则函数什么事都不做。
malloc和free都声明在stdlib.h头文件中。
以下为malloc和free的使用例子:
# include <stdio.h># include <stdlib.h>int main (){int num = 0 ;scanf ( "%d" , &num);int arr[num] = { 0 };int * ptr = NULL ;ptr = ( int *) malloc (num* sizeof ( int ));if ( NULL != ptr) // 判断 ptr 指针是否为空{int i = 0 ;for (i= 0 ; i<num; i++){*(ptr+i) = 0 ;}}free (ptr); // 释放 ptr 所指向的动态内存ptr = NULL ;return 0 ;}
3.calloc和realloc
3.1 calloc
calloc也用来动态内存分配 ,其原型如下:
void * calloc ( size_t num, size_t size);
•
函数的功能是为
num
个⼤⼩为
size
的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
•
与函数
malloc
的区别只在于
calloc
会在返回地址之前把申请的空间的每个字节初始化为全0 。
如下所示:
#
include
<stdio.h>
#
include
<stdlib.h>
int
main
()
{
int
*p = (
int
*)
calloc
(
10
,
sizeof
(
int
));
if
(
NULL
!= p)
{
int
i =
0
;
for
(i=
0
; i<
10
; i++)
{
printf
(
"%d "
, *(p+i));
}
}
free
(p);
p =
NULL
;
return
0
;
}
最后输出的结果为:
0 0 0 0 0 0 0 0 0 0
3.2 realloc
•
realloc函数的出现让动态内存管理更加灵活。
•
有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时候内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc
函数就可以做到对动态开辟内存⼤
⼩的调整。
realloc的函数原型如下:
void * realloc ( void * ptr, size_t size);
•
ptr
是要调整的内存地址
•
size
调整之后新⼤⼩
•
返回值为调整之后的内存起始位置。
•
这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到 新 的空间。
•
realloc在调整内存空间的是存在两种情况:
◦
情况1:原有空间之后有⾜够⼤的空间
◦
情况2:原有空间之后没有⾜够⼤的空间
对于情况1:
拓展内存直接在原有内存之后追加空间,原来空间的数据不发生变化。
对于情况2:
在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存空间,原有空间的数据也会被转移到新的内存空间中。
4.常见的动态内存的错误
4.1对NULL指针的解引用操作
如下所示:
void test (){int *p = ( int *) malloc (INT_MAX/ 4 );*p = 20 ; // 如果 p 的值是 NULL ,就会有问题free (p);}
4.2对动态开辟空间的越界访问
void test (){int i = 0 ;int *p = ( int *) malloc ( 10 * sizeof ( int ));if ( NULL == p){exit (EXIT_FAILURE);}for (i= 0 ; i<= 10 ; i++){*(p+i) = i; // 当 i 是 10 的时候越界访问}free (p);}
4.3对非动态开辟内存使用free释放
void test (){int a = 10 ;int *p = &a;free (p);}
4.4 使用free释放一块动态开辟内存的一部分
void test (){int *p = ( int *) malloc ( 100 );p++;free (p); //p 不再指向动态内存的起始位置}
4.5对同一块动态内存多次释放
void test (){int *p = ( int *) malloc ( 100 );free (p);free (p); // 重复释放}
4.6动态开辟内存忘记释放
void test (){int *p = ( int *) malloc ( 100 );if ( NULL != p){*p = 20 ;}}int main (){test();while ( 1 );}
忘记释放不再使用的动态开辟的空间会造成内存泄露
5.柔性数组
在C99中,结构中最后一个元素允许是未知大小的数组,这就叫做柔性数组
如下就是一个典型的柔性数组:
typedef struct st_type{int i;int a[]; // 柔性数组成员}type_a;
5.1柔性数组的特点
•
结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
•
sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。
•
包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩
5.2 柔性数组的使用
// 代码 1# include <stdio.h># include <stdlib.h>int main (){int i = 0 ;type_a *p = (type_a*) malloc ( sizeof (type_a)+ 100 * sizeof ( int ));p->i = 100 ;for (i= 0 ; i< 100 ; i++){p->a[i] = i;}free (p);return 0 ;}
这样就相当于获得了100个整型元素的连续空间。
5.3柔性数组的优势
type_a结构可以设计为下面的结构同样也能完成
// 代码 2# include <stdio.h># include <stdlib.h>typedef struct st_type{int i;int *p_a;}type_a;int main (){type_a *p = (type_a *) malloc ( sizeof (type_a));p->i = 100 ;p->p_a = ( int *) malloc (p->i* sizeof ( int ));// 业务处理for (i= 0 ; i< 100 ; i++){p->p_a[i] = i;}// 释放空间free (p->p_a);p->p_a = NULL ;free (p);p = NULL ;return 0 ;}
代码1相较于代码2有两个好处
1.方便内存释放
2.有利于访问速度
7.总结C/C++中内存区域的划分
C/C++程序分配内存的几个区域:
1.栈区:主要存放运行函数而分配的局部变量,函数参数,返回数据,返回地址等、
2.堆区:⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配⽅
式类似于链表, ++动态内存分配的空间在堆里
3.数据段(静态区):存放全局变量,静态数据,程序结束后由系统释放
4.代码段:存放函数体(类成员函数和全局函数)的二进制代码