uc笔记03---环境变量,内存管理,进程映像/堆栈区别,虚拟内存,malloc 特点

本文详细介绍了Linux环境变量的查看、设置与管理,包括`getenv`、`putenv`、`setenv`和`unsetenv`函数的使用。接着讨论了内存管理的不同层次,从STL、C++的`new/delete`、C语言的`malloc/free`到操作系统层面的`brk/sbrk`和`mmap/munmap`。此外,还讲解了进程映像、堆栈的区别以及虚拟内存的概念,强调了动态分配内存时控制信息的重要性。最后,通过示例展示了如何使用`sbrk`和`brk`进行内存分配和释放,并给出了自行实现`malloc`的思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.    环境变量
    #env        // 查看环境变量
    
    环境表
    1) 每个程序都会接收到一张环境表,
       是一个以 NULL 指针结尾的字符指针数组。

    2) 全局变量 environ 保存环境表的起始地址。
                +---+
     environ -> | * --> HOME=/root
                +---+
                | * --> SHELL=/bin/bash
                +---+
                | * --> PATH=/bin:/usr/bin:...:.
                +---+
                 | . |
                 | . |
                 | . |
                +---+
                 | 0 |

    图示:env_list.bmp
    范例:env.c
        #include <stdio.h>
        void printenv () {
            extern char** environ;            // 不是定义,只是声明,名字只能写 environ;
            char** env;
            for (env = environ; env && *env; ++env)
                printf ("%s\n", *env);
        }
        int main () {
            char env[256];
            const char* name = "MYNAME";
            sprintf (env, "%s=minwei", name);
            putenv (env);
            printf("%s=%s\n", name, getenv (name));
            
            sprintf (env, "%s=beijing", name);
            putenv (env);
            printf("%s=%s\n", name, getenv (name));
            
            setenv (name, "minwei", 0);        // 如果存在,不覆盖;
            printf("%s=%s\n", name, getenv (name));
            
            printenv ();
            return 0;
        }

2.     环境变量函数

    #include <stdlib.h>

    环境变量:name=value

    getenv   - 根据 name 获得 value。
    putenv   - 以 name=value 的形式设置环境变量,
               name 不存在就添加,存在就覆盖其 value。
    setenv   - 根据 name 设置 value,不存在就添加;注意最后一个参数表示,
                最后一个参数为 0:若 name 已存,不覆盖其 value;
                最后一个参数为 1:若 name 已存,覆盖其 value;
    unsetenv - 删除一个环境变量。
    clearenv - 清空环境变量,environ==NULL。
    
    上面这些环境变量函数,都是改变本进程的环境变量,不会改变系统 shell 的环境变量;
    当程序结束时,设置的所有环境变量也都将失效。

    范例:env.c
        #include <stdio.h>
        void printenv () {
            extern char** environ;            // 不是定义,只是声明,名字只能写 environ;
            char** env;
            for (env = environ; env && *env; ++env)
                printf ("%s\n", *env);
        }
        int main () {
            char env[256];
            const char* name = "MYNAME";
            sprintf (env, "%s=minwei", name);
            putenv (env);
            printf("%s=%s\n", name, getenv (name));
            
            sprintf (env, "%s=beijing", name);
            putenv (env);
            printf("%s=%s\n", name, getenv (name));
            
            setenv (name, "minwei", 0);        // 如果存在,不覆盖;
            printf("%s=%s\n", name, getenv (name));
            
            setenv (name, "minwei", 1);        // 如果存在,覆盖;
            printf("%s=%s\n", name, getenv (name));
            
            unsetenv (name);

            return 0;
        }

3.    内存管理

    +----+--------+----------------------------+----------+
        STL        自动分配/释放内存资源            调C++
        C++        new/delete,构造/析构            调标C
         标C            malloc/calloc/realloc/free    调POSIX
        POSIX        brk/sbrk                        调Linux
         Linux        mmap/munmap                    调Kernel
    +----+--------+----------------------------+----------+
        Kernel        kmalloc/vmalloc                调Driver
        Driver        get_free_page                    ...
         . . .          . . .                                . . .
    +----+--------+----------------------------+----------+

4.    进程映像
    1)程序是保存在磁盘上的可执行文件。

    2)运行程序时,需要将可执行文件加载到内存,形成进程。

    3)一个程序(文件)可以同时存在多个进程(内存)。

    4)进程在内存空间中的布局就是进程映像。
    从高地址到低地址依次为:
    
        ===============================================================
        命令行参数与环境区:
            命令行参数和环境变量;
        ---------------------------------------------------------
        栈区(stack):0xbf
            非静态局部变量,包括函数的参数和返回值;从高地址向低地址扩展;        /* 所有局部(非静态)变量都位于这个区域 */
        ---------------------------------------------------------
        堆区和栈区之间存在一块间隙(shared):0x40
            一方面为堆和栈的增长预留空间,
            同时共享库、共享内存等亦位于此;
            (后面进程间通信会用到共享内存,参见 DAY09 “共享内存”)
        ---------------------------------------------------------
        堆区(heap):0x08
            动态内存分配;从低地址向高地址扩展;
        ---------------------------------------------------------
        BSS区:
            未初始化的全局和静态局部变量;进程一经加载此区即被清 0;
            (数据区和BSS区有时被合称为全局区或静态区)
        ---------------------------------------------------------    /* 静态位于这个区域 */
        数据区(data):
            初始化的全局和静态局部变量;
        ---------------------------------------------------------
        代码区(text):
            可执行指令、字面值常量、具有常属性的全局和静态局部变量;只读;
        ===============================================================
        
        范例:maps.c
            #include <stdio.h>
            #include <stdlib.h>
            const int const_global = 0;        // 常全局变量
            int init_global = 0;            // 初始化全局变量
            int uninit_global;                // 未初始化全局变量
            int main (int argc, char* argv[]) {        // 命令行参数
                const static int const_static = 0;    // 常静态变量
                static int init_static = 0;            // 初始化静态变量
                static int uninit_static;            // 未初始化静态变量
                const int const_local = 0;            // 常局部变量
                int prev_local;                        // 前局部变量
                int next_local;                        // 后局部变量
                int* prev_heap = malloc (sizeof (int));    // 前堆变量
                int* next_heap = malloc (sizeof (int));    // 后堆变量
                const char* literal = "literal";        // 字面值常量
                extern char** environ;                    // 环境变量
                // 按地址从高到低打印
                printf ("环境变量 %p\n", environ);
                printf ("命令行参数 %p\n", argv);
                // 栈
                printf ("常局部变量 %p\n", &const_local);
                printf ("前局部变量 %p\n", &prev_local);
                printf ("后局部变量 %p\n", &next_local);
                // 堆
                printf ("后堆变量 %p\n", next_heap);
                printf ("前堆变量 %p\n", prev_heap);
                // BSS
                printf ("未初始化全局变量 %p\n", &unint_global);
                printf ("未初始化静态变量 %p\n", &unint_static);
                // 数据
                printf ("初始化静态变量 %p\n", &init_static);
                printf ("初始化全局变量 %p\n", &init_global);
                // 代码区
                printf ("常静态变量 %p\n", &const_static);
                printf ("字面值常量 %p\n", literal);
                printf ("常全局变量 %p\n", &const_global);
                printf ("主函数 %p\n", main);
                // 查看 pid
                printf ("查看/proc/%u/maps");
                getpid();
                // 用户不按回车,系统就不会结束,用于有时间查看 pid
                getchar ();
                return 0;
            }

    查看进程映像 pid
    #vi /proc/<pid>/maps

    #size a.out

        text    data     bss     dec     hex filename
        2628     268      28    2924     b6c a.out
         |       |       |       |       |
         +-------+-------+  (10) +---+---+ (16)
                  V                   ^
                    +-------------------+
                              (+)
    
    堆和栈的区别:
    1)申请方式
    栈: 由系统自动分配。
        例如,声明在函数中一个局部变量 int b; 系统自动在栈中为 b 开辟空间;
    堆: 需要程序员自己申请,并指明大小,在 c 中 malloc 函数
        如 p1 = (char*)malloc(10);
        在 C++ 中用 new 运算符
        如 p2 = (char*)malloc(10);
        但是注意 p1、p2 本身是在栈中的。
        
    2)申请后系统的响应
    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
    堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,
    寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,
    另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,
    这样,代码中的 delete 语句才能正确的释放本内存空间。
    另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
    
    3)申请大小的限制
    栈:在 Windows 下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。
    这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,
    在 WINDOWS 下,栈的大小是 2M(也有的说是 1M,总之是一个编译时就确定的常数),
    如果申请的空间超过栈的剩余空间时,将提示 overflow。因此,能从栈获得的空间较小。
    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,
    自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。
    由此可见,堆获得的空间比较灵活,也比较大。
    
    4)申请效率的比较:
    栈: 由系统自动分配,速度较快。但程序员是无法控制的。
    堆: 是由 new 分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
    另外,在 WINDOWS 下,最好的方式是用 Virtual Alloc 分配内存,他不是在堆,也不是在栈,
    而是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
    
    5)堆和栈中的存储内容
    栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,
    然后是函数的各个参数,在大多数的 C 编译器中,参数是由右往左入栈的,然后是函数中的局部变量。
    注意:静态变量是不入栈的。
    当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,
    也就是主函数中的下一条指令,程序由该点继续运行。
    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。

    6)存取效率的比较
    char s1[]="aaaaaaaaaaaaaaa";
    char *s2="bbbbbbbbbbbbbbbbb";
    aaaaaaaaaaa 是在运行时刻赋值的;
    而 bbbbbbbbbbb 是在编译时就确定的;
    但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
    比如:
        #include
        void main() {
            char a=1;
            char c[]="1234567890";
            char *p="1234567890";
            a = c[1];
            a = p[1];
            return;
        }
    对应的汇编代码
    10:a=c[1];
    004010678A4DF1movcl,byteptr[ebp-0Fh]
    0040106A884DFCmovbyteptr[ebp-4],cl
    11:a=p[1];
    0040106D8B55ECmovedx,dwordptr[ebp-14h]
    004010708A4201moval,byteptr[edx+1]
    004010738845FCmovbyteptr[ebp-4],al
    第一种在读取时直接就把字符串中的元素读到寄存器 cl 中,
    而第二种则要先把指针值读到 edx 中,在根据 edx 读取字符,显然慢了。

5.    虚拟内存
    1)每个进程都有各自互独立的 4G 字节虚拟地址空间:
        4G 进程地址空间分成两部分:
            [0, 3G) 为用户空间,
        如某栈变量的地址 0xbfc7fba0 = 3,217,554,336,约 3G;
            [3G, 4G) 为内核空间。

    2)用户程序中使用的都是虚拟地址空间中的地址,
       永远无法直接访问实际物理内存地址;
       
       用户空间中的代码,不能直接访问内核空间中的代码和数据,
       但可以通过系统调用进入内核态,间接地与系统内核交互。
       
       用户空间对应进程,进程一切换,用户空间即随之变化;
       内核空间由操作系统内核管理,不会随进程切换而改变;
       内核空间由内核根据独立且唯一的页表 init_mm.pgd 进行内存映射,而用户空间的页表则每个进程一份;
       所以,两个同时运行的进程,即便拿到的地址一样,那也是虚拟地址,他们映射的物理地址一定是不一样的;

    每个进程的内存空间完全独立。
    不同进程之间交换虚拟内存地址是毫无意义的。
       
       对内存的越权访问,或试图访问没有映射到物理内存的虚拟内存,将导致段错误。

    3)虚拟内存到物理内存的映射由操作系统动态维护。

    4)虚拟内存一方面保护了操作系统的安全,
    另一方面允许应用程序,使用比实际物理内存更大的地址空间。

    图示:vm.png
    图示:kernel.png

    范例:vm.c
        #include <stdio.h>
        int g_vm = 0;
        int mian () {
            printf ("%p\n", &g_vm);
            scanf ("%d%*c", &g_vm);    // %*c 忽略回车
            // 启动另一个进程,按回车继续
            getchar ();
            printf ("%d\n", g_vm);
        }

6.    标准库内部通过一个双向链表,管理在堆中动态分配的内存。
    malloc 函数分配内存时会附加若干(通常是 12 个)字节,存放控制信息;
    该信息一旦被意外损坏,可能在后续操作中引发异常。

    范例:crash.c
        #include <stdio.h>
        #include <stdlib.h>    // malloc
        int main () {
            int* p1 = malloc (sizeof (int));
            int* p2 = malloc (sizeof (int));
            printf ("%p\n", p1);
            printf ("%p\n", p2);
            free (p2);
            free (p1);
            return 0;
        }
        输出结果会发现,p1 和 p2 之间相差 16 字节,而非 4 字节;
        所以 p1 和 p2 之间有 12 字节的空间,用于存放控制信息;
        如果在 free (p2); 和 free (p1); 之间加入 p1[3] = 0;
        导致段错误;

7.    页    
    虚拟内存到物理内存的映射以页 (4K = 4096 字节) 为单位。
    通过 malloc 函数首次分配内存,至少映射 33 页。
    即使通过 free 函数释放掉全部内存,
    最初的 33 页仍然保留。
    (下面的 sbrk/mmap 一次分配一页)

    图示:address_space.png

    #include <unistd.h>
    int getpagesize (void);
    返回内存 1 页的字节数。

    范例:page.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    void presskey () {
        printf ("查看/proc/%u/maps,按<enter>继续 . . .", getpid ());
        getchar ();
    }
    int main () {
        printf ("%d\n", getpagesize ());
        char* pc = malloc (sizeof (char));
        printf ("%p\n", pc);
        presskey ();
        free (pc);
        
        presskey ();
        setbuf (stdout, NULL);    // 关闭输出缓冲区,保证下面 printf 可以正常输出;
        size_t i = 0;
        for ( ; ; ) {
            printf ("向堆内存%p写. . . ", &pc[i]);
            printf ("%c\n", pc[i++] = (i % 26) + 'A');
        }
        return 0;
    }
    
    分析:
        char* pc = malloc (sizeof (char));
                |
              v<--------------- 33页 --------------->|
        ------+-------+----------+-------------------+------
               |  1 字节  |  控制信息  |                      |
        ------+-------+----------+-------------------+------
          ^        ^          ^                ^              ^
        段错误     OK     后续错误          不稳定          段错误

8.    内存管理 APIs

    1) 增量方式分配虚拟内存

    #include <unistd.h>
    void* sbrk (
        intptr_t increment        // 内存增量(以字节为单位)
    );
    返回上次调用 brk/sbrk 后的末尾地址,失败返回 -1。

    increment取值:
         0 - 获取末尾地址;
        >0 - 增加内存空间;
        <0 - 释放内存空间;

    内部维护一个指针,指向当前堆内存最后一个字节的下一个位置。
    sbrk 函数根据增量参数调整该指针的位置,同时返回该指针原来的位置。
    若发现页耗尽或空闲,则自动追加或取消页映射。

    void* p=sbrk(4);     p=sbrk(0);
           ^                ^
           |                 |
         返回 *--increment->* 返回
           |                 |
         v               v
     --+---+---+---+---+---+---+--
     | B | B | B | B | B | B |
     --+---+---+---+---+---+---+--
          |<--------- 页 --------

    2)修改虚拟内存块末尾地址

    #include <unistd.h>
    int brk (
        void* end_data_segment        // 内存块末尾地址
    );
    成功返回 0,失败返回 -1。

    内部维护一个指针,指向当前堆内存最后一个字节的下一个位置。
    brk 函数根据指针参数设置该指针的位置。
    若发现页耗尽或空闲,则自动追加或取消页映射。

        void* p=sbrk(0);   brk(p+4);
               ^                 |
              |               v
              返回 *           * 设置
               |                  |
               v               v
        --+---+---+---+---+---+---+--
           | B | B | B | B | B | B |
        --+---+---+---+---+---+---+--
               |<--------- 页 --------

    sbrk/brk 底层维护一个指针位置,
    以页 (4K) 为单位分配和释放虚拟内存。
    简便起见,可用 sbrk 分配内存,用 brk 释放内存。

    范例:brk.c
        #include <stdio.h>
        #include <unistd.h>
        void presskey () {
        printf ("查看/proc/%u/maps,按<enter>继续 . . .", getpid ());
        getchar ();
        }
        int main () {
            // sbrk 举例
            void* p1 = sbrk (4);            // RXXX(R 为 sbrk 返回的地址)
            printf ("p1 = %p\n", p1);
            void* p2 = sbrk (4);            // XXXX RXXX
            printf ("p2 = %p\n", p2);
            void* p3 = sbrk (4);            // XXXX XXXX RXXX
            printf ("p3 = %p\n", p3);
            void* p4 = sbrk (0);            // XXXX XXXX XXXX R
            printf ("p4 = %p\n", p4);
            
            int* pn = (int*)p1;
            pn[0] = 0;                    // OK
            pn[1] = 1;                    // OK
            pn[2] = 2;                    // OK
            pn[1023] = 1023;                // OK
            pn[1024] = 1024;                // 段错误
            
            void* p5 = sbrk (-8);        // XXXX ---- ---- R
            void* p6 = sbrk (-4);        // ---R(返回上一次 sbrk 调用后的地址)
            
            pn[0] = 10;                    // 段错误;和 malloc 不一样,映射结束;
            
            int page = getpagesize ();
            printf ("%p\n", sbrk (page));
            printf ("%p\n", sbrk (1));
            printf ("%p\n", sbrk (-page));
            printf ("%p\n", sbrk (-1));
            // brk 举例
            p1 = sbrk (0);                // 用 brk 需要先计算初始地址
            printf ("%p\n", p1);
            brk (p2 = p1 + 4);            // XXXX S
            brk (p3 = p2 + 4);            // XXXX XXXX S
            brk (p4 = p3 + 4);            // XXXX XXXX XXXX S
            
            pn = (int*)p1;
            pn[0] = 0;                    // OK
            pn[1] = 1;                    // OK
            pn[2] = 2;                    // OK
            pn[1023] = 1023;            // OK
            pn[1024] = 1024;            // 段错误
            
            brk (p2);                    // XXXX S
            brk (p1);                    // S
            
            pn[0] = 10;                 // 段错误;
            // 用 sbrk 分配内存:
            void* begin = sbrk (sizeof (int));
            pn = (int*)begin;
            *pn = 1234;
            double* pd = (double*)sbrk (sizeof (double));
            *pd = 3014;
            char* psz = (char*)sbrk (sizeof (char) * 256);
            sprintf (psz, "hello word");
            brk (begin);                    // 一下子全部释放;
            
            return 0;
        }
        分析:p1,p2,p3 之间相差 4 字节,没用 malloc,所以没有多加 12 字节;
        
9.    练习:自己实现 malloc
    思路:定义一个结构体,表示 12 字节的内存控制块;
    申请内存的时候,从后往前遍历,遇到空闲内存块大于等于需要内存的时候,就直接返回该内存块地址;
    否则,就需要自己新定义一个内存(页);
    
    #include <stdio.h>
    #inlcude <unistd.h>
    #include <stdbool.h>
    // 内存控制块 12 字节
    typedef struct men_control_block {            // 重命名
        bool free;                                // 自由标志,用于判断本块内存是否闲置
        struct men_control_block* prev;            // 前块指针
        size_t size;                            // 块大小
    } MCB;
    MCB* g_top = NULL;                            // 栈顶指针(指向最后一个块的指针)
    
    // +----------------------+              g_top
    // v                      |                |
    // +------+------------+------+------------+------+------------+
    // | prev |            | prev |            | prev |            |
    // | free |            | free |            | free |            |
    // | size |            | size |            | size |            |
    // +------+------------+------+------------+------+------------+
    //   MCB  |<-- size -->|
    
    void* my_malloc (size_t size) {
        // 找空闲块
        MCB* mcb;
        for (mcb = g_top; mcb; mcb->prev)        // 在现有页中,从后往前遍历
            if (mcb-prev && mcb->size >= size)    // 找到合适大小的内存块
                break;
        if (! mcb) {                                // 现有页中,没找到合适大小的内存块
            mcb = sbrk (sizeof (MCB) + size);    // 重新分配一页内存
            if (mcb == (void*)-1)                // 分配失败
            // sbrk 分配失败返回 -1,但是 mcb 是指针类型,所以对 -1 进行强制类型转换;
                return NULL;                        // malloc 分配失败也是返回 NULL,同步;
            mcb->prev = g_top;
            mcb->size = size;
            g_top = mcb;
        }
        // 无论是在现有里面找到还是新分配的,都需要把标志置为零;
        mcb->free = false;                        // 0 说明这块内存闲置;
        return mcb + 1;
        // 此时 mcb 指向控制块首部,+1 相当于加 12 字节,指向控制块尾部
    }
    // 释放内存
    void my_free (void* ptr) {
        if (! ptr)    // 拿到空指针
            return;
        MCB* mcb = (MCB*)ptr -1;        // 跳到控制快首
        mcb->free = true;
        for (mcb = g_top; mcb->prev; mcb = mcb->prev)
            if (! mcb->free)
                break;
        if (mcb->free) {        // 这个块之后全是空
            g_top = mcb->prev;
            brk (mcb);
        }
        else if (g_top != mcb) {
            g_top = mcb;
            brk ((void*)mcb + sizeof (mcb) +mcb->size);
        }
    }
    int main () {
        int* p1 = my_malloc (10 * sizeof (int));
        int* p1 = my_malloc (5 * sizeof (int));
        size_t i;
        for (i = 0; i < 10; ++i)
            p1[i] = i + 1;
        for (i = 0; i < 10; ++i)
            p2[i] = 10 + i + 1;
        my_free (p1);
        my_free (p2);
    }

10.    创建虚拟内存到物理内存或文件的映射 mmap

    #include <sys/mman.h>
    void* mmap (
        void*  start,  // 映射区内存起始地址,NULL 系统自动选定,成功返回之
        size_t length, // 字节长度,自动按页(4K)对齐
        int    prot,   // 映射权限
        int    flags,  // 映射标志
        int    fd,     // 文件描述符
         off_t  offset  // 文件偏移量,自动按页(4K)对齐
    );
    成功返回映射区内存起始地址,失败返回 MAP_FAILED(-1)。

    prot取值(只能选其中一个):
        PROT_EXEC  - 映射区域可执行。
        PROT_READ  - 映射区域可读取。
        PROT_WRITE - 映射区域可写入。
        PROT_NONE  - 映射区域不可访问。

    flags取值:
        MAP_FIXED     - 若在 start 上无法创建映射,则失败(无此标志系统会自动调整)。
        MAP_SHARED    - 对映射区域的写入操作直接反映到文件中。
        MAP_PRIVATE   - 对映射区域的写入操作只反映到缓冲区中,不会真正写入文件。
        MAP_ANONYMOUS - 匿名映射,将虚拟地址映射到物理内存而非文件,忽略 fd。
        MAP_DENYWRITE - 拒绝其它对文件的写入操作。
        MAP_LOCKED    - 锁定映射区域,保证其不被置换。

11.    销毁虚拟内存到物理内存或文件的映射 munmap

    int munmap (
        void*  start,  // 映射区内存起始地址
        size_t length, // 字节长度,自动按页(4K)对齐
    );
    成功返回 0,失败返回-1。

    mmap/munmap 底层不维护任何东西,只是返回一个首地址,所分配内存位于堆中。

    brk/sbrk 底层维护一个指针,记录所分配的内存结尾,
    所分配内存位于堆中,底层调用 mmap/munmap。

    malloc 底层维护一个双向链表和必要的控制信息,
    不可越界访问,所分配内存位于堆中,底层调用 brk/sbrk。
    
    切记:malloc 和 brk/sbrk 只能用其一,不要混合使用,会出错;
    
    每个进程都有 4G 的虚拟内存空间,虚拟内存地址只是一个数字,并没有和实际的物理内存相关联。
    所谓内存分配与释放,其本质就是建立或取消虚拟内存和物理内存间的映射关系。

    范例:mmap.c
        #include <stdio.h>
        #include <unistd.h>
        #include <sys/mman.h>
        #define MAX_TEXT 256
        int mian () {
            char* psz = (char*)mmap (NULL,        // 返回为地址,需要强制类型转换
                MAX_TEXT * sizeof (char),        // 分配大小
                PROT_READ | PROT_WRITE,            // 可读可写
                MAP_PRIVATE | MAP_ANONYMOUS,    // 不用文件
                0, 0);
            if (psz == MAP_FAILED) {            // if (psz == -1)
                perror ("mmap");
                return -1;
            }
            sprintf (psz, "hello wrod");
            printf ("%s\n", psz);
            printf ("%p\n", psz);
            
            if (munmap (psz, MAX_TEXT * sizeof (char)) == -1) {
                perror ("munmap");
                return -1;
            }
        }

    作业:实现一个基于顺序表的堆栈类模板,
    其数据缓冲区内存可根据数据元素的多少自动增减,
    但不得使用标准C的内存分配与释放函数。

    代码:stack.cpp

    思考:该堆栈模板是否适用于类类型的数据元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值