【C语言】(20)动态内存分配
动态内存分配是通过stdlib
标准库函数来管理的,主要包括malloc
、calloc
、realloc
和free
。这些函数允许在程序运行时分配和释放内存,使得内存的使用更加灵活。
1.动态内存分配函数
1.1 malloc
malloc
函数用于分配一定数量的内存。它的原型在stdlib.h
头文件中定义:
void* malloc(size_t size);
size
:需要分配的内存字节数。- 返回值:成功时返回指向分配内存的指针;如果分配失败,返回
NULL
。
示例:
#include <stdlib.h>
int main() {
int *p = malloc(10 * sizeof(int)); // 分配10个整数的空间
if (p == NULL) {
// 处理内存分配失败的情况
}
// 使用p...
free(p);
return 0;
}
1.2 calloc
calloc
函数用于分配并初始化内存。它的原型也在stdlib.h
头文件中定义:
void* calloc(size_t num, size_t size);
num
:需要分配的元素数量。size
:每个元素的大小。- 返回值:成功时返回指向分配并初始化为0的内存的指针;如果分配失败,返回
NULL
。
示例:
#include <stdlib.h>
int main() {
int *p = calloc(10, sizeof(int)); // 分配并初始化10个整数
if (p == NULL) {
// 处理内存分配失败的情况
}
// 使用p...
free(p);
return 0;
}
1.3 realloc
realloc
函数用于重新分配内存。它可以增加或减少已分配内存的大小。其原型定义在stdlib.h
头文件中:
void* realloc(void* ptr, size_t newSize);
ptr
:指向先前分配的内存的指针。newSize
:新的内存大小。- 返回值:成功时返回指向新分配内存的指针;如果分配失败,返回
NULL
,原指针ptr
仍然有效。
示例:
#include <stdlib.h>
int main() {
int *p = malloc(10 * sizeof(int)); // 最初分配10个整数的空间
p = realloc(p, 20 * sizeof(int)); // 现在重新分配为20个整数的空间
if (p == NULL) {
// 处理内存分配失败的情况
}
// 使用p...
free(p);
return 0;
}
1.4 free
free
函数用于释放先前通过malloc
、calloc
或realloc
分配的内存。它的原型定义在stdlib.h
头文件中:
void free(void* ptr);
ptr
:指向需要释放的内存的指针。
注意:一旦内存被释放,指针ptr
就不应再被访问。为了避免悬挂指针,建议将ptr
设置为NULL
。
示例:
#include <stdlib.h>
int main() {
int *p = malloc(10 * sizeof(int));
// 使用p...
free(p);
p = NULL; // 避免悬挂指针
return 0;
}
2.动态内存分配的常见错误
2.1 未检查返回值
使用malloc
或calloc
分配内存时,如果系统没有足够的内存可供分配,这些函数将返回NULL
。不检查这些函数的返回值直接使用返回的指针,可能会导致程序解引用空指针而崩溃。
错误示例:
int *ptr = malloc(sizeof(int) * 50); // 假设分配失败
*ptr = 5; // 如果ptr为NULL,这里会导致程序崩溃
正确做法:
int *ptr = malloc(sizeof(int) * 50);
if (ptr == NULL) {
// 处理内存分配失败的情况
}
2.2 忘记释放内存
每次调用malloc
、calloc
或realloc
分配的内存,在不再需要时应该使用free
函数释放。忘记释放内存会导致内存泄露,可能导致程序随着时间的推移而运行缓慢,并最终耗尽可用内存。
2.3 重复释放内存
对同一块内存调用free
两次是未定义行为,可能会导致程序崩溃。
错误示例:
int *ptr = malloc(sizeof(int));
free(ptr);
free(ptr); // 重复释放
2.4 使用已释放的内存
释放内存后继续使用该内存的指针,会导致未定义行为,通常称为悬挂指针(dangling pointer)问题。
错误示例:
int *ptr = malloc(sizeof(int));
free(ptr);
*ptr = 10; // 使用已释放的内存
2.5 越界访问
动态分配的内存块有固定的大小,超出其边界的访问是未定义的行为,可能会导致数据损坏或程序崩溃。
2.6 错误的内存大小计算
在使用malloc
等函数时,传递错误的大小给这些函数,可能会导致内存分配不足或过多,从而引起未定义行为或浪费资源。
2.7 忘记为realloc
返回的新指针赋值
realloc
函数用于调整已分配内存的大小。如果realloc
成功,它会返回指向新内存的指针,可能与原来的指针不同。如果不更新指针,可能会导致访问旧指针和内存泄露。
错误示例:
int *ptr = malloc(sizeof(int) * 2);
realloc(ptr, sizeof(int) * 4); // 忽略了realloc的返回值
正确做法:
int *ptr = malloc(sizeof(int) * 2);
int *new_ptr = realloc(ptr, sizeof(int) * 4);
if (new_ptr != NULL) {
ptr = new_ptr;
}
3.柔性数组
柔性数组成员(Flexible Array Member,FAM)提供了一种方便的方式来表示结构体末尾的可变长度数组。然而,柔性数组本身并不支持动态扩容,因为它们的大小在结构体实例被首次分配内存时就已经确定。要实现类似“动态扩容”的功能,你需要手动重新分配内存,并小心处理数据的复制和迁移。
基本思路
要“动态扩容”一个包含柔性数组的结构体,可以按照以下步骤操作:
- 确定新的大小:根据需要扩容的数量,计算新的总大小。
- 重新分配内存:使用
realloc
函数为原结构体实例分配更大的内存块。 - 检查分配结果:确保
realloc
成功,否则处理内存分配失败的情况。 - 更新结构体状态:如果扩容成功,更新结构体内部的状态,如元素计数。
注意
- 使用
realloc
进行内存重新分配时,如果新的内存分配成功,原内存块的内容(直到较小的旧尺寸或新尺寸)会被自动复制到新内存块中,原内存块随后会被释放。 - 如果
realloc
返回NULL
,原内存块不会被释放。因此,在处理realloc
失败时要小心,以避免内存泄漏。 - 在实际使用中,应考虑如何处理数据迁移和默认值初始化等问题,尤其是扩容后新添加的元素。
示例
假设我们有以下包含柔性数组的结构体定义:
struct flex_array_struct {
size_t count;
int data[]; // 柔性数组成员
};
接下来,我们将通过一个函数来扩展这个结构体实例的data
数组大小:
#include <stdio.h>
#include <stdlib.h>
struct flex_array_struct* resize_flex_array(struct flex_array_struct* array, size_t new_count) {
// 计算新的内存大小
size_t new_size = sizeof(struct flex_array_struct) + sizeof(int) * new_count;
// 尝试重新分配内存
struct flex_array_struct* new_array = realloc(array, new_size);
if (new_array == NULL) {
// 内存分配失败,根据需要处理错误
return NULL;
}
// 更新新数组的元素计数
new_array->count = new_count;
return new_array;
}
int main() {
// 初始化并分配一个具有5个元素的柔性数组
size_t initial_count = 5;
struct flex_array_struct* my_array = malloc(sizeof(struct flex_array_struct) + sizeof(int) * initial_count);
if (my_array == NULL) {
// 处理内存分配失败
return 1;
}
my_array->count = initial_count;
// 动态扩容数组到10个元素
size_t new_count = 10;
my_array = resize_flex_array(my_array, new_count);
if (my_array == NULL) {
// 处理内存分配失败
free(my_array);
return 1;
}
// 使用扩容后的数组...
// 释放内存
free(my_array);
return 0;
}