Appearance
内存管理
C语言提供了手动管理内存的能力,这是C语言强大但也容易出错的地方。本章将详细介绍C语言的动态内存分配和管理。
内存区域
c
/*
* C程序的内存布局
*/
#include <stdio.h>
#include <stdlib.h>
/* 全局变量 - 存储在数据段 */
int global_init = 100; /* 已初始化数据段 */
int global_uninit; /* 未初始化数据段(BSS) */
/* 静态变量 */
static int static_var = 50;
/* 常量 - 存储在代码段/只读数据段 */
const int const_var = 10;
void memory_layout_demo(void)
{
/* 局部变量 - 存储在栈上 */
int local_var = 20;
/* 动态分配 - 存储在堆上 */
int *heap_var = (int*)malloc(sizeof(int));
*heap_var = 30;
printf("=== 内存区域地址 ===\n");
printf("代码段(函数地址): %p\n", (void*)memory_layout_demo);
printf("只读数据(常量): %p\n", (void*)&const_var);
printf("已初始化数据段: %p\n", (void*)&global_init);
printf("未初始化数据段: %p\n", (void*)&global_uninit);
printf("栈(局部变量): %p\n", (void*)&local_var);
printf("堆(动态分配): %p\n", (void*)heap_var);
/*
* 内存布局(从低地址到高地址):
*
* +------------------+ 低地址
* | 代码段 | 程序代码
* +------------------+
* | 只读数据段 | 字符串常量、const变量
* +------------------+
* | 数据段 | 已初始化的全局/静态变量
* +------------------+
* | BSS段 | 未初始化的全局/静态变量
* +------------------+
* | 堆 ↑ | 动态分配,向上增长
* | ... |
* +------------------+
* | 栈 ↓ | 局部变量,向下增长
* +------------------+ 高地址
* | 命令行参数 |
* +------------------+
*/
free(heap_var);
}
int main(void)
{
memory_layout_demo();
return 0;
}malloc和free
c
/*
* malloc 和 free 基本使用
*/
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
/* malloc - 分配内存 */
/* void* malloc(size_t size) */
/* 分配单个整数 */
int *p1 = (int*)malloc(sizeof(int));
if (p1 == NULL) {
printf("内存分配失败\n");
return 1;
}
*p1 = 100;
printf("p1指向的值: %d\n", *p1);
free(p1); /* 释放内存 */
p1 = NULL; /* 避免野指针 */
/* 分配数组 */
int n = 5;
int *arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
printf("内存分配失败\n");
return 1;
}
/* 初始化数组 */
for (int i = 0; i < n; i++) {
arr[i] = i * 10;
}
printf("\n动态数组: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
arr = NULL;
/* 常见错误示例 */
printf("\n=== 常见错误 ===\n");
/* 错误1:忘记free(内存泄漏) */
int *leak = (int*)malloc(sizeof(int));
*leak = 10;
/* 忘记free(leak); */
/* 错误2:重复free */
int *double_free = (int*)malloc(sizeof(int));
free(double_free);
/* free(double_free); */ /* 错误!重复释放 */
/* 错误3:free后继续使用 */
int *use_after_free = (int*)malloc(sizeof(int));
free(use_after_free);
/* *use_after_free = 10; */ /* 错误!使用已释放的内存 */
/* 错误4:free非动态分配的内存 */
int stack_var = 10;
/* free(&stack_var); */ /* 错误!不能释放栈上的内存 */
printf("错误示例已注释,避免程序崩溃\n");
return 0;
}calloc和realloc
c
/*
* calloc 和 realloc
*/
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
/* calloc - 分配并初始化为0 */
/* void* calloc(size_t num, size_t size) */
int n = 5;
int *arr1 = (int*)calloc(n, sizeof(int));
if (arr1 != NULL) {
printf("calloc分配的数组(自动初始化为0):\n");
for (int i = 0; i < n; i++) {
printf("arr1[%d] = %d\n", i, arr1[i]);
}
free(arr1);
}
/* malloc vs calloc */
printf("\n=== malloc vs calloc ===\n");
int *arr2 = (int*)malloc(n * sizeof(int));
printf("malloc分配的数组(未初始化):\n");
for (int i = 0; i < n; i++) {
printf("arr2[%d] = %d\n", i, arr2[i]); /* 可能是随机值 */
}
free(arr2);
/* realloc - 重新分配内存大小 */
/* void* realloc(void* ptr, size_t new_size) */
printf("\n=== realloc 示例 ===\n");
/* 初始分配 */
int *arr3 = (int*)malloc(5 * sizeof(int));
if (arr3 == NULL) {
printf("分配失败\n");
return 1;
}
/* 初始化 */
for (int i = 0; i < 5; i++) {
arr3[i] = i + 1;
}
printf("原始数组: ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr3[i]);
}
printf("\n");
/* 扩展数组 */
int *arr3_new = (int*)realloc(arr3, 10 * sizeof(int));
if (arr3_new == NULL) {
printf("重新分配失败\n");
free(arr3);
return 1;
}
arr3 = arr3_new; /* 使用新指针 */
/* 初始化新增部分 */
for (int i = 5; i < 10; i++) {
arr3[i] = i + 1;
}
printf("扩展后数组: ");
for (int i = 0; i < 10; i++) {
printf("%d ", arr3[i]);
}
printf("\n");
/* 缩小数组 */
arr3_new = (int*)realloc(arr3, 3 * sizeof(int));
if (arr3_new != NULL) {
arr3 = arr3_new;
printf("缩小后数组: ");
for (int i = 0; i < 3; i++) {
printf("%d ", arr3[i]);
}
printf("\n");
}
free(arr3);
/* realloc的特殊用法 */
printf("\n=== realloc特殊用法 ===\n");
/* realloc(NULL, size) 等价于 malloc(size) */
int *p = (int*)realloc(NULL, sizeof(int));
*p = 100;
printf("realloc(NULL, ...) = malloc: %d\n", *p);
/* realloc(ptr, 0) 等价于 free(ptr) */
realloc(p, 0); /* 释放内存 */
return 0;
}动态数组实现
c
/*
* 动态数组实现
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 动态数组结构体 */
typedef struct {
int *data; /* 数据指针 */
size_t size; /* 当前元素个数 */
size_t capacity;/* 容量 */
} DynamicArray;
/* 初始化动态数组 */
int array_init(DynamicArray *arr, size_t initial_capacity)
{
arr->data = (int*)malloc(initial_capacity * sizeof(int));
if (arr->data == NULL) {
return -1; /* 失败 */
}
arr->size = 0;
arr->capacity = initial_capacity;
return 0;
}
/* 释放动态数组 */
void array_free(DynamicArray *arr)
{
free(arr->data);
arr->data = NULL;
arr->size = 0;
arr->capacity = 0;
}
/* 扩展容量 */
int array_expand(DynamicArray *arr)
{
size_t new_capacity = arr->capacity * 2;
int *new_data = (int*)realloc(arr->data, new_capacity * sizeof(int));
if (new_data == NULL) {
return -1;
}
arr->data = new_data;
arr->capacity = new_capacity;
printf("容量扩展到: %zu\n", new_capacity);
return 0;
}
/* 添加元素 */
int array_push(DynamicArray *arr, int value)
{
if (arr->size >= arr->capacity) {
if (array_expand(arr) != 0) {
return -1;
}
}
arr->data[arr->size++] = value;
return 0;
}
/* 获取元素 */
int array_get(DynamicArray *arr, size_t index)
{
if (index >= arr->size) {
printf("索引越界\n");
return 0;
}
return arr->data[index];
}
/* 设置元素 */
int array_set(DynamicArray *arr, size_t index, int value)
{
if (index >= arr->size) {
printf("索引越界\n");
return -1;
}
arr->data[index] = value;
return 0;
}
/* 打印数组 */
void array_print(DynamicArray *arr)
{
printf("数组内容(size=%zu, capacity=%zu): ", arr->size, arr->capacity);
for (size_t i = 0; i < arr->size; i++) {
printf("%d ", arr->data[i]);
}
printf("\n");
}
int main(void)
{
DynamicArray arr;
/* 初始化 */
if (array_init(&arr, 4) != 0) {
printf("初始化失败\n");
return 1;
}
printf("初始状态:\n");
array_print(&arr);
/* 添加元素 */
printf("\n添加元素:\n");
for (int i = 1; i <= 10; i++) {
array_push(&arr, i * 10);
array_print(&arr);
}
/* 修改元素 */
printf("\n修改元素:\n");
array_set(&arr, 0, 100);
array_print(&arr);
/* 获取元素 */
printf("\n获取元素:\n");
printf("arr[5] = %d\n", array_get(&arr, 5));
/* 释放 */
array_free(&arr);
return 0;
}内存泄漏检测
c
/*
* 内存泄漏检测技巧
*/
#include <stdio.h>
#include <stdlib.h>
/* 简单的内存分配跟踪 */
#ifdef DEBUG_MEMORY
static size_t total_allocated = 0;
static size_t allocation_count = 0;
void* debug_malloc(size_t size, const char *file, int line)
{
void *ptr = malloc(size);
if (ptr != NULL) {
total_allocated += size;
allocation_count++;
printf("[ALLOC] %p (%zu bytes) at %s:%d\n",
ptr, size, file, line);
}
return ptr;
}
void debug_free(void *ptr, const char *file, int line)
{
if (ptr != NULL) {
allocation_count--;
printf("[FREE] %p at %s:%d\n", ptr, file, line);
}
free(ptr);
}
void memory_report(void)
{
printf("\n=== 内存报告 ===\n");
printf("总分配: %zu bytes\n", total_allocated);
printf("未释放: %zu 次\n", allocation_count);
if (allocation_count > 0) {
printf("警告: 可能存在内存泄漏!\n");
}
}
/* 替换标准函数 */
#define malloc(size) debug_malloc(size, __FILE__, __LINE__)
#define free(ptr) debug_free(ptr, __FILE__, __LINE__)
#endif
/* 使用Valgrind检测(Linux) */
/*
* 编译: gcc -g program.c -o program
* 运行: valgrind --leak-check=full ./program
*/
/* 使用AddressSanitizer(GCC/Clang) */
/*
* 编译: gcc -fsanitize=address -g program.c -o program
* 运行: ./program
*/
int main(void)
{
printf("=== 内存管理最佳实践 ===\n\n");
/* 1. 分配后检查返回值 */
int *p1 = (int*)malloc(sizeof(int));
if (p1 == NULL) {
printf("分配失败\n");
return 1;
}
/* 2. 使用后释放 */
*p1 = 100;
printf("值: %d\n", *p1);
free(p1);
/* 3. 释放后置NULL */
p1 = NULL;
/* 4. 谁分配谁释放 */
int *create_array(int n) {
int *arr = (int*)malloc(n * sizeof(int));
return arr; /* 调用者负责释放 */
}
void use_array(void) {
int *arr = create_array(10);
/* 使用数组 */
free(arr); /* 调用者释放 */
}
/* 5. 避免多次释放 */
/* 6. 避免释放后使用 */
/* 7. 注意realloc可能返回新地址 */
printf("\n内存管理建议:\n");
printf("1. 分配后检查返回值\n");
printf("2. 使用后及时释放\n");
printf("3. 释放后置NULL\n");
printf("4. 使用工具检测泄漏\n");
return 0;
}小结
本章学习了:
- 内存区域:代码段、数据段、BSS段、堆、栈
- malloc/free:动态内存分配和释放
- calloc:分配并初始化为0
- realloc:重新分配内存大小
- 动态数组:实现动态扩容的数组
- 内存泄漏检测:调试技巧和工具
下一章,我们将学习预处理器,了解C语言的预处理指令。
