首页 > 编程开发 > C类语言    日期:2026-07-05 / 浏览

栈区与堆区初探

C程序会对内存进行分区,主要分为5个区域:

  • 栈区(Stack)
  • 堆区(Heap)
  • 全局/静态区
  • 常量区(Constant)
  • 代码区

我们先主要了解前两个:

栈内存由编译器自动分配和释放,我们不需要操心。每调用一个函数,都会在栈区为该函数分配一块内存区域,这块区域就叫做函数栈帧。其中主要存放一些非静态的局部变量、函数参数等。

例如,下面代码中的函数形参 b、定义的局部变量 a 所用到的内存,都会由编译器自动开辟,开辟的方式是静态内存开辟。当 add 函数执行完毕返回时,对应的函数栈帧就会被销毁,自然这些占用的内存会被编译器自动回收。

int add(int b) {
    int a = 10;
    return a + b;
}

堆内存由我们程序员手动分配(malloccalloc)和释放(free)。

malloc 和 calloc 的区别在于:malloc 分配的内存存储的都是未初始化的随机垃圾值,而 calloc 会自动将分配的内存全部初始化为 0。

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 申请了32MB的内存
    int* arr = (int*) malloc(8 * 1024 * 1024 * sizeof(int)); // 返回值类型是void*,表示无类型指针,我们可以强转赋予它类型
    
    // 每次动态分配内存后,都要检查返回值是否为 NULL
    if (arr == NULL) {
        // 防止操作到空指针,导致程序崩溃
        printf("Memory allocation failed!\n");
        return -1; 
    }

    // 释放内存        
    free(arr);
    // 置空,防止野指针
    arr = NULL;
    
    return 0;
}

堆内存的开辟方式是动态内存开辟,这些内存不会自动回收,如果不手动回收,就会造成内存泄漏。

此外,栈空间通常很小(1MB),堆空间则很大,和系统可用的内存有关。

运行时决定内存大小

动态内存开辟的使用场景有很多:数据长度只在运行时才确定、栈空间不满足需求、需要延长变量的生命周期、内存大小需要动态改变等。

我们以第一种场景为例:运行时由用户输入决定人员的数量。

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 用户输入
    int num = 0;
    printf("Please enter the number of people.\n");
    scanf_s("%d", &num);

    // 开辟对应大小的空间
    int* arr = (int*)malloc(num * sizeof(int));
    
    // 检查内存是否开辟成功
    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return -1; // 退出程序
    }

    for (int i = 0; i < num; i++)
    {
        // 输入年龄
        int age = 0;
        printf("Please enter the age of the %d member at this position.\n", i + 1);
        scanf_s("%d", &age);
        arr[i] = age;
    }

    // 输出每个人的年龄
    for (int i = 0; i < num; i++)
    {
        printf("The age of the %d member is %d\n", i + 1, arr[i]);
    }

    // 释放并置空
    free(arr);
    arr = NULL;
    
    return 0;
}

scanf_s 是 Visual Studio 环境下特有的安全函数,在非 VS 环境中请使用 scanf。

运行结果:

realloc 的扩容机制与暗坑

再来看看第四种场景,普通的数组一旦定义后,长度就固定了,而动态内存的大小可以使用 realloc 进行重新调整,根据自己的需要扩容或缩容。

使用 realloc 进行扩容时,有两种情况:

原地扩容:如果原位置后有足够的连续内存空间,它会直接在原地址后追加空间,返回的地址和原地址相同。

异地扩容:如果原位置所需的连续地址空间不足,它会尝试在堆区找到一块合适的内存空间,将之前的数据拷贝到新位置,并自动释放之前的旧内存,最后返回指向这块新内存空间的指针。

因为异地扩容很常见,所以我们应该总是要使用新的指针去接收返回值。同时,如果发生后了异地扩容,原来的指针就变为了野指针,应该置为空。

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 初始可以存储8个整型
    int* p = (int*)malloc(8 * sizeof(int));
    if (p == NULL) {
        printf("Initial memory allocation failed.\n");
        return -1;
    }

    for (int i = 0; i < 8; i++)
    {
        p[i] = i + 1;
    }

    printf("Before capacity expansion\n");
    for (int i = 0; i < 8; i++)
    {
        printf("%d ", p[i]);
    }

    // 扩容至16
    printf("\nAfter capacity expansion\n");
    
    // 使用新指针变量接收,防止因扩容失败导致原内存地址 p 丢失
    int* new_p = (int*)realloc(p, 16 * sizeof(int));
    
    if (new_p == NULL) {
        printf("\nFailed to allocate memory for expansion.\n");
        // 扩容失败,旧内存 p 依然有效,程序结束前记得释放
        free(p);
        p = NULL;
        return -1;
    }

    // 扩容成功,原指针 p 可能已在异地扩容中被自动释放而失效,为防止误用,我们将其置空
    p = NULL; 

    for (int i = 8; i < 16; i++)
    {
        new_p[i] = i + 1;
    }
    for (int i = 0; i < 16; i++)
    {
        printf("%d ", new_p[i]);
    }

    // 此时由 new_p 管理这块空间,我们只需释放 new_p
    free(new_p);
    new_p = NULL;

    return 0;
}

如果 realloc 扩容失败,它将返回 NULL 空指针,但旧内存不会被释放,我们需要手动处理。

#include <stdio.h>
#include <stdlib.h>

int main() {
    int* p = (int*)malloc(8 * sizeof(int));
    if (p == NULL) return -1;
    
    // 尝试申请一块非常大的内存,模拟失败的情况
    int* new_p = (int*)realloc(p, 8 * 1024LL * 1024 * 1024 * sizeof(int));

    if (new_p == NULL)
    {
        printf("Failed to allocate memory.\n");
        // 虽然申请新内存失败,但是旧内存块 p 依然存在,需要由我们手动释放
        free(p);
        p = NULL;
    }
    else 
    {
        printf("Success to allocate memory.\n");
        // 如果成功,释放新指针 new_p 即可
        free(new_p);
        new_p = NULL;
        p = NULL; // 置空防误用
    } 
    
    return 0;
}

注意:永远不要多次释放同一块内存,可能会导致程序崩溃。

觉得上面的内容有用吗?快来点个赞吧!

点赞() 我要打赏

温馨提示 : 本站内容来自会员投稿以及互联网,所有源码及教程均为作者总结编辑,请大家在使用过程中提前做好备份,以免发生无法预知的错误,源码类教程请勿直接用于生产环境!

 可能感兴趣的文章