文章目录
动态内存管理
一、为什么要有动态内存分配
我们已经掌握的开辟内存方式:
int a = 20;
char c = ‘c’;
int a[20] ;等等等…
以上的这些开辟的内存都是已经固定好了的,数组在声明时,必须指定数组长度,编译之后便不能增减了,那么当我们遇到像只有在运行时才能知道我们需要多大的内存空间时的需求该怎么办呢?
答:使用动态内存函数,运行时动态申请空间,需要多少申请多少。
二、动态分配内存分配在内存的哪一块?
三、什么是动态内存函数
1.malloc和free
malloc
C语言提供了一个动态内存开辟的函数:
void* malloc (size_t size);
官方说明:分配大小为字节的内存块,返回指向块开头的指针。
新分配的内存块的内容未初始化,仍保留不确定的值。
如果size为零,则返回值取决于特定的库实现(它可能是也可能不是空指针),但返回的指针不应被取消引用。
- 这个函数向内存中堆空间申请一块连续可用的空间,并返回指向这块内存空间的指针。
- 1.如果开辟成功,则返回一个指向开辟好空间的指针
- 2.如果开辟失败,则返回一个NULL指针,因此要对malloc 的返回值做检查
- 3.返回值为void*类型,所以需要写代码的人自己对该指针进行强制转换成需要的指针类型
- 4.如果参数size为0,malloc会做什么是未知的,取决于编译器
例如:动态申请一块2个int型的堆内存空间
int *ptr = NULL;
int num = 2;
ptr = (int*)malloc( num * sizeof(int) );
if( NULL == ptr){
printf("申请失败!\n");
}
变量说明:
由于需要的是int型空间,所以指针必须是int类型,故ptr是int型并且malloc返回值必须强转为int*型
由于需要一块2个int型大小的空间,所以malloc需要申请空间大小为2 * sizeof(int)字节,即8字节
由以上代码便动态申请了一块2个int型大小的空间
free
C语言提供了一个函数free,用来对动态开辟的内存进行释放回收
void free (void* ptr);
官方说明:
作用:释放内存块
如果ptr没有指向分配给上述函数的内存块,则会导致未定义的行为。
如果ptr是空指针,则函数不执行任何操作。
请注意,此函数不会更改ptr本身的值,因此它仍然指向相同(现在无效)的位置。
free(ptr);
使用free()函数便将之前申请的动态内存释放了
注意:
在如下代码中,ptr存储的地址为0X00B59B98,该片地址中存储的是1111111十进制数字,在free之后,ptr存储的地址仍然是为0X00B59B98,但是该片地址中存储的是随机数字
2.calloc
void* calloc (size_t num, size_t size);
官方说明:分配一块num个大小为size字节的内存块,并将每一位初始化为0
size为个数
num为每个的字节数
总大小 = num * size
- calloc函数与malloc函数的区别在于:calloc会将动态分配的内存中的数据初始化为0,而malloc动态申请的内存中的数据是随机值
3.realloc
想一想:有时我们动态分配的内存大小,万一出现了偏大或偏小了,为了最大限度的利用内存,我们应该怎么办呢?
答案:C语言提供了一个函数,可以修改已经动态分配的内存大小
void* realloc (void* ptr, size_t size);
官方说明
作用:重新分配内存块,更改ptr指向的内存块的大小。
size变量说明:内存块的新大小,以字节为单位;size_t是无符号整数类型。
函数可以将内存块移动到新的位置(其地址由函数返回)。
内存块的内容将保留到新大小和旧大小中的较小值,即使块被移动到新位置。如果新大小更大,则新分配部分的值不确定。
如果ptr是空指针,那么函数的行为类似malloc,分配一个新的大小字节块,并返回一个指向其开头的指针。
- realloc函数让动态内存管理更加灵活
- ptr是要调整的内存地址
- size是调整之后的新大小
- 返回值为调整之后的内存起始位置
- 这个函数调整原内存空间大小,同时可能会将这片内存中的数据移动到新的地方。(原因是对原内存空间增大之后,增加的连续内存是被别的程序使用的内存,这样就无法增加,所以需要重新找到一块连续的且足够的内存空间)
realloc一般在内存中有两种情况:
情况一
情况二:
四、常见的动态内存错误
1.内存泄漏
void test()
{
int *p = (int *)malloc(100); //动态分配内存
if(NULL != p)
{
*p = 20;
}
//未释放内存,导致内存泄漏
}
int main()
{
test();
while(1);
}
2.同块内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p); //重复释放
}
3.内存未全部释放
void test(){
int *p = (int *)malloc(100);
p++; //p指针已经不是指向动态开辟的内存的起始位置了
free(p); //p指向空间内存只释放了一部分
}
4.释放非动态内存
int a = 10;
int *p = &a;
free(p); //由于p指向的空间并不是动态申请的,所以无需释放
5.越界访问动态内存
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
exit(EXIT_FAILURE);
for(i=0; i<=10; i++)
//一共访问了11次,动态申请的内存只可以访问10从
*(p+i) = i; //当i是10的时候越界访问
free(p);
}
6.解引用NULL指针
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
五、给结构体动态分配空间
由于结构体是一种自定义类型,它和int、char、double、float等C语言自带的类型是没区别的,我们来看一看结构体如何分配内存空间:
struct Array{
int num;
int* mem; //指向一个边长空间
};
int main(){
//由外而内申请内存
struct Array* pArray = (struct Array*)malloc(sizeof(struct Array));
if(pArray == NULL){
//检查结构体申请空间是否成功
return 1;
}
pArray->num = 10;
pArray->mem = (int*)malloc(pArray->num * sizeof(int));
if(pArray->mem == NULL){
//检查结构体成员空间是否申请成功
free(pArray); //失败返回前不要忘记释放结构体的空间
return 2;
}
//由内而外释放内存
free(pArray->mem);
free(pArray);
return 0;
}
六、柔性数组
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
写法1:
typedef struct A
{
int i;
int a[0];//柔性数组成员
};
写法2:
typedef struct B
{
int i;
int a[];//柔性数组成员
};
柔性数组的特点:
- 结构中的柔性数组成员前面至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
例如:
上述代码实现了一个变长数组,存放了0-9这10个数字,每一个数字存放在一个int型大小的内存中。
sizeof(struct A) + 10 * sizeof(int)中申请了一个结构体大小的内存空间外加10个int类型的内存空间,这10个int类型的内存空间就被保存在柔性数组中。
a[0]是结构体的终止地址,同时又是后面内存的起始地址。
struct A* pA = (struct A*)malloc(sizeof(struct A) + 10 * sizeof(int));
七、经典面试题
面试题一
void GetMemory(char *p){
p = (char*)malloc(100);
}
int main(){
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world!");
printf(str);
return 0;
}
上述代码有哪些错误?
1.p指向的动态内存未被释放
2.试图向空指针str中写入数据
3.试图输出空指针中的内容
如何修改代码能完美解决上述错误呢?
提示:使用二级指针,将str的地址传入!
修改后代码:
void GetMemory(char **p){
*p = (char**)malloc(100);
}
int main(){
char* str = NULL;
GetMemory(&str);
strcpy(str, "hello world!\n");
printf(str);
return 0;
}
输出: