开始之前,我们解释一为什么存在动态内存分配?
在我们之前写的:
cs
int arr[10]={0}; 连续开辟40个字节的空间
int a=10; 在内存开辟4个字节
但是,
1.这种大小是固定死的,我们是无法改变的。
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了
对此,动态存的开辟就显得十分优异的做法了:
所以,开始我们的正文吧!
1.我们先了解各区的分布情况
一.malloc函数
头文件:#include <stdlib.h>
(内存块的大小,以字节为单位)
cs
void* malloc (size_t size);
使用规则:
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器
cs
int main()
{
int* pf=(int *)malloc(20); 这里要强制类型转换
if(NULL==pf)
建议这里将NULL写在前面,因为不知道有没有一天,你会不小心将==写成了一个,
如果这样写的话它直接出现错误,告知你,
如果你写成if(pf=NULL),它不会显示错误的,对新手很不友好(不容易发现)。
问就是我做过
{
perror("malloc");
这里如果为空指针,打印出来,你也可以使用其他形式,我喜欢这种,方便
return 1;
}
return 0;
}
二.free函数
头文件:#include <stdlib.h>
1.作用:释放动态开辟的内存(只能动态)
2.如果参数str指向的空间不是动态,free的行为是未被定义的
3.str指向的空间是空指针,则什么事都不做。
cs
int main()
{
int num = 0;
scanf("%d", &num);
int arr[num] = {0};
int* pf = NULL;
pf= (int*)malloc(num*sizeof(int));
if(NULL != pf)//判断pf指针是否为空
{
int i = 0;
for(i=0; i<num; i++)
{
*(pf+i) = 0;
}
}
free(pf);释放pf所指向的动态内存
pf = NULL;
return 0; 主动弄为NULL,如果不弄可能会造成非法访问
}
三.calloc
1.其实与malloc的用法差不多
区别:若把申请的值放得足够大
malloc申请到的空间,没有初始化,直接返回起始地址
calloc申请好空间后,会把空间初始化为0,然后返回起始地址
对此以后按照需求使用malloc还是calloc。
对于,我们要申请的内存空间,需要初始化时,使用这个就特别方便了。
2.使用规则:
cs
void* calloc (size_t num, size_t size);
函数的功能是num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
cs
#include<stdio.h>
#include<stdlib.h>
int main()
{
int *pf=calloc(20,sizeof(int));
if(NULL==calloc)
{
perror("calloc");
return 1;
}
free(pf);
pf=NULL;
return 0;
}
四.realloc
1.作用:扩容
realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,对此,我们会对内存的大小做灵活的调整
cs
void* realloc (void* pf, size_t size);
*pf 就是你要扩容的指针
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
realloc在调整内存空间的是存在两种情况:
第一种:原有空间之后有足够大的空间
这种会直接在后面追加,原来空间的数据不会发生改变。
第二种:原有空间之后没有足够多的空间时
这时,我们应该在堆空间上另找一个合适大小的连续空间来使用。
这样函数返回的是一个新的内存地址
也就是把旧的空间的数据,拷贝新空间的前面位置,并且把旧的空间释放掉,同上返回新的空间的地址
使用时需要注意的一些情况:
cs
#include <stdio.h>
int main()
{
int *ptr = malloc(100);
if(ptr != NULL)
{
perror("malloc");
}
//扩展容量
//代码1
ptr = realloc(ptr, 1000);这样可以吗?(如果申请失败会如何?)
直接返回NULL,如果像这样直接ptr,会把旧的数据搞丢
这也是为什么,建议使用新的指针来
//代码2
int*p = NULL;
p = realloc(ptr, 1000); 所以新指针
if(p != NULL)
{
ptr = p;
}
//业务处理
free(ptr);
return 0;
}
列举一些使用动态内存使用时容易出现的错误:
1.对NULL指针的解引用操作
cs
void test()
{
int *p = (int *)malloc(20);
*p = NULL; 问题出现在空指针这里
free(p);
}
失败的问题:当申请空间时太大而造成堆区内存申请的不够。此时,返回空指针。
试图通过空指针对数据进行访问,而导致运行时的错误,
程序试图通过解引用一个非空,(但实际确实是空)的数,会发生空指针解引用错误,导致成了未定义的行为。这种情况大多数的平台会导致程序异常和拒绝服务的情况。
2.对动态开辟空间的越界访问
cs
int main()
{
int *p=(int*)malloc(20); //这里申请了4个int
if(NULL==p)
{
perror("malloc");
return 1;
}
int i=0;
for(i=0;i<=4;i++) 当=4时,它就超过了申请空间,是越界访问。此时就是野指针了
{
*(p+i)=i;
}
free(p);
p=NULL;
return ;
}
3.对非动态开辟内存使用free释放
cs
void test()
{
int a = 10;
int *p = &a;
free(p);
p=NULL;
return 0;
}
4.使用free释放一块动态开辟内存的一部分
cs
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
5.对同一块动态内存多次释放
cs
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
6.动态开辟内存忘记释放(内存泄漏)
cs
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
!!!重要!!!!:动态开辟的空间一定要释放,并且正确释放
下面再给几道题,使得更加清晰的了解:
cs
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
分析:
1.str传给p的时候,p是str的临时拷贝,有独立的空间
当GetMemory函数内部申请了空间后,地址放在p中时,str依然是NULL,
当GetMemory函数返回之后,strcpy拷贝的时候,形成了非法访问内存
2.在GetMemory函数内部,动态申请了内存,但是没有释放,会内存泄漏
那么,怎么修改呢?
因为str本来就是一级指针,取地址后,那是不是变成了二级指针了
所以应该是char**p,而p里面放的是str的地址,想要找到str,
那么就应该是*p被赋值给函数
改正版本:
cs
void GetMemory(char **p)
{
*p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
有小伙伴会问:为什么这个可以printf(str)?
例如:
char* p="hehe\n"
printf("hehe\n");
printf(p);
这两者本质上传的都是h元素的地址,所以一样的
同样,这个代码也是将放在str的地址
str是不是指向这个典型空间,然后strcpy
把hello world拷贝放在str里面,然后str不是指向那字符串的起始位置上吗?
题目二:
cs
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
返回栈空间地址的问题:
这里虽然确实得到了p中首元素h的地址
但是当你出去返回函数了之后,char p[]里面的数就销毁了
但是出去了之后,在Test函数中记住了之前的的地址,就会进行访问,从而造成了非法访问
怎么改呢? -按照实际需求改就行
方法1:
改为char* p就可以了
为什么呢?"hello world"是一个常量字符串,这个就在内存里面存放着(某区域)
然后把这个地址交给p,p这个地方可以销毁,但是我把这个地址放到str里面去了,
所以仍然可以通过str去找到这个常量字符串,常量字符串并不是像刚刚的局部变量一样
局部变量进来创建,出去销毁
但是p是局部变量,p指向常量字符串,常量字符串并不是局部范围的一部分,所以并不会随函数返回而销毁
方法2 :
可以加static,因为这个不会还给操作系统
cs
char *GetMemory(void)
{
char* p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
cs
char *GetMemory(void)
{
static char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
题目3:
二级指针接受一级指针的地址
cs
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
问题:
内存泄漏,忘记释放内存空间
题目4:
cs
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str); <---注意,没有NULL
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
虽然已经释放,不属于我们了,但是看清,它没有设为NULL,所以仍然指向h那里,但它已经没有权限访问了
在一步中,只要前面申请成功了,基本不会NULL,所以变成了野指针,将world\0覆盖到了hello\0里,强行,造成非法访问
柔性数组
C99 (才出现的)中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
cs
struct S
{
int a=20;
char arr[]; 表示数组的大小是未知的
也可以改成
//char arr[0]; 一些编译器不可这样,那就看两个哪个可以吧
};
柔性数组的特点:
1.结构中的柔性数组成员前面必须至少一个其他成员。
2.sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于 结构的大小,以适应柔性数组的预期大小
柔性数组结构体中计算大小时,不算进去
cs
struct S
{
int i;
int a[0];//柔性数组成员
};
int main()
{
printf("%d\n", sizeof(struct S));
}
答案为4
柔性数组的使用
cs
int main()
{
int i = 0;
struct S *p = (struct S*)malloc(sizeof(struct S*)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
p->a[i] = i;
}
free(p); 这里释放了一次
p=NULL;
return 0;
}
不使用柔性数组:
cs
typedef struct S
{
int i;
int *p_a;
}S;
int main()
{
S *p = malloc(sizeof(S));
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;
总结:
好处:1.方便内存释放
2.这样有利于访问速度
最后的最后,让我们一起在心中默念点赞今天努力的自己吧!
点赞我们无论多么困难,仍不断奋斗,在努力使自己变强的路上,做一个孤独的奋斗者!