- 我们开辟空间的时候,空间的大小是固定的。
- 在数组申明的函数,必须要指定数组的长度,它所需要的内存在编译时分配的。
- 但是如果想要开辟不固定大小的空间,该怎么办?
- 解决方式就是动态进行内存的分配,即在堆上开辟空间。
- 本篇博客将介绍如何动态开辟空间。
一、动态内存函数
- C语言中与动态内存管理相关的函数,主要有四个malloc、free、calloc和realloc。
1.malloc
- void* malloc(size_t size);
- 该函数是开辟一个大小为size字节大小的空间。
- 返回值:若开辟失败,则返回一个NULL指针,因此malloc的返回值一定要作检查。若开辟成功,则返回这块空间的指针,因为该指针为void*类型,所以具体使用时可以进行强转。
2.free
- void free(void* ptr);
- 该函数是用来释放ptr指针指向的一块空间的。
- 如果参数ptr指向的空间不是动态开辟的,则free函数的行为是未定义的。
- 若果参数ptr是NULL指针,则该函数什么都不做。
- 一般用了malloc申请空间,在使用之后一定要释放该空间,不然会造成资源泄露。
#include <stdio.h>
#include <stdlib.h>//malloc和free函数在该头文件里
int main()
{
int num = 0;
scanf("%d", &num);
int* ptr = (int*)malloc(num * sizeof(int));
if (NULL != ptr)
{
for (int i = 0; i < num; ++i)
{
*(ptr + i) = i;
printf("%d ", *(ptr + i));
}
printf("\n");
}
free(ptr);
ptr = NULL;//防止其成为野指针
system("pause");
return 0;
}
- 最后一定要ptr设为NULL,不然会变成野指针。
3.calloc
- void* calloc (size_t num, size_t size);
- 该函数功能是为num个大小为size的元素开辟一块空间,并且开辟完之后该空间的每个字节都会初始化为0。
- 其实该函数就是malloc加上初始化。
- 返回值:若开辟失败,则返回一个NULL指针。若开辟成功,则返回这块空间的指针,因为该指针为void*类型,所以具体使用时可以进行强转。
- 其开辟完空间,在这个空间使用完之后也一定要调用free函数将其释放掉。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>//calloc和free函数在该头文件里
int main()
{
int num = 0;
scanf("%d", &num);
//int* ptr = (int*)malloc(num * sizeof(int));
int* ptr = (int*)calloc(num, sizeof(int));
if (NULL != ptr)
{
printf("使用之前,会自动初始化:\n");
for (int i = 0; i < num; ++i)
{
printf("%d ", *(ptr + i));
}
printf("\n");
printf("使用之后:\n");
for (int i = 0; i < num; ++i)
{
*(ptr + i) = i;
printf("%d ", *(ptr + i));
}
printf("\n");
}
free(ptr);
ptr = NULL;
system("pause");
return 0;
}
- 不难发现,该函数作用和malloc一样,只不过比malloc多了一个初始化。
4.realloc
- void* realloc (void* ptr, size_t size);
- 该函数是当我们发现过去申请的空间太小了、或者太大了的时候,可以用该函数对动态开辟内存大小调整。
- ptr是要调整的空间的指针。
- size是调制之后的新大小。
- 返回值为调整之后的内存起始位置。
- 该函数在调整原内存空间大小的基础上,还会将原来内存中的数据移到新的空间中。
- realloc在调整内存空间时会出现两种情况:①原有空间之后本身就有足够大的空间,此时只需要在原有内存之后直接追加空间,原来空间的数据不发生变化。 ②原有空间之后没有足够大的空间,此时要在堆上重新找一个适合大小的连续空间进行使用,此情况下函数返回的是一个新的内存地址。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>//ralloc和free函数在该头文件里
int main()
{
int* ptr = (int*)malloc(5 * sizeof(int));
if (NULL != ptr)
{
for (int i = 0; i < 5; ++i)
{
*(ptr + i) = i;
printf("%d ", *(ptr + i));
}
printf("\n");
}
//用realloc扩容
int* ptr2 = NULL;
ptr2 = (int*)realloc(ptr, 6 * sizeof(int));
if (NULL != ptr2)
{
ptr = ptr2;
}
//之前的数据都在
*(ptr + 5) = 5;
for (int i = 0; i < 6; ++i)
{
printf("%d ", *(ptr + i));
}
printf("\n");
free(ptr);
ptr = NULL;
system("pause");
return 0;
}
二、常见的动态内存错误
- 对NULL指针的解引用
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr = (int*)malloc(2 * sizeof(int));
*ptr = 20;//不判断
free(ptr);
ptr = NULL;
system("pause");
return 0;
}
此时没有对ptr进行判断,则申请失败时ptr值为NULL,那么就会有问题。
- 对动态开辟空间越界访问
#include <stdio.h>
#include <stdlib.h>//malloc和free函数在该头文件里
int main()
{
int* ptr = (int*)malloc(5 * sizeof(int));
if (NULL != ptr)
{
for (int i = 0; i < 6; ++i)
{
*(ptr + i) = i;
printf("%d ", *(ptr + i));
}
printf("\n");
}
free(ptr);
ptr = NULL;
system("pause");
return 0;
}
上面 *(ptr + 5) 就是越界访问了。
- 对不是动态开辟的内存使用了free函数进行释放
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i = 20;
int* ptr = ⅈ
free(ptr);//
ptr = NULL;
system("pause");
return 0;
}
此时程序会崩。
- 对同一块动态内存多次释放
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr = (int*)malloc(5 * sizeof(int));
if (NULL != ptr)
{
for (int i = 0; i < 6; ++i)
{
*(ptr + i) = i;
printf("%d ", *(ptr + i));
}
printf("\n");
}
free(ptr);
free(ptr);
ptr = NULL;
system("pause");
return 0;
}
此时程序会崩。
- 使用free函数释放一块动态开辟内存的一部分
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>//malloc和free函数在该头文件里
int main()
{
int* ptr = (int*)malloc(5 * sizeof(int));
++ptr;//ptr不再指向该动态内存的起始位置了。
free(ptr);
ptr = NULL;
system("pause");
return 0;
}
程序依旧会崩。
- 当我们动态开辟内存空间时,使用完之后忘记使用free函数释放之后就会造成内存泄漏。
三、C语言内存开辟
-
C语言中程序的内存区域划分
-
栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
-
堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于链表。
-
数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。
-
代码段:存放函数体(类成员函数和全局函数)的二进制代码。