21.动态内存分配 - malloc

int *arr = malloc(100 * sizeof(int));
  • malloc:内存分配函数(Memory ALLOCation)
  • 100 * sizeof(int):计算需要的内存大小
  • int *arr:声明一个整型指针接收分配的内存地址
  • 逐步解析

(1)sizeof(int):计算一个整数占多少字节
在大多数现代系统上,int占4字节
sizeof(int) → 结果为4

(2)100 * sizeof(int):计算总需求
需要存储100个整数 → 100 * 4 = 400字节

(3)malloc(400):向系统申请内存
系统在堆内存中寻找连续的400字节空闲区域
如果成功,返回这块内存的首地址(比如0x12345678)
如果失败,返回NULL

(4)int *arr = ...:指针接收地址
arr现在指向这400字节内存的开始位置
可以像普通数组一样使用arr[0]到arr[99]
  • 内存图示

假设分配成功,返回地址0x12345678:

arr → 0x12345678
       |
       v
      [0x12345678] arr[0] (第1个int)
      [0x1234567C] arr[1] (第2个int,地址+4)
      ...
      [0x123457E8] arr[99] (第100个int)
  • 与普通数组的区别
特性 普通数组 int arr[100] 动态数组 malloc
内存区域 栈(stack) 堆(heap)
大小 编译时固定 运行时动态决定
生命周期 随函数结束自动释放 需手动free()
大小修改 不可改变 可用realloc()调整
典型用途 已知大小的临时数据 需要动态增长/大型数据
  • 栈与堆的理解
点击查看代码
概念:在C语言中,**栈(Stack)和堆(Heap)**是两种核心的内存分配区域,它们的特性和用途有显著区别

一、栈(Stack)
1. 基本特性
分配方式:由编译器自动管理(自动分配/释放)。
生命周期:与函数调用相关。变量在函数结束时自动释放。
大小限制:通常较小(默认约1-8MB,取决于系统)。
访问速度:极快(直接通过指针移动分配内存)。
存储内容:局部变量、函数参数、返回地址等。

2. 典型例子
void func() {
    int a = 10;          // 栈上分配
    char str[100];       // 栈上数组(固定大小)
}
# a和str在func()执行时分配,函数结束自动释放

3. 优点
无需手动管理:自动释放,避免内存泄漏。
高速访问:内存连续,CPU缓存友好。

4. 缺点
大小固定:无法动态调整(如char str[100]不能扩容)。
栈溢出风险:大数组或递归过深会导致崩溃(如int arr[1000000])。

二、堆(Heap)
1. 基本特性
分配方式:手动管理(通过malloc/calloc/realloc申请,free释放)。
生命周期:从分配持续到显式释放(或程序结束)。
大小限制:受系统可用内存限制(通常远大于栈)。
访问速度:稍慢(需通过指针间接访问)。
存储内容:动态数据结构(如链表、动态数组)。

2. 典型例子
int *arr = malloc(100 * sizeof(int)); // 堆上动态数组
if (arr == NULL) { /* 处理失败 */ }
free(arr); // 必须手动释放!
# 内存需手动释放,否则会导致内存泄漏。

3. 优点
动态灵活:运行时决定大小(如malloc(n))。
大内存支持:适合处理大型数据(如1GB数组)。
全局可用:生命周期不受函数限制。

4. 缺点
管理复杂:需手动释放,易出错(泄漏/野指针)。
碎片化风险:频繁分配/释放可能产生内存碎片。

常见问题
1. 为什么栈比堆快?
栈通过简单的指针移动分配内存(如ESP寄存器),而堆需要复杂的内存管理算法。

2. 栈溢出和堆溢出的区别?
栈溢出:递归太深或局部变量过大(如int arr[1000000])。
堆溢出:持续分配不释放导致内存耗尽(如循环中malloc但无free)。

3. 动态内存可以放在栈上吗?
不可以。栈大小需编译时确定,但C99后支持变长数组(VLA),仍在栈上分配
int n = 100;
int arr[n]; // VLA(不推荐,可能引发栈溢出)


总结
栈:简单高效,适合小型临时数据,但不够灵活。
堆:灵活强大,适合动态需求,但需谨慎管理。

黄金法则:
能用栈优先用栈(安全高效)。
必须动态/大内存时再用堆(记得free!)。


  • 为什么需要动态数组?

1.处理未知大小的数据

// 用户输入决定数组大小
int n;
scanf("%d", &n);
int *arr = malloc(n * sizeof(int));

2.避免栈溢出

栈空间有限(通常约1-8MB)
堆空间大得多(通常以GB计)

3.长期保存数据

堆内存的生命周期不受函数限制
  • 使用示例
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 动态分配
    int *arr = malloc(100 * sizeof(int));
    if (arr == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }

    // 像普通数组一样使用
    for (int i = 0; i < 100; i++) {
        arr[i] = i * 2; // 赋值
    }

    // 打印部分值
    printf("arr[50] = %d\n", arr[50]);

    // 必须手动释放!
    free(arr);
    arr = NULL; // 避免野指针

    return 0;
}
  • 常见错误

1.忘记检查NULL

int *arr = malloc(1000000000); // 可能失败
arr[0] = 1; // 如果arr是NULL会崩溃

2.忘记释放内存(内存泄漏)

void func() {
    int *arr = malloc(100);
    // 忘记free(arr)
} // 每次调用func()都会泄漏100字节

3.访问已释放的内存

free(arr);
arr[0] = 1; // 危险!已释放的内存
  • 动态调整大小(realloc)
int *new_arr = realloc(arr, 200 * sizeof(int));
if (new_arr ! = NULL) {
    arr = new_arr; // 现在有200个元素的空间
} else {
    // 处理分配失败(原数据仍在arr中)
}
  • 总结
  1. malloc在堆上分配内存,返回起始地址
  2. 必须用指针接收返回值
  3. 需要手动计算字节数(数量 × sizeof(类型))
  4. 必须检查返回值是否为NULL
  5. 必须配对使用free()释放内存
  6. 动态数组突破了栈空间的限制,是C语言灵活性的核心体现
posted @ 2025-03-26 23:42  little小新  阅读(80)  评论(0)    收藏  举报