1、为什么存在动态内存分配
int a = 10;
int arr[10];
上述方式开辟的空间不可变,但有时我们需要的空间大小在程序运行的时候才知道
2、动态内存函数的函数
2.1、malloc和free
动态内存开辟 void* malloc(size_t size)
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针
如果开辟成功,则返回一个指向这块空间的指针;
如果开辟失败,则返回NULL指针,因此malloc的返回值一定要做检查;
返回值的类型是void*,所以malloc函数其实并不知道开辟空间的类型,具体在使用的时候使用者自己来决定;
如果参数size为0,这种行为是标准未定义的;
int main()
{
int* p = (int*)malloc(40);//malloc申请的空间大小单位为字节
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));//malloc申请到空间后,直接返回这块空间的起始地址,不会初始化空间的内容
}
free(p);
p = NULL;
return 0;
}
malloc申请的内存空间,当程序退出时,还给操作系统,当程序不退出,动态申请的内存不会主动释放,需要使用free函数来释放
C语言提供另一个函数free,专门是用来做动态内存的释放和回收的
void free(void* ptr)
如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
如果参数ptr是NULL指针,则函数什么事都不做
int main()
{
int arr[10] = { 0 };
int* ptr = NULL;
ptr = (int*)malloc(40);//将malloc所开辟空间返回的指针赋给ptr
if (ptr != NULL)//检查ptr是否为空
{
int i = 0;
for (i = 0; i < 10; i++)
{
*(ptr + i) = 0;//给开辟的空间赋值为0
}
for (i = 0; i < 10; i++)
{
printf("%d\n", *(ptr + i));
}
}
free(ptr);
ptr = NULL;//释放指针
return 0;
}
2.2、calloc
calloc函数也用来动态内存分配
void* calloc(size_t num,size_t size)
函数的功能是为num个大小为size的元素开辟一块空间,并且把每个空间的每个字节初始化为0
与函数malloc的区别只在于calloc会在返回地址之前把申请的每个空间自己初始化为0
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));
}
free(p);
p = NULL;
return 0;
}
2.3、realloc
void realloc(void* ptr,size_t size);
ptr是要调整的内存地址
返回值为调整之后的内存起始位置
这个函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
realloc在调整内存空间时存在两种情况:
1、原有空间之后有足够大的空间,直接加
2、后面的空间不够:
1、开辟新空间
2、将旧的空间的数据拷贝到新的空间
3、释放旧的空间
4、返回新空间的起始地址
int main()
{
int* p = (int*)malloc(40);//开辟40字节的空间
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)//赋值
{
p[i] = i;
}
int* ptr = (int*)realloc(p, 80);//在p原有基础上再增加80字节空间
if (ptr != NULL)
{
p = ptr;
ptr = NULL;
}
else
{
perror("realloc");
return 1;
}
for (i = 0; i < 30; i++)
{
printf("%d\n", p[i]);
}
return 0;
}*/
3、常见的动态内存错误
3.1、对NULL指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;//如果p的值是NULL,就会有问题,所以在使用前都需要进行检验
free(p);
}
3.2、对动态开辟空间的越界访问
int main()
{
int* p = (int*)malloc(40);//开辟40字节的空间
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 20; i++)//使用20个整型空间,即80字节
{
p[i] = i;
}
return 0;
}
3.3、对非动态开辟内存使用free释放
void main()
{
int a = 10;
int* p = &a;//p不是动态开辟的,是直接定义的
printf("%d\n", *p);
free(p);
p = NULL;
return 0;
}
3.4、使用free释放一块动态开辟内存的一部分
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*p = i;
p++;//p经过++,已经不再指向malloc开辟的40字节空间的开头了
}
free(p);
p=NULL;
return 0;
}
3.5、对同一块空间的多次释放
int main()
{
int* p = malloc(40);
if (p = NULL);
{
perror("malloc");
return 1;
}
free(p);
free(p);
return 0;
}
3.6、忘记释放(内存泄漏)
在函数中malloc100个字节的空间,并且把地址给p,当离开作用域后,p的值销毁,且开辟的空间没有释放,这100字节的空间就永久无法使用
动态申请的内存空间,不会因为出了作用域自动销毁(还给操作系统)
只有两种方式销毁:free;程序结束
void test()
{
int* p = (int*)malloc(100);
if (p != NULL)
{
*p = 20;
}
}
void main()
{
test();
return 0;
}
练习:以下程序能否成功运行
1、
void GetMemory(char* p)
{
p = (char*)malloc(100);//空间开辟后没有释放,造成内存泄漏
}
void Test(void)
{
char* str = NULL;
GetMemory(str);//此处的传值并不能改变str指向的内容,仍然是空指针
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
修改为以下即可
void GetMemory(char** p)//使用二级指针接收
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);//函数传指针str的地址
strcpy(str, "hello world");
printf(str);
free(str);//释放空间
str = NULL;
}
int main()
{
Test();
return 0;
}
2、
char* GetMemory(void)
{
char p[] = "hello world";//将数组创建为static变量即可
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();//此处str可以拿到GetMemory的返回值p的地址,但是p地址指向空间的内容离开GetMemory函数后已经销毁了,p也变成了野指针
printf(str);
}
int main()
{
Test();
return 0;
}
3、
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);//代码可以正常运行,但是没有free
strcpy(str, "hello");
printf(str);
}
int main()
{
Test();
return 0;
}
4、能否运行
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);//str释放后空间已经还给操作系统,后面的使用为非法访问,在释放后应该立马置空NULL
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
4、柔性数组
4.1、柔性数组的特点
结构中的柔性数组成员前面必须要有至少一个成员
sizeof返回的这种结构大小不包含柔性数组的内存
包含柔性数组成员的结构一般用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
struct S
{
int n;
int arr[0];//柔性数组
};
int main()
{
//printf("%d\n", sizeof(struct S));//打印4
struct S* ps = (struct S*)malloc(sizeof(struct S) + 40);//malloc开辟44字节空间给结构指针ps
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->n = 1;//结构指针ps元素n赋值为1
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
struct S* ptr = (struct S*)realloc(ps, 80);
if (ptr == NULL)
{
perror("realloc");
return 1;
}
ps = ptr;
ps->n = 2;
for (i = 0; i < 15; i++)
{
printf("%d\n", ps->arr[i]);
}
free(ps);
ps = NULL;
return 0;
}
另一种实现方式
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S));
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->n = 1;
ps->arr = (int*)malloc(40);
if (ps->arr == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
int* ptr = realloc(ps->arr, 80);
if (ptr == NULL)
{
perror("realloc");
return 1;
}
ps->arr = ptr;
for (i = 0; i < 15; i++)
{
printf("%d\n", ps->arr[i]);
}
free(ps);
ps = NULL;
free(ps->arr);
ps->arr = NULL;
return 0;
}
上述两段代码虽然实现的功能相同,但是第一段有更多好处
1、内存分配的次数少,需要释放的次数就少
2、有利于访问速度,连续的内存可以减少内存碎片