我们为什么要进行动态内存分配呢?
我们一定遇到过这样一种情况,创建了一个一维数组,数组包含10个元素,用于存储10个整型。代码没有任何问题,但是一旦我们需要往数组中存入更多的整型,就非常麻烦,需要将数组的元素个数手动调整大一点,以便于放入新加入的数据。这样显得非常不智能。我们会想如何能够让计算机自动创建空间而不需要人为手动调整空间大小呢?这时候动态内存管理就显得非常重要了。
int ar[10] = {1,2,3,4,5,6,7,8,9,10};
//当有新的整型数据要放入ar数组,那就要手动调整数组的大小。
首先我们需要知道,例如int a这样的变量内存是系统自动开辟并且自动释放的,它们都被放到了栈区,而本文所说的动态开辟内存是需要手动开辟,并且手动释放的,它们一般都放到了堆区。
malloc
我们可以用malloc动态开辟内存空间,但是需要注意的是当内存空间不再使用的时候需要手动释放,否则会造成内存泄漏
int *par = NULL;
int num = 4;
par = (int*)malloc(sizeof(int) * num);//使用malloc开辟内存,进行强转,将指针的值赋给指针变量
if(par == NULL)//由于使用malloc开辟空间可能会失败,需要判断是否开辟成功
printf("内存开辟失败\n");
if(par != NULL)
{
for(int i = 0;i < num;i++)
*(par + i) = 0;
}
free(par);//使用动态开辟内存的方式需要用free手动释放
par = NULL;//释放完的指针,虽然依然指向那块内存,但是和那块内存空间没有任何关系,为了防止出现野指针,我们需要对指针置空
calloc
与malloc相比,calloc的特点在于我们开辟的内存空间会被初始化为0;而在用法上有稍许不同。所以如果我们对申请的内存空间内容要求初始化,可以使用calloc
void malloc(size_t size);
void calloc(size_t num, size_t size);//是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0
realloc
当我们使用前两个函数的时候,依然觉得开辟内存很方便,那么我们就可以使用realloc,我们可以对开辟的内存空间大小进行调整。
void* realloc (void* ptr, size_t size);
//ptr 是要调整的内存地址
//size 调整之后新大小
//返回值为调整之后的内存起始位置。
//这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
使用realloc进行内存大小的调整时,可能会存在原内存后面的空间足够我们扩充,也有可能不足够扩充,当不足的时候,realloc会将原地址的内存拷贝到新的更大的空间。
所以我们进行开辟时,需要先判断是否拷贝成功,如果成功再指向新的指针,以防开辟失败而丢失原来的内存数据
int main()
{
int *ptr = malloc(100);
if(ptr != NULL)
{
//业务处理
}
else
{
exit(EXIT_FAILURE);
}
//扩展容量
//代码1
ptr = realloc(ptr, 1000);//如果这样申请扩容,失败会将原来的内存空间也丢失
//代码2
int*p = NULL;
p = realloc(ptr, 1000);
if(p != NULL)//对是否成功开辟进行判断
{
ptr = p;
}
//业务处理
free(ptr);
return 0;
}
//当然也可以这样
int main()
{
int *ptr = malloc(100);
if(ptr != NULL)
{
//业务处理
}
else
{
exit(EXIT_FAILURE);
}
new_ptr = (int*)realloc(ptr, 1000);//这里直接将扩容返回的指针赋给一个新的指针,就省去了上面繁杂的代码,即使开辟失败,原来的指针依旧存在
free(new_ptr);
return 0;
}
柔性数组
柔性数组最大的优势在于方便内存释放。
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
//代码1,使用柔性数组
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);//由于使用了柔性数组,那么对于函数使用者来说,只需要进行一次内存释放
//代码2,未使用柔性数组
typedef struct st_type
{
int i;
int *p_a;
}type_a;
type_a *p = 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;
常见的动态内存错误
1.对NULL指针进行解引用
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
2.对非动态开辟的内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//p并非动态开辟,不可使用free释放
}
3.传入的参数不是指针
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);//传入参数不是指针,而是这个指针的值NULL,也就无法正常开辟内存空间
strcpy(str, "hello world");
printf(str);
}