动态内存管理
一、动态分配内存的必要性
普通内存分配
- 空间开辟大小是固定的。
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
动态内存分配
1、空间大小可以更改。
2、空间大小可以在程序执行时再分配。
二、动态内存分配函数
(一)malloc
/
void* malloc (size_t size);
使用规则:
传入字节数,申请成功返回申请空间的首地址,申请失败返回空地址,因此申请空间需要进行检查。
/
int main() {
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL) { // 进行检查会更安全
perror("malloc");
return 1;
}
printf("申请成功");
free(p); //后面会有函数用法
p = NULL;
return 0;
}
(二)calloc
/
void* calloc (size_t num, size_t size);
使用规则:
传入申请类型个数以及一个类型的字节数,申请成功返回申请空间的首地址,申请失败返回空地址,申请空间需要进行检查。
与 malloc 不同的是calloc 会初始化每个字节为0;
/
int main() {
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL) {
perror("calloc");
return 1;
}
printf("申请成功");
free(p);
p = NULL;
return 0;
}
(三)realloc
/
void* realloc (void* ptr, size_t size);
使用规则:
如果原来的内存块后面的空间足够开辟空间,那么直接在内存块后面扩展内存。否则 realloc 会另外寻找一片内存进行开辟,返回找到的地址,如果实在找不到这么大的内存,就会返回NULL。
/
int main() {
int* arr = (int*)malloc(sizeof(int) * 10);
if (arr == NULL) {
perror("malloc\n");
return 1;
}
int* p = (int*)realloc(arr, sizeof(int) * 20);
if (p == NULL) {
perror("realloc\n");
return 1;
}
arr = p;
free(arr);
arr = NULL;
return 0;
}
如果 ptr 传入NULL,他的效果就相当于 malloc。
/
int main() {
int* p = (int*)realloc(NULL, sizeof(int) * 20);
if (p == NULL) {
perror("realloc\n");
}
free(p);
p = NULL;
return 0;
}
(四)free
/
void free (void* ptr);
上面的函数 malloc calloc realloc 分配了空间之后,也需要进行释放。如果不释放,可能导致内存泄漏。如果不置空,可能导致非法访问。
如果传入NULL,就不进行操作。
三、常见的错误
(一)对空指针进行解引用操作
如果分配空间时,内存不足以开辟返回NULL;不进行检查就解引用可能导致程序崩溃。
(二)对动态分配空间越界访问
顾名思义,就是不可以越界访问。
(三)free释放动态分配空间的一部分
如下,p++ 改变了 p 指向的内存,不再是首地址,就好像是释放一部分空间,这种情况程序会崩溃。
/
int main() {
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL) {
perror("malloc");
return 1;
}
for (int i = 0; i < 5; i++) {
*(p++) = i;
}
free(p);
p = NULL;
return 0;
}
(四)动态开辟内存忘记释放
下面代码中,如果开辟成功,malloc 开辟的内存没有释放,但是 p 的地址我们在函数外面就再也找不到了,这就是内存泄漏。
/
void test()
{
int flag = 1;
int*p = (int*)malloc(100);
if (p == NULL)
{
return;
}
if (flag)
return;
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
四、柔性数组
(一)柔性数组的特点
1、结构中的柔性数组成员前面必须至少一个其他成员。
2、sizeof 返回的这种结构大小不包括柔性数组的内存。
3、包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
/
struct S {
int n;
int arr[];
};
int main() {
struct S* p = (struct S*)malloc(sizeof(struct S) + sizeof(int) * 10);
if (p == NULL) {
perror("malloc");
}
struct S* temp = (struct S*)realloc(p, sizeof(struct S) + sizeof(int) * 20);
if (temp == NULL) {
perror("realloc");
}
p = temp;
return 0;
}
(二)柔性数组的优势
相比于上面的代码,肯定也会有疑惑,为什么不在结果体中设置一个指针变量进行动态内存开辟空间。
那我们就进行探究
/
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S));
if (ps == NULL)
{
perror("malloc");
return 1;
}
int*tmp = (int*)malloc(20*sizeof(int));
if (tmp != NULL)
{
ps->arr = tmp;
}
else
{
return 1;
}
tmp = (int*)realloc(ps->arr, 40*sizeof(int));
if (tmp != NULL)
{
ps->arr = tmp;
}
else
{
perror("realloc");
return 1;
}
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
return 0;
}
我们发现柔性数组的优势:
1、提高访问速度:
连续的内存有益于提高访问速度,也有益于减少内存碎片
2、方便内存释放:
如果用动态内存开辟的方式,我们要释放两次才能释放掉内存空间,因此柔性数组更优秀。
五、有趣的题目
1、求下面代码的输出结果
/
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
这个代码有两个问题:
1、 传入函数中的为NULL,动态分配空间GetMemory 函数结束后就无法访问到了,造成内存泄漏。
2、strcpy 函数中,如果对空指针进行解引用操作,导致程序崩溃。
可查看strcpy详解;
2、求下面代码的输出结果
/
int main() {
char* p = (char*)malloc(sizeof(int) * 10);
strcpy(p, "hello");
free(p); //这里没有及时的将 p 置空造成了野指针
if (p) { //发生非法访问
strcpy(p, "what can i see!");
}
return 0;
根据代码中分析,这段代码在运行时也会导致程序崩溃。
六、结束语
小伙伴看了这篇文章感觉如何呢,如果能有收获是值得高兴的,如果觉得有帮助的化,别忘记点赞支持支持哦!