为什么要用动态内存管理
在我们平时写C语言程序时,一般在设置变量空间大小时是设置死了的比如在设置数组的时候必须要有一个长度,每一个变量要有一个具体的值像这样
int a = 4;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
这就是我们平常写的代码,这些内存空间的申请都是在栈上申请的
在栈上申请的空间大小是固定的,有以下两个特点
1.空间开辟⼤⼩是固定的。
2.组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知
道,那数组的编译时开辟空间的⽅式就不能满⾜了。所以在这种情况下C语言引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。
malloc函数
malloc函数就是C语言设计的一款可以动态开辟内存空间的函数他的类型是这样的他包含在stdlib.h头文件里
void* malloc (size_t size);
这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。
1 如果开辟成功,则返回⼀个指向开辟好空间的指针。
2 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
3 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃
⼰来决定。
4如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。
我们现在来看看怎么使用。
# define _CRT_SECURE_NO_WARNINGS
# include<stdio.h>
# include<stdlib.h>
int main()
{
int* p = (int*)malloc(5*sizeof(int));
if (p == NULL)
{
perror("p");
return 1;
}
for (int i = 0; i < 5; i++)
{
*(p + i) = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", * (p + i));
}
return 0;
}
这个代码我们向内存堆区申请了20个字节(5*sizeof(int))的空间来存放我们的整形数据if来判断动态内存是否开辟成功,下面两个循环分别是输入数据和输出数据,下面是结果
我们成功实现了在我们开辟的内存中输入并输出元素。
在我们申请完内存之后使用完内存之后,我们肯定是要释放内存空间的,并让他还给操作系统,那么这时候就要用到另一个函数free内存释放函数
free函数
C语⾔提供了另外⼀个函数free,专⻔是⽤来做动态内存的释放和回收的,函数原型如下:
void free (void* ptr);
free函数⽤来释放动态开辟的内存。
1 如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
2 如果参数 ptr 是NULL指针,则函数什么事都不做。
malloc和free都声明在 stdlib.h 头⽂件中。
我们接着上面的例子来讲一下。
int main()
{
int* p = (int*)malloc(5*sizeof(int));
if (p == NULL)
{
perror("p");
return 1;
}
for (int i = 0; i < 5; i++)
{
*(p + i) = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", * (p + i));
}
free(p);//释放内存
p = NULL;//把指针设置成空指针
return 0;
}
这里为什么要设置成空指针,因为我们用malloc开辟的内存空间,malloc返回了一个开辟空间的首地址赋值给了p这个指针变量,当p指向的这个内存空间被释放之后,p就变成野指针了,所以需要赋一个空值。
calloc和realloc函数
1.calloc
C语⾔还提供了⼀个函数叫 calloc , calloc 函数也⽤来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
1.函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
2.与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
举个列子
我们可以发现在calloc函数分配内存的时候把分配的内存空间全部设置成了0,而malloc不会。
所以如果我们对申请的内存空间的内容要求初始化,那么可以很⽅便的使⽤calloc函数来完成任务。
realloc函数
realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时
候内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤
⼩的调整。
函数原型如下:
void* realloc (void* ptr, size_t size);
1.ptr 是要调整的内存地址
2.size 调整之后新⼤⼩
3 返回值为调整之后的内存起始位置。
4这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到 新 的空间。
5 realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有⾜够⼤的空间
◦情况2:原有空间之后没有⾜够⼤的空间
我们一般的想法是在malloc开辟完内存空间之后在在malloc开辟的内存空间后面用realloc新添加内存空间就像这样
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
perror("p");
return 1;
}
p = (int*)realloc(p,40);
return 0;
}
这样是不行的,编译器会报一个警告
这是为什么呢,我们来画图看看。
通过这上面的讲解,由于上述的两种情况,realloc函数的使⽤就要注意⼀些我们应当在使用realloc开辟新空间时赋一个新地址就像这样
# define _CRT_SECURE_NO_WARNINGS
# include<stdio.h>
# include<stdlib.h>
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
perror("p");
return 1;
}
int* ptr = (int*)realloc(p,40);
if (ptr == NULL)
{
perror("ptr");
return 1;
}
//....S
return 0;
}
常见的动态内存错误
我们来看看一些典型错误
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
}
int main()
{
Test();
return 0;
}
我们运行程序,会发现程序崩溃了,这是怎么回事呢我们来看看
首先一个很明显的问题就是malloc申请的内存空间没有被释放,会出现内存泄漏。下面是具体解释
还有一道题;
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
我们来看看这又是怎么回事
还有一道题;
# include<stdio.h>
# include<stdlib.h>
# include<string.h>
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
int main()
{
Test();
return 0;
}
最后一道题
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
柔性数组
什么是柔性数组,通过名字我我们可以发现柔性数组就是可以随便变长变短的数组,可以根据自己的使用空间来调整的数组
柔性数组的特点:
1 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
2sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。
3包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤
⼩,以适应柔性数组的预期⼤⼩。
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
int main()
{
printf("%d\n", sizeof(type_a));//输出的是4
return 0;
}
为什么不计算最后数组的大小呢,我们可以发现最后一个数组定义为柔性数组这个是可以变大变小的数组空间大小是不定的,所以不计算,而且要把他放到最后面,我们可以来画个图看看。
柔性数组的使用
**# define _CRT_SECURE_NO_WARNINGS
# include<stdio.h>
# include<stdlib.h>
struct stu
{
int age;
int num[];
};
int main()
{
struct stu* ps = (struct stu *)malloc(sizeof(struct stu)+10*sizeof(int));
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->age = 18;
printf("%d ",ps->age);
printf("\n");
for (int i = 0; i < 5; i++)
{
ps->num[i] = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", ps->num[i]);
}
struct stu* ps2 = (struct stu*)realloc(ps,sizeof(struct stu) + 20 * sizeof(int));
if (ps == NULL)
{
perror("realloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
ps2->num[i] = i;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", ps2->num[i]);
}
return 0;
}**
这里我们用了柔性数组num并用malloc和realloc分配和添加内存空间,并完成了我们想要的效果。