• C 语言编程 — 堆栈与内存管理


    目录

    前文列表

    程序编译流程与 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 头文件中找到。
    在这里插入图片描述
    注: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

    malloc 函数详解

    函数原型:

    void *malloc(size_t size);
    
    • size_t size:表示需要分配内存的字节数,使用 size_t 类型是因为不同的平台所具有的字节大小实际上是不一样的,使用 size_t 可以保证保证了程序对字节大小理解的统一,且为当前平台最大的字节可用范围(32 位系统通常为 32bit,64 位系统通常为 64bit,但均有特例)
    • 如果分配成功:则返回指向被分配内存空间的指针,指向一段可用内存的起始位置。因指向一段可用内存的起始位置为 void 表示未确定类型的指针,所以可以被强制类型转换为任意类型*。
    • 如果分配不成功:则返回空指针 NULL,所以在调用该函数之后需要判断是否成功分配了内存空间
    • 当内存不再使用的时候,应使调用 free 函数手动的将内存块释放掉,也只能释放一次,否则存在内存泄露问题。释放空指针例外,释放空指针也等于什么也没做,所以释放多少次都是可以的。
    int *p;
    p = (int *)malloc(sizeof(int));
    
    • malloc(sizeof(int)):指明了分配一个整型数据需要的大小的空间,如果写成 malloc(1) 则只是申请了一个字节大小的空间。

    简而言之,malloc 函数其实就是在内存中找到一片指定大小的、逻辑连续的内存空间,然后将这个空间的首地址给一个指针变量,这里的指针变量可以是一个单独的指针,也可以是一个数组的首地址,具体要看 size_t size 实参的具体内容。

    int *arr;
    arr = (int *)malloc(sizeof(int) * 10);
    
    • malloc(sizeof(int) * 10):指明了分配 10 个整型数据需要的大小的空间,这时返回的是一个数组的首地址。可以通过 arr[i] 来引用。

    memset 初始化内存数据

    在 C 语言编程汇中应该要保持一个习惯:定义变量时一定要进行初始化,尤其是数组和结构体这种占用内存大的数据结构。因为在使用数组或分配内存时,程序得到的只是一个内存空间的地址,实际上内存空间的值并非一直是 “干净” 的,在没有初始化的情况下,经常会因为脏数据而产生乱码。

    C 语言中,每种数据类型的变量都有各自的初始化方法,为 memset 函数可以说是初始化内存的万能函数,作用是在一段内存块中填充某个给定的值,通常用于为新申请的内存或数组进行初始化工作。

    函数原型:将指针变量 s 所指向的前 n 字节的内存单元用一个 int 类型 c(通常为 0)替换。其中,n 通常是使用 sizeof 获取的,因为 s 是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。用 memset 初始化完后,后续的代码中再向该内存空间中存放预期的数据。

    #include <string.h>
    void *memset(void *s, int c, unsigned long n);
    

    注意:如果是对指针变量所指向的内存单元进行清零初始化,那么一定要先对这个指针变量进行初始化,即一定要先让它指向某个有效的地址。

    示例

    #include <stdio.h>
    #include <string.h>
    
    
    int main(void)
    {
        int i;
        char str[10];
        char *p = str;
        memset(str, 0, sizeof(str));  // 应该写 sizeof(str), 而不是 sizeof(p)
        for (i=0; i<10; ++i) {
            printf("%dx20", str[i]);
        }
        printf("
    ");
        return 0;
    }
    

    相关阅读:

  • 相关阅读:
    html URLRewriter生成静态页不能访问
    sql server 2008 不允许保存更改,您所做的更改要求删除并重新创建以下表
    IIS7.0 伪静态页配置
    hubbledotnet 定时更新索引
    今天开通了这个BLOG。
    ASP.NET公有六种验证控件 功能描叙
    Recommend of the Day:Orkut社区和明星推荐
    每日英语:Why You Need a Dictator in a Marriage
    每日英语:An Unhappy Middle in the Middle Kingdom
    每日英语:Web Browsers Are Reinvented
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13309366.html
Copyright © 2020-2023  润新知