31、指针进阶3:指针与内存管理
大约 5 分钟C语言基础程序程序厨
在C语言中,指针是访问内存的重要工具,而内存管理则是确保程序稳定运行和高效利用资源的关键。
动态内存分配与释放
在C语言中,除了使用静态分配的数组和变量外(数据段),还可以通过动态内存分配函数(如malloc
、calloc
、realloc
和free
)在程序运行时根据需要分配和释放内存(堆区)。
动态内存分配函数
malloc(size_t size)
: 分配指定字节数的内存,并返回一个指向该内存的指针。如果分配失败,返回NULL
。calloc(size_t num, size_t size)
: 分配一个包含num
个元素的数组,每个元素的大小为size
字节,并自动将分配的内存初始化为零。如果分配失败,返回NULL
。realloc(void *ptr, size_t size)
: 调整之前调用malloc
或calloc
分配的内存块的大小。ptr
是指向要调整大小的内存块的指针,size
是新的大小。如果调整成功,返回指向新内存块的指针(可能与ptr
相同,也可能不同)。如果失败,返回NULL
,并且原内存块保持不变。free(void *ptr)
: 释放之前调用malloc
、calloc
或realloc
分配的内存。ptr
是指向要释放的内存块的指针。
为什么malloc的时候传递字节数,而free的时候却不需要传递字节数呢?会不会释放多了或者释放少了?
示例代码
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int *)malloc(5 * sizeof(int)); // 分配一个包含5个整数的数组
if (arr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
// 使用数组
for (int i = 0; i < 5; i++) {
arr[i] = i * 2;
}
// 打印数组内容
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 释放内存
free(arr);
return 0;
}
推荐阅读:内存管理
内存泄漏
内存****泄漏是指程序在动态分配内存后,没有正确地释放这些内存,导致这些内存无法被其他程序或后续操作使用。内存泄漏会导致系统性能下降,甚至可能导致系统崩溃。
推荐几个调试内存泄漏的方法:
- 使用工具:如
Asan
或者Valgrind
(在Linux上)等内存检查工具可以帮助识别内存泄漏和其他内存问题。 - 手动检查:在代码中添加适当的
free
调用,确保每个malloc
、calloc
或realloc
都有对应的free
。 - 编写代码时养成良好的习惯:在分配内存后立即检查返回值是否为
NULL
,并在使用完内存后立即释放,特别是if-else
分支的处理上,尤其需要注意要释放内存。(后续你使用C++时,可以使用更方便的内存管理编码技巧)
悬挂指针与野指针问题
悬挂指针是指指向已经被释放的内存的指针。悬挂指针通常发生在释放内存后仍然保留该内存地址的指针变量上。
危害:如果尝试通过悬挂指针访问内存,将导致未定义行为,可能导致程序崩溃或数据损坏。
野指针是指未初始化或未正确赋值的指针。野指针可能指向任意内存地址,包括无效的内存区域或受保护的内存区域。
识别与防范
- 初始化指针:在声明指针时立即将其初始化为
NULL
或指向有效的内存地址。 - 避免悬挂指针:在释放内存后将指针设置为
NULL
。 - 检查指针的有效性:在访问指针指向的内存之前,检查指针是否为
NULL
。
示例代码(避免悬挂指针)
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int *)malloc(sizeof(int));
if (ptr == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
}
*ptr = 42;
printf("Value: %d\n", *ptr);
// 释放内存并将指针设置为NULL
free(ptr);
ptr = NULL; // 如果不设置NULL,就会出现悬挂指针
// 避免使用悬挂指针
// if (ptr != NULL) { // 这行代码是多余的,因为ptr已经被设置为NULL
// printf("Avoiding dangling pointer\n");
// }
// 尝试访问悬挂指针将导致未定义行为(已注释掉)
// printf("Value after free: %d\n", *ptr); // 不要这样做!
return 0;
}
内存对齐与结构体布局
内存对齐是指编译器在分配结构体成员时,为了优化内存访问速度和数据一致性,将成员按一定的规则对齐到特定的内存地址上。
原因:某些硬件平台在访问未对齐的内存时会导致性能下降或引发异常。
结构体布局的优化与调整
我们可以通过以下策略对结构体布局进行优化和调整:
- 使用
#pragma pack
指令指定结构体以几字节对齐。 - 通过调整成员变量的顺序来优化结构体布局,以减少内存填充(空洞)(padding)。
- 使用
sizeof
运算符和offsetof
宏(在stddef.h
中)来检查结构体的大小和成员偏移量。
示例代码(检查结构体布局)
#include <stdio.h>
#include <stddef.h>
struct A {
char a;
int b;
short c;
};
struct B {
char a;
short c;
int b;
};
int main() {
printf("Size of struct A: %zu\n", sizeof(struct A));
printf("Offset of a in struct A: %zu\n", offsetof(struct A, a));
printf("Offset of b in struct A: %zu\n", offsetof(struct A, b));
printf("Offset of c in struct A: %zu\n", offsetof(struct A, c));
printf("Size of struct B: %zu\n", sizeof(struct B));
printf("Offset of a in struct B: %zu\n", offsetof(struct B, a));
printf("Offset of c in struct B: %zu\n", offsetof(struct B, c));
printf("Offset of b in struct B: %zu\n", offsetof(struct B, b));
return 0;
}
输出结果(https://godbolt.org/z/zc4TeEnab):
Size of struct A: 12
Offset of a in struct A: 0
Offset of b in struct A: 4
Offset of c in struct A: 8
Size of struct B: 8
Offset of a in struct B: 0
Offset of c in struct B: 2
Offset of b in struct B: 4
通过比较struct A
和struct B
的大小和成员偏移量,可以看到不同的成员顺序会导致不同的内存布局和填充。在实际编程中,应根据需要优化结构体布局以减少内存占用和提高性能(很重要)。
