首先,在讨论这个话题之前,我们已经知道了如何静态开辟空间,其具有两个特点:
1. 空间开辟
大小是
固定
的。
2.
数组在申明的时候,必须
指定数组的长度
,它所需要的内存在编译时分配。
但有时候我们需要的空间大小在程序运行的时候才能知道,这时候就只能动态开辟内存了。
※动态内存函数
1.malloc
和
free
①void* malloc (size_t size);
这个函数向内存申请一块
连续可用
的空间,并返回指向这块空间的
指针
。
·如果开辟成功,则返回一个指向开辟好空间的指针。
·如果开辟失败,则返回一个
NULL
指针,因此
malloc
的返回值一定要做检查。
·返回值的类型是
void*
,所以
malloc
函数并不知道开辟空间的类型,具体在使用的时候使用者
自己来决定
。
·如果参数
size
为
0
,
malloc
的行为是标准是
未定义
的,取决于
编译器
。
②void free (void* ptr);
free
函数用来
释放
动态开辟的内存。
·如果参数
ptr
指向的空间不是动态开辟的,那
free
函数的行为是
未定义
的。
·如果参数
ptr
是
NULL
指针,则函数什么事都不做。
最后让
ptr = NULL;
目的是为了
释放指针
,因为回收了空间使用权以后ptr就变成
野指针
了。
malloc
和
free
都声明在
stdlib.h
头文件中。
2.calloc
void* calloc (size_t num, size_t size);
·函数的功能是为
num
个大小为
size
的元素开辟一块空间,并且把空间的每个字节
初始化为0
。
·与函数
malloc
的区别只在于
calloc
会在返回地址之前把申请的空间的每个字节初始化为全
0
。所以如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。
3.realloc
realloc
函数的出现让动态内存管理更加灵活。
realloc
函数就可以做到对动态开辟内存大小的调整。 函数原型如下:
void* realloc (void* ptr, size_t size);
·ptr
是
要调整的
内存地址
·size
调整之后
新大小
·返回值为
调整之后
的内存起始位置。
·这个函数调整原内存空间大小的基础上,还会将
原来
内存中的数据移动到
新
的空间。
realloc
在调整内存空间的是存在两种情况:
情况
1:原有空间之后有足够大的空间。这种时候要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2:原有空间之后没有足够大的空间时会 在堆空间上 另找一个 合适大小的连续空间来 使用。这样函数返回的是一个 新的内存地址 。
情况2:原有空间之后没有足够大的空间时会 在堆空间上 另找一个 合适大小的连续空间来 使用。这样函数返回的是一个 新的内存地址 。
※常见的动态内存错误
1.对
NULL
指针的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
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);
}
3.对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);
}
上述代码就是典型的错误示范!
4.使用
free
释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
5.对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
如果另p = NULL;了多次释放也无碍。
6.动态开辟内存忘记释放(
内存泄漏
)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏。因此,动态开辟的空间一定要释放,并且正确释放 。
※C/C++程序的内存开辟
实际上普通的局部变量是在
栈区
分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。
但是被
static
修饰的变量存放在
数据段(静态区)
,数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。
而动态申请的内存都是在
堆区
,堆区一般由程序员分配释放。
※柔性数组
C99
中,结构中的最后一个元素允许是未知大小的数组,这就叫做 柔性数组成员 。
柔性数组的
特点
:
·结构中的柔性数组成员前面必须至少一个其他成员。
·sizeof
返回的这种结构大小不包括柔性数组的内存。
·包含柔性数组成员的结构用
malloc ()
函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。
柔性数组的
优势
:
①方便内存释放;
②
这样有利于访问速度,减少内存碎片
。