C 语言编程 — 堆栈与内存管理 目录 前文列表 栈(Stack)和堆(Heap) 内存管理 动态分配内存 重新调整内存的大小和释放内存

前文列表

程序编译流程与 GCC 编译器
C 语言编程 — 基本语法
C 语言编程 — 基本数据类型
C 语言编程 — 变量与常量
C 语言编程 — 运算符
C 语言编程 — 逻辑控制语句
C 语言编程 — 函数
C 语言编程 — 高级数据类型 — 指针
C 语言编程 — 高级数据类型 — 数组
C 语言编程 — 高级数据类型 — 字符串
C 语言编程 — 高级数据类型 — 枚举
C 语言编程 — 高级数据类型 — 结构体与位域
C 语言编程 — 高级数据类型 — 共用体
C 语言编程 — 高级数据类型 — void 类型
C 语言编程 — 数据类型的别名
C 语言编程 — 数据类型转换
C 语言编程 — 宏定义与预处理器指令
C 语言编程 — 异常处理
C 语言编程 — 头文件
C 语言编程 — 输入/输出与文件操作

栈(Stack)和堆(Heap)

C 语言的设计者把内存简单粗暴地想象成一个巨大的字节(Byte)数组。事实上,它被更加合理地划分成了两部分,即栈和堆。实际上,它们只是内存中的两块不同的区域,分别用来完成不同的任务而已。

栈是程序赖以生存的地方,所有的临时变量和数据结构都保存于其中,供你读取及编辑。每次调用一个新的函数,就会有一块新的栈区压入,并在其中存放函数内的临时变量、传入的实参的拷贝以及其它的一些信息。当函数运行完毕,这块栈区就会被弹出并回收,供其他函数使用。

我喜欢把栈想象成一个建筑工地。每次需要干点新事情的时候,我们就圈出一块地方来,放工具、原料,并在这里工作。如果需要的话,我们也可以到工地的其他地方,甚至是离开工地。但是我们所有的工作都是在自己的地方完成的。一旦工作完成,我们就把工作成果转移到新的地方,并把现在工作的地方清理干净。

堆占据另一部分内存,主要用来存放长生命周期期的数据。堆中的数据必须手动申请和释放

申请内存使用 malloc 函数。这个函数接受一个数字作为要申请的字节数,返回申请好的内存块的指针。当使用完毕申请的内存,我们还需要将其释放,只要将 malloc 函数返回的指针传给 free 函数即可。

堆比栈的使用难度要大一些,因为它要求程序员手动调用 free 函数释放内存,而且还要正确调用。如果不释放,程序就有可能不断申请新的内存,而不释放旧的,导致内存越用越多。这也被称为内存泄漏。避免这种情况发生的一个简单有效的办法就是,针对每一个 malloc 函数调用,都有且只有一个 free 函数与之对应。这某种程度上就能保证程序能正确处理堆内存的使用。

我把堆想象成一个自助存储仓库,我们使用 malloc 函数申请存储空间。我们可以在自主存储仓库和建筑工地之间*存取。它非常适合用来存放大件的偶尔才用一次的物件。唯一的问题就是在用完之后要记得使用 free 函数将空间归还。

内存管理

C 语言为内存的分配和管理提供了几个标准函数。这些函数可以在 stdlib.h 头文件中找到。
C 语言编程 — 堆栈与内存管理
目录
前文列表
栈(Stack)和堆(Heap)
内存管理
动态分配内存
重新调整内存的大小和释放内存
注:void *类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过强制类型转换为任何其它类型的指针。

动态分配内存

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

int main() {
    char name[100];
    /* 定义字符指针类型变量 */
    char *description;

    strcpy(name, "Zara Ali");
    /* 
        分配内存,内存大小为 200 个字符长度(8 Bit)。
        函数调用返回内存的指针(地址)并强制类型转换为字符指针类型。
    */
    description = (char *)malloc(200 * sizeof(char));

    if (description == NULL) {
        fprintf(stderr, "Error - unable to allocate required memory
");
    }
    else {
        strcpy(description, "Zara ali a DPS student in class 10th");
    }
    printf("Name = %s
", name);
    printf("Description: %s
", description);
    return 0;
}

运行:

$ ./main
Name = Zara Ali
Description: Zara ali a DPS student in class 10th

上面的程序也可以使用 calloc() 函数来编写,只需要把 malloc 替换为 calloc 即可:

calloc(200, sizeof(char));

当动态分配内存时,程序有完全控制权,可以传递任何大小的值。不同的是,那些预先定义了大小的数组,一旦定义则无法改变大小。

重新调整内存的大小和释放内存

当程序退出时,操作系统会自动释放所有分配给程序的内存,但是主动释放内存是一个良好的编程习惯。

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

int main() {
    char name[100];
    char *description;

    strcpy(name, "Zara Ali");

    /* 
        分配内存,内存大小为 200 个字符长度(8 Bit)。
        函数调用返回内存的指针(地址)并强制类型转换为字符指针类型。
    */
    description = (char *)malloc(200 * sizeof(char));

    if (description == NULL) {
        fprintf(stderr, "Error - unable to allocate required memory
");
    }
    else {
        strcpy(description, "Zara ali a DPS student in class 10th
");
    }

    /* 
        扩展分配内存,内存大小为 100 个字符长度(8 Bit)。
        函数调用返回内存的指针(地址)并强制类型转换为字符指针类型。
    */
    description = (char *)realloc(description, 100 * sizeof(char));

    if (description == NULL) {
        fprintf(stderr, "Error - unable to allocate required memory
");
    }
    else {
        strcat(description, "She is in class 10th
");
    }

    printf("Name = %s
", name);
    printf("Description: %s
", description);

    /* 释放内存 */
    free(description);
    return 0;
}

运行:

$ ./main
Name = Zara Ali
Description: Zara ali a DPS student in class 10th
She is in class 10th