目录
1.为什么会有动态内存管理
我们已经熟练的两种空间开辟方式:
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
上面两个空间开辟方式有些特点: •空间开辟的大小是固定的 •数组在创建时,就要明确指出数组的大小,一旦创建就不能再改,但是这两个开辟方式太固定化了,有时候我们的程序运行时才能知道需要多少空间大小,那数组编译时开辟空间的大小就不适用了。C语言引入了动态内存管理,让我们可以申请和释放空间,这就非常灵活了。
2.malloc和free
2.1 malloc
C语言提供了一个动态内存开辟函数:
void* malloc (size_t size);
该函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。 • 如果开辟成功,则返回指向新空间的指针 • 如果开辟失败,则返回NULL指针,所以检查空间开辟是否成功是必要的 • 返回值类型是void*,所以malloc函数并不知道创开辟空间的类型,具体要我们自己来决定 • 如果size为0,malloc的行为是标准未定义的,具体看编译器
2.2 free
C语言还提供了一个函数free,用来释放和回收动态开辟的空间:
void free (void* ptr);
free函数是释放和回收动态开辟的内存。 • 如果ptr是NULL指针,函数啥都不做 • 如果ptr指向的空间不是动态开辟的,free的行为未定义
malloc和free函数都声明在stdlib.h头文件里。
举个例子:
#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;
}
ptr=NULL这一步很有必要,因为我们释放了ptr指向的空间,如果再依靠ptr访问该空间,就属于野指针访问,将导致未定义行为。
3.calloc和realloc
3.1 calloc
C语言还提供了一个函数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
所以如果我们需要对开辟的空间初始化,可以直接使用calloc很好的完成任务。
3.2 realloc
realloc使动态内存管理变得更加灵活,前面介绍的malloc和calloc都是申请空间,而realloc可以调整动态开辟的空间大小。
void* realloc (void* ptr, size_t size);
•
ptr 是要调整的内存地址
•
size是调整之后空间的大小
•
返回值为调整之后的内存起始位置。
•
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
•
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
情况1呢就是直接在原来的空间后面追加空间,初始地址不变,原来的数据不变。
情况2呢就是原来空间加上后面的空余空间也不够,这时候只能在堆空间上找一段合适的空间来使用,这时候返回新的内存地址。
也是因为有上面两种情况的发生,因此我们使用realloc函数的时候需要小心点。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr = (int*)malloc(100);
if(ptr != NULL)
{
//业务处理
}
else
{
return 1;
}
//扩展容量
//代码1 - 直接将realloc的返回值放到ptr中
ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
//代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中
int*p = NULL;
p = realloc(ptr, 1000);
if(p != NULL)
{
ptr = p;
}
//业务处理
free(ptr);
return 0;
}
直接将realoc的返回值放到ptr里是不可以的,因为可能申请失败,这时候返回的就是NULL,ptr=NULL,我们就找不到原来的空间了,所以需要创建一个新指针接收。
4.常见的动态内存的错误
4.1 对NULL指针的解引用
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//?
free(p);
}
这个就是上面说到的问题,我们不能确定空间是否申请成功,如果失败返回NULL,那么指针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;//?
}
free(p);
}
这个还是很明显的,一共就申请了10个int类型大小的空间,也就是当i=9时,访问到最后一个元素,结果i还能等于10,这肯定就越界了啊。
4.3 对非动态开辟内存使用free
void test()
{
int a = 10;
int *p = &a;
free(p);//?
}
指针p是局部变量,不能释放,可能会出现使程序崩溃等问题。(free不能释放动态开辟内存以外的内存)
4.4 使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//?
}
因为p++,所以p已经不指向这块动态内存的起始位置了,使用free不能释放这整块动态内存。
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);
}
这个程序里动态申请了空间,直到最后也没有手动释放,这个问题很经典,这只是一小段代码而且没有合作,假如我们在公司里一起编写一个软件,那么我的这段代码申请了一块空间,他的代码申请一块空间......大家都不释放,那么肯定会出现问题(当程序结束时,OS会回收空间)。
所以我们申请了空间就要记得释放,养成好习惯。
5.柔性数组
可能你没听过柔性数组这么一个东西,但它的确存在,在C99里,结构体里最后一个成员是允许是不知大小的数组,这就是柔性数组成员。
struct st_type
{
int i;
int a[0];//柔性数组成员
};
不过上面的写法在一些编译器可能出错,还有种写法:
struct st_type
{
int i;
int a[];//柔性数组成员
};
5.1柔性数组的特点
1.柔性数组的前面至少有一个其他成员 2.sizeof计算结构体的大小不包含柔性数组大小 3.包含柔性数组的结构体用malloc进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小
比如:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
int main()
{
printf("%d\n", sizeof(type_a));//输出的是4
return 0;
}
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;
}
这样的柔性数组成员a就获得了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;
}
6.总结C/C++程序中内存区域划分
C/C++程序内存分配的几个区域:
1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时 这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内 存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。 《函数栈帧的创建和销毁》 2. 堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配⽅ 式类似于链表。 3. 数据段(静态区):(static)存放全局变量、静态数据。程序结束后由系统释放。 4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。 标签:malloc,管理,int,free,动态内存,空间,NULL,ptr From: https://blog.csdn.net/wangzhezhifwng/article/details/142352753