在C语言中我们在栈上开辟的空间是固定的,一旦确定好大小就不能随意改变,就想你创建了
int i = 10;
int arr[10] = {0};
int i 一旦确定下来就是四个字节,arr一旦确定好大小在重新运行时也是不能改变的。
为此C语言引入了动态内存空间开辟,这是给程序员自主开辟好空间,并且这个空间是在堆上开辟的。
动态内存函数
头文件是stdlib.h
malloc
malloc 可以向堆上申请 size 个字节的空间大小,如果开辟成功就会返回相应的指针,并且对于开辟的空间是不会进行初始化赋值,里面都是随机值!
如果开辟空间失败就会返回NULL!
如果开辟0个字节的空间,返回值取决于特定的库实现,可能为NULL也可能不是.
要注意了返回的指针类型是 void*,所以我们需要强制类型转换为我们需要的。
由于可能会返回NULL,一旦被解引用就是野指针的非法访问。
所以我们要对返回的指针做检查
下面我们来实践一下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(sizeof(int) * 5);
if (p == NULL)
{
perror("malloc");
return 1;
}
// 使用
return 0;
}
free
介绍完malloc 函数后,我们来介绍一下如何释放空间,由于堆的空间是有限的,如果一直开辟不去释放空间,那就会一直占用堆上的空间,导致运行时发生内存泄漏。所以我们需要学会释放空间!
free只能对malloc,calloc和realloc 这些动态内存开辟函数开辟的空间进行释放空间,如果释放的不是这些空间那这个行为就是未定义的行为(也就是错误的)
如果你传过去的指针为NULL的话,那这个函数不会做任何事情
free函数不会改变你传过去的指针的值,所以在释放空件后,可以将指针置为NULL,避免野指针的使用
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(sizeof(int) * 5);
if (p == NULL)
{
perror("malloc");
return 1;
}
// 使用
free(p);
p = NULL;
return 0;
}
calloc
这个函数也是申请 num 个 size 大小的字节空间,唯一和malloc不同的是,calloc会将空间全部初始化为0
realloc
这个函数就是扩大空间的,如果你觉得malloc或者calloc开辟的空间小了,就传入一个指针,输入你想要扩大的字节数,就可以了。
如果你传的是空指针,那realloc就会像malloc一样,为你开辟一块空间并返回新空间的地址。
如果开辟失败就会返回空指针
这里的开辟方式分两种:
第一种是就地扩容,返回原来的指针
第二种是异地扩容,将原先的空间的值全部拷贝到新空间,并释放就空间,最后返回新空间的地址
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)calloc(5, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
// 使用
int* ptr = (int*)realloc(p, 50);
if (ptr == NULL)
{
perror("realloc");
return 1;
}
p = ptr;
//使用
free(p);
p = NULL;
return 0;
}
练习
题目1
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;
free(p);
}
如果p的值是NULL,就是对空指针解引用,发生错误
题目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);
}
当i是10的时候就会越界访问
题目3
void test()
{
int a = 10;
int* p = &a;
free(p);
}
对非动态内存开辟的空间不能使用free
题目4
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);
}
由于p已经不是原先的起始位置,释放的空间不彻底,导致内存泄漏
题目5
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);
}
对一块动态开辟的内存多次释放是错误的,在第一次释放的时候,空间以及还给操作系统了,free是无权访问操作系统的空间,所以在使用free后,我们可以将指针置空,避免多次释放。
题目6
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);
}
这个就是忘记对开辟的内存进行释放,导致内存泄漏。
笔试题
题目1
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
首先str还是NULL,因为形参的改变不会影响实参,如果要改变str需要传入str的地址,对NULL解引用程序发生崩溃,并且对于开辟的内存空间要记得释放
题目2
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
临时变量p一旦出了GetMemory 函数就被销毁了,str非法访问内存空间。
题目3
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
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
free已经释放了str所指向的内存空间,但是free不会将str置空,后面的代码段内存空间进行了非法访问!
柔性数组
柔性数组就是数组的大小是未定义的,这种一般出现在结构体上,如果不知道结构体的大小的话,可以看我上上篇的文章《结构体详解》。
柔性数组的创建:
struct S
{
int i;
char arr[];
};
特点
柔性数组前面必须要有一个成员
使用sizeof去计算这个结构体的时候,是没有包含柔性数组的大小(因为他还没大小)
包含柔性数组成员的结构⽤malloc ()函数进行内存的动态分配,并且分配的内存应该⼤于结构的大小,以适应柔性数组的预期大小。
使用
当我们要使用结构体是,就需要对这个柔性数组开辟空间:
#include <stdio.h>
#include <stdlib.h>
struct S
{
int i;
char arr[];
};
int main()
{
struct S* s1;
s1 = (struct S*)malloc(sizeof(int) + 10 * sizeof(char));
return 0;
}
这样子我们就获得了10个字节的arr数组的空间了。
优点
我们在学习链表的时候会定义下面的结构体:
#include <stdio.h>
#include <stdlib.h>
struct S
{
int data;
struct S* next;
};
int main()
{
struct S* p;
p = (struct S*)malloc(sizeof(struct S));
if (p == NULL)
{
perror("malloc p");
return 1;
}
p->next = (struct S*)malloc(sizeof(struct S));
if (p->next == NULL)
{
perror("malloc next");
return 1;
}
//使用
free(p->next);
p->next = NULL;
free(p);
p = NULL;
return 0;
}
当我们使用这个结构体的时候需要对内存进行两次释放,如果使用柔性数组就可以一次释放。
柔性数组的使用能让内存空间更加连续,减少内存碎片化。