🦄个人主页:小米里的大麦-CSDN博客
🎏所属专栏:https://blog.csdn.net/huangcancan666/category_12718530.html
🎁代码托管:C语言: C语言方向(基础知识和应用) (gitee.com)
⚙️操作环境:Visual Studio 2022
目录
[1. 简介](#1. 简介)
[2. 语法](#2. 语法)
[3. 使用方法](#3. 使用方法)
[4. 关于内存的使用(重点)](#4. 关于内存的使用(重点))
[示例一: malloc(sizeof(int))](#示例一: malloc(sizeof(int)))
[示例二: malloc(20)](#示例二: malloc(20))
[5. 结论](#5. 结论)
[1. 简介](#1. 简介)
[2. 语法](#2. 语法)
[3. 使用方法](#3. 使用方法)
[4. 注意事项](#4. 注意事项)
[1. 只释放一次](#1. 只释放一次)
[2. 检查指针是否为 NULL](#2. 检查指针是否为 NULL)
[3. 避免悬挂指针](#3. 避免悬挂指针)
[4. 动态数组的处理](#4. 动态数组的处理)
[5. 释放结构体中的指针](#5. 释放结构体中的指针)
[1. 简介](#1. 简介)
[2. 语法](#2. 语法)
[3. 使用方法](#3. 使用方法)
[4. 注意事项](#4. 注意事项)
[1. 简介](#1. 简介)
[2. 语法](#2. 语法)
[3. 使用方法](#3. 使用方法)
[4. 注意事项](#4. 注意事项)
[5. 缩容的语法和基本用法](#5. 缩容的语法和基本用法)
一、引言
动态内存管理是C语言中一个重要的部分,它允许程序在运行时动态地分配、使用和释放内存资源。这篇博客将详细讲解C语言中的四个核心函数:malloc、free、calloc和realloc,并讨论它们的使用方法和注意事项。
二、malloc
1. 简介
malloc
(memory allocation)用于动态分配指定大小的内存块。它从堆中分配内存,并返回指向该内存块的指针。分配的内存内容未初始化。(malloc - C++ Reference)
2. 语法
cpp
#include <stdlib.h>//头文件
void* malloc(size_t size);//函数原型
size_t
是一个无符号整数类型,用来表示对象的大小。size
参数指定要分配的内存字节数。- 如果成功分配内存,则
malloc
返回指向这块内存的指针;如果失败(例如没有足够的可用内存),则返回NULL
。
3. 使用方法
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)malloc(sizeof(int) * 10); // 分配能存储10个int类型元素的内存
if (p == NULL) {
printf("内存分配失败\n");
return 1;
}
// 使用分配的内存
for (int i = 0; i < 10; i++) {
p[i] = i;
printf("%d ", p[i]);
}
// 释放内存
free(p);
return 0;
}
cpp
分配内存:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p;
p = (int *)malloc(sizeof(int)); // 分配一个 int 类型大小的内存
if (p == NULL) {
printf("内存分配失败!\n");
return 1;
}
*p = 10; // 给分配的内存赋值
printf("值: %d\n", *p); // 输出值
free(p); // 释放内存
return 0;
}
cpp
分配多个元素:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p;
size_t num_elements = 5;
p = (int *)malloc(num_elements * sizeof(int));
if (p == NULL) {
printf("内存分配失败!\n");
return 1;
}
for (int i = 0; i < num_elements; i++) {
p[i] = i + 1;
}
for (int i = 0; i < num_elements; i++) {
printf("%d ", p[i]);
}
printf("\n");
free(p);
return 0;
}
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)malloc(sizeof(int)); // 分配4字节内存
char *q = (char*)malloc(20); // 分配20字节内存
if (p == NULL || q == NULL) {
printf("内存分配失败\n");
return 1;
}
*p = 100; // 存储一个int类型的值
for (int i = 0; i < 20; i++) {
q[i] = 'A'; // 使用分配的20字节空间
}
// 当你尝试访问 q[20](第21字节)时,程序行为未定义,可能会崩溃
// q[20] = 'B'; // **不要这样做**,因为它会导致未定义行为
printf("p: %d\n", *p);
printf("q: %.20s\n", q);
free(p); // 释放分配的内存
free(q);
return 0;
}
4. 关于内存的使用(重点)
- 内存对齐:
实际上,
malloc
不仅分配请求的字节数,还会考虑内存对齐的要求。这意味着它可能分配比你请求的更多的内存,以便确保内存地址能够适当地对齐。例如,如果系统要求int
对齐到4字节边界,那么malloc
在分配内存时,会确保返回的地址是4的倍数。如果当前的内存块不满足对齐要求,malloc
可能会跳过一些字节(称为填充字节),以找到一个符合对齐要求的位置。
- 内存碎片:
内存管理库会试图高效地管理内存,避免碎片化。意味着它可能会保留一些额外的内存,以备将来分配使用,但这不是用户直接控制的。
工作原理:
示例一: malloc(sizeof(int))
sizeof(int)
通常为4(这取决于具体的系统和编译器),意味着malloc(sizeof(int))
分配了4字节的内存。malloc(sizeof(int))
的返回值是一个指向这4字节内存块的指针。程序可以通过这个指针来存储和访问一个int
类型的变量。- 总结 :
malloc(sizeof(int))
一次性分配了4字节的内存供程序使用。示例二: malloc(20)
- 当你调用
malloc(20)
时,内存管理器(通常由操作系统或运行时库管理)会在堆中找到一块足够大的、至少有20字节的空闲内存块,一次性分配给程序使用(将其分配给你)- 分配的20字节内存块起始地址由
malloc
返回,返回的指针指向这段20字节的内存块,你可以在这块内存上存储数据,但只能使用这20字节的空间。- 一旦这块内存分配给你,你就完全拥有了这20字节的空间,程序可以自由地在这个范围内操作。
- 内存管理 :如果你需要更多的空间,你需要手动调用
realloc
来调整这块内存的大小,或者分配一块新的、更大的内存块。重要点:
- 一次性分配 :
malloc(20)
是一次性 从堆中分配20字节的内存。这20字节的内存是连续的,并且可以立即使用。- 使用限制 :这20字节的内存并不会像"用一点给一点"那样逐渐分配,而是一次性分配。如果在使用过程中超过了这20字节的边界(例如试图访问第21字节),则可能会引发未定义行为,通常会导致程序崩溃或内存损坏。
- 不会自动扩展 :
malloc(20)
分配的内存是固定的20字节。如果这20字节的空间用完了,程序必须手动使用realloc
来扩展内存块,否则无法存储更多的数据。malloc
本身不会自动再给20字节空间。
5. 结论
malloc
是一次性分配 :不管你请求多少字节,malloc
都会一次性分配指定数量的内存给你,供你自由使用。- 内存不会自动扩展 :一旦分配完内存,除非你使用
realloc
,否则内存块大小固定,不会自动增长。- 边界检查:在使用分配的内存时,必须确保不超出已分配的范围,否则会导致程序崩溃或数据损坏。
- 内存对齐:
malloc
不仅分配你请求的内存量,还会考虑对齐要求,因此可能会分配比请求的更多的内存。其返回的内存地址会满足特定的对齐要求,确保高效且安全的内存访问。尽管你可能请求了例如malloc(20)
,实际分配的内存可能会大于20字节,以确保内存对齐,但对你而言,能安全使用的仍然是请求的20字节空间。
三、free
1. 简介
free
用于释放由malloc
、calloc
或realloc
分配的内存,使这部分内存重新可用。(手动释放内存)(free - C++ Reference)
2. 语法
cpp
#include <stdlib.h>//头文件
void free(void *ptr);//函数原型
ptr: 是一个指向要释放的内存块的指针。如果 ptr 是 NULL,free 不做任何操作。
3. 使用方法
free
的使用非常简单,以下是一个例子:
cpp
int *p = (int*)malloc(sizeof(int) * 10);
if (p != NULL) {
// 使用内存
free(p); // 释放内存
}
4. 注意事项
1. 只释放一次
- 不要对同一块内存多次调用
free
。这会导致未定义行为,比如程序崩溃。- 不要对未分配的内存调用
free
。确保你只释放通过malloc
,calloc
, 或realloc
分配的内存。2. 检查指针是否为
NULL
cpp在调用 free 之前检查指针是否为 NULL: if (p != NULL) { free(p); p = NULL; // 避免悬挂指针 }
3. 避免悬挂指针
cpp当释放内存后,通常将指针设置为 NULL 来避免悬挂指针问题。 这样可以防止意外地再次尝试释放相同的内存。 free(p); p = NULL;
4. 动态数组的处理
cpp如果使用 malloc 或 calloc 分配了数组,释放内存时也需要正确处理: int *arr = (int *)malloc(10 * sizeof(int)); ... free(arr); arr = NULL;
5. 释放结构体中的指针
cpp如果结构体中包含指向动态分配内存的指针,在销毁结构体前需要释放这些内存: struct Node { int data; struct Node *next; }; void freeNode(struct Node *node) { if (node != NULL) { freeNode(node->next); // 递归释放链表中的每个节点 free(node); } }
正确使用
free
函数是良好编程实践的一部分,它可以避免内存泄漏,并确保程序的稳定性和性能。
四、calloc
1. 简介
calloc
(contiguous allocation)用于分配内存并初始化所有位为零。与malloc
不同,它接受两个参数:分配的元素个数和每个元素的大小。(calloc - C++ Reference)
2. 语法
cpp
#include <stdlib.h>//头文件
void* calloc(size_t num, size_t size);//函数原型
参数说明
- num:需要分配的元素数量。
- size:每个元素的大小(以字节为单位)。
返回值
- 如果内存分配成功,则返回指向新分配内存的指针。
- 如果内存分配失败,则返回
NULL
。
3. 使用方法
calloc()
函数为所请求的元素数量分配一个连续的内存块,并且初始化所有元素为零值。意味着对于基本数据类型如int
, float
, 或者char
等,所有元素都会被设置为0。
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)calloc(10, sizeof(int)); // 分配能存储10个int类型元素的内存,并初始化为0
if (p == NULL) {
printf("内存分配失败\n");
return 1;
}
// 使用分配的内存
for (int i = 0; i < 10; i++) {
printf("%d ", p[i]);
}
// 释放内存
free(p);
return 0;
}
4. 注意事项
- 与
malloc
相似,calloc
返回的指针也应检查是否为NULL
。 - 使用完动态分配的内存后,必须调用
free
释放内存。
与 malloc 的区别
malloc()
只分配内存但不初始化。calloc()
不仅分配内存,还会初始化所有元素为零值。calloc()
接受两个参数:元素个数和单个元素的大小;而malloc()
仅接受一个参数:总大小。
cpp如果你需要分配一段内存并且希望该内存被自动初始化为零,那么calloc()是一个更好的选择。 如果你只需要分配内存而不关心初始化,或者需要更灵活地控制内存大小,那么malloc()可能更适合。 这里有一个简单的例子来展示calloc()和malloc()的区别: #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int *array_calloc, *array_malloc;//array:数组 int n = 5; // 使用 calloc array_calloc = (int *) calloc(n, sizeof(int)); if (array_calloc == NULL) { printf("calloc failed\n"); return 1; } for (int i = 0; i < n; i++) { printf("calloc[%d]: %d\n", i, array_calloc[i]); } free(array_calloc); // 使用 malloc array_malloc = (int *) malloc(n * sizeof(int)); if (array_malloc == NULL) { printf("malloc failed\n"); return 1; } memset(array_malloc, 0, n * sizeof(int)); // 手动初始化为零 for (int i = 0; i < n; i++) { printf("malloc[%d]: %d\n", i, array_malloc[i]); } free(array_malloc); return 0; }
五、realloc
1. 简介
realloc
(reallocation)在 C 语言中用于改变已分配内存块的大小。这个函数允许您动态地增加或减少内存空间的大小,这对于需要根据运行时条件调整数据结构大小的应用程序非常有用。它可以扩展或缩小内存块,如果新大小大于旧大小,未初始化的新内存内容是不确定的。(realloc - C++ Reference)
2. 语法
cpp
#include <stdlib.h>//头文件
void* realloc(void *ptr, size_t size);//函数原型
ptr
: 指向要重新分配的内存块的指针**(先前通过malloc
、calloc
或realloc
分配的内存块的指针)** 。如果ptr
是NULL
,那么realloc()
将执行与malloc(size)
相同的操作。size
: 新的内存大小**(以字节为单位)** 。如果size
为0
并且ptr
不是NULL
,那么realloc()
将释放ptr
指向的内存块,并返回一个NULL
值。- 返回值:
realloc()
返回一个指向新内存块的指针。如果内存重新分配成功,则返回的新指针可能与原来的指针不同。如果失败,它将返回NULL
。如果size
为0,则相当于调用free(ptr)
,并返回NULL
。
3. 使用方法
以下是一个简单的例子:
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)malloc(sizeof(int) * 5); // 初始分配5个int类型元素的内存
if (p == NULL) {
printf("内存分配失败\n");
return 1;
}
// 扩展内存到10个int类型元素
p = (int*)realloc(p, sizeof(int) * 10);
if (p == NULL) {
printf("内存重新分配失败\n");
return 1;
}
// 使用扩展的内存
for (int i = 0; i < 10; i++) {
p[i] = i;
printf("%d ", p[i]);
}
// 释放内存
free(p);
return 0;
}
4. 注意事项
realloc
的返回值应检查是否为NULL
,因为重新分配可能失败。- 如果
realloc
失败,原来的内存块仍然有效,应该避免内存泄漏。 - 如果新大小为0,
realloc
等同于调用free
。
5. 缩容的语法和基本用法
语法不变,同 realloc 的函数原型。
缩容注意事项
1. 数据保留:
- 如果新的大小大于或等于原始大小,那么原始数据会被保留。
- 如果新的大小小于原始大小,那么原始数据中超出新大小范围的部分将被丢弃。
- 因此,在缩小内存之前,最好先备份重要数据,以防丢失。
2. 检查返回值:
- 总是要检查
realloc()
的返回值是否为NULL
,这表示内存分配失败。- 如果返回值非
NULL
,则需要将指针更新为新的地址。3. 类型转换:
- 类型转换通常需要应用到
realloc()
的返回值上,以保持类型安全。4. 性能考虑:
- 频繁缩容可能导致内存碎片化和性能降低。
- 如果知道内存大小变化频繁,考虑使用其他数据结构或者技术,如自定义内存池。
cpp
以下是一个示例代码,展示了如何使用 realloc() 缩小内存大小:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *array;
int n = 10;
int new_n;
// 分配初始内存
array = (int *) malloc(n * sizeof(int));
if (array == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
// 初始化数组
for (int i = 0; i < n; i++) {
array[i] = i + 1;
}
// 打印初始数组
printf("Initial array:\n");
for (int i = 0; i < n; i++) {
printf("%d ", array[i]);
}
printf("\n");
// 缩小内存
new_n = 5;
array = (int *) realloc(array, new_n * sizeof(int));
if (array == NULL) {
printf("Memory reallocation failed!\n");
free(array);
return 1;
}
// 打印缩容后的数组
printf("Reduced array:\n");
for (int i = 0; i < new_n; i++) {
printf("%d ", array[i]);
}
printf("\n");
// 释放内存
free(array);
return 0;
}
在这个例子中,原始数组大小为 10,之后通过 realloc() 将其大小减小到了 5。
注意,缩容后只打印了前五个元素,这是因为原始数据中的后五个元素已经不再存在于新的内存块中。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *p = (char*)malloc(20 * sizeof(char)); // 分配20字节内存
if (p == NULL) {
printf("内存分配失败\n");
return 1;
}
strcpy(p, "Hello, World!"); // 向内存块中写入字符串
// 缩小内存块到10字节
p = (char*)realloc(p, 10 * sizeof(char));
if (p == NULL) {
printf("内存重新分配失败\n");
return 1;
}
printf("缩容后的内容: %s\n", p); // 输出可能出现意外的内容
free(p); // 释放内存
return 0;
}
代码解析:
初始时,我们分配了20字节的内存并存储了字符串"Hello, World!"。
然后,我们使用realloc将内存块缩小到10字节。此时,只能保证前10字节的数据被保留。
由于字符串"Hello, World!"超过了10字节,后面的部分可能被裁剪或者造成字符串结尾缺失。
打印时,由于没有足够的空间保存完整的字符串以及字符串的终止符\0,因此可能会出现意外的输出(未定义行为)。
realloc
缩容的工作原理当你使用
realloc
缩减内存块的大小时(即size
比原来分配的大小小),其行为如下:
数据保留 :
realloc
会保留内存块起始位置的一部分内容,保留的部分大小为size(new_size)
。换句话说,缩容后的内存块中,前size
字节的数据会保留并保持不变。释放多余内存 :对于超出
size
范围的多余内存,realloc
会将其释放。具体的释放方式取决于内存管理器的实现,但对程序员来说,这部分内存块将不再可用,不能访问和使用。指针返回 :如果缩容后的内存块可以在原始地址上处理,那么
realloc
会直接返回原指针。如果内存管理器为了效率或其他原因在缩容过程中移动了内存块,realloc
将返回一个新的内存地址,并且会将原来ptr
指向的内容复制到新地址。
缩容的实际应用场景
缩容操作常用于以下场景:
- 内存优化 :在动态数据结构(如可变大小数组、缓冲区)中,随着数据量减少,使用
realloc
来释放多余的内存空间。- 节省资源:在程序运行中,及时缩减不再需要的内存块大小,可以节省系统资源,特别是在嵌入式系统或内存受限的环境中。
总结
通过合理使用
realloc
缩容,可以有效管理程序的内存资源,确保程序在执行过程中占用最小的内存空间,同时保持灵活性和高效性。
- 在缩小内存时,一定要确保新大小能够容纳必要的数据。
- 使用
realloc()
缩容时,必须检查返回值是否为NULL
。- 对于经常需要改变大小的情况,考虑使用更高效的数据结构或内存管理策略。
六、总结
动态内存管理是C语言编程中不可或缺的一部分,正确使用malloc
、free
、calloc
和realloc
可以提高程序的灵活性和效率。以下是一些关键点:
- 始终检查内存分配函数的返回值是否为
NULL
。 - 使用完动态内存后,一定要释放内存,防止内存泄漏。
- 对于复杂的数据结构,确保正确释放所有分配的内存,以避免内存泄漏和悬空指针。
掌握这些基本函数和相关注意事项,可以帮助你编写出更健壮和高效的C程序。希望这篇博客能帮助你更好地理解和使用C语言中的动态内存管理!