c /c++复习笔记 第二天

5 篇文章 0 订阅
4 篇文章 0 订阅

内存管理

一、错误处理


1. 通过函数的返回值表示错误

~~~~~~~~~~~~~~~~~~~~~~~

1) 返回合法值表示成功,返回非法值表示失败。

范例:bad.c

#include <stdio.h>
#include <limits.h>
// 获取文件大小
// 成功返回文件的字节数,失败返回-1
long fsize (const char* path) {
    FILE* fp = fopen (path, "r");
    if (! fp)
        return -1;
    fseek (fp, 0, SEEK_END);
    long size = ftell (fp);
    fclose (fp);
    return size;
}
int main (void) {
    printf ("文件路径:");
    char path[PATH_MAX+1];
    scanf ("%s", path);
    long size = fsize (path);
    if (size == -1) {
        printf ("文件大小获取失败!\n");
        return -1;
    }
    printf ("文件大小:%ld字节\n", size);
    return 0;
}

2) 返回有效指针表示成功,
返回空指针(NULL/0xFFFFFFFF)表示失败。

范例:null.c

#include <stdio.h>
#include <string.h>
// 求字符串的最大值
// 成功返回参数字符串中的最大值,失败返回NULL
const char* strmax (const char* a, const char* b) {
    return a && b ? (strcmp (a, b) > 0 ? a : b) : NULL;
}
int main (void) {
//  const char* max = strmax ("hello", "world");
    const char* max = strmax ("hello", NULL);
    if (! max) {
        printf ("求字符串最大值失败!\n");
        return -1;
    }
    printf ("字符串最大值:%s\n", max);
    return 0;
}

3) 返回0表示成功,返回-1表示失败,
不输出数据或通过指针/引用型参数输出数据。

范例:fail.c

#include <stdio.h>
// 整数取模
// 成功返回0,失败返回-1
int intmod (int a, int b, int* mod) {
    if (b == 0)
        return -1;
    *mod = a % b;
    return 0;
}
int main (void) {
    printf ("两个整数:");
    int a, b;
    scanf ("%d%d", &a, &b);
    int mod;
    if (intmod (a, b, &mod) == -1) {
        printf ("整数取模失败!\n");
        return -1;
    }
    printf ("整数取模:%d\n", mod);
    return 0;
}

4) 永远成功,如:printf()。

练习:实现四个函数
slen() - 求字符串的长度,若为空指针,则报错。
scpy() - 字符串拷贝,考虑缓冲区溢出,
成功返回目标缓冲区地址,
目标缓冲区无效时报错。
intmin() - 求两个整数的最小值,若二者相等,则报错。
intave() - 求两个整数的平均值,考虑求和溢出,
该函数不会失败。

代码:error.c

#include <stdio.h>
#define min(a,b) ((a)<(b)?(a):(b))
// 求字符串长度
// 成功返回字符串长度,失败返回(size_t)-1
size_t slen (const char* s) {
    if (! s)
        return -1;
    size_t len;
    for (len = 0; s[len]; ++len);
    return len;
}
// 字符串拷贝
// 成功返回目标字符串,失败返回NULL
char* scpy (char* dst, size_t size, const char* src) {
    if (! dst || ! size || ! src)
        return NULL;
    if (dst != src) {
        size_t i, count = min (size - 1, slen (src));
        if (dst > src)
            for (i = 0; i < count; ++i)
                dst[count-1-i] = src[count-1-i];
        else
            for (i = 0; i < count; ++i)
                dst[i] = src[i];
        dst[count] = '\0';
    }
    return dst;
}
// 求整数最小值,两个数相等视作失败
// 成功返回0,失败返回-1
int intmin (int a, int b, int* min) {
    if (a == b)
        return -1;
    *min = a < b ? a : b;
    return 0;
}
// 求整数平均值
// 永远成功,不会失败
int intave (int a, int b) {
    return (a & b) + ((a ^ b) >> 1);
}
int main (void) {
#ifndef ERROR
    size_t len = slen ("Hello, World !");
#else
    size_t len = slen (NULL);
#endif
    if (len == -1)
        printf ("求字符串长度失败!\n");
    else
        printf ("字符串长度:%u\n", len);
    char dst[5];
#ifndef ERROR
    if (! scpy (dst, sizeof (dst) / sizeof (dst[0]),
        "0123456789"))
#else
    if (! scpy (NULL, 0, "0123456789"))
#endif
        printf ("字符串拷贝失败!\n");
    else
        printf ("字符串副本:%s\n", dst);
    int min;
#ifndef ERROR
    if (intmin (-1, 0, &min) == -1)
#else
    if (intmin (-1, -1, &min) == -1)
#endif
        printf ("求整数最小值失败!\n");
    else
        printf ("整数最小值:%d\n", min);
    printf ("整数平均值:%d\n", intave (1234, 5678));
    return 0;
}

2. 通过errno表示错误

~~~~~~~~~~~~~~~~

#include <errno.h>

1) 根据errno得到错误编号。

2) 将errno转换为有意义的字符串:

#include <string.h>
char* strerror (int errnum);

#include <stdio.h>
void perror (const char* s);

printf ("%m");

范例:errno.c

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main (void) {
    FILE* fp = fopen ("none", "r");
    if (! fp) {
        printf ("fopen出错了:%d\n", errno);
        printf ("fopen出错了:%s\n", strerror (errno));
        perror ("fopen出错了");
        printf ("fopen出错了:%m\n");
        return -1;
    }
    // ...
    fclose (fp);
    return 0;
}

3) errno在函数执行成功的情况下不会被修改,
因此不能以errno非零,作为发生错误判断依据。

范例:iferr.c

#include <stdio.h>
#include <errno.h>
void foo (void) {
    fopen ("none", "r");
}
int main (void) {
    foo ();
    FILE* fp = fopen ("/etc/passwd", "r");
    if (errno) {
        perror ("fopen");
        printf ("%p\n", fp);
        return -1;
    }
    // ...
    fclose (fp);
    return 0;
}

4) errno是一个全局变量,其值随时可能发生变化。

二、环境变量


1. 环境表

~~~~~

1) 每个程序都会接收到一张环境表,
是一个以NULL指针结尾的字符指针数组。

2) 全局变量environ保存环境表的起始地址。

environ-->---: HOME=/root
environ-->---: SHELL=/bin/bash
environ-->---: PATH=/bin:/usr/bin:...:.
environ-->---: |----|-----|

图示:env_list.bmp

2. 环境变量函数

~~~~~~~~~~~

#include <stdlib.h>

环境变量:name=value

getenv - 根据name获得value。

putenv - 以name=value的形式设置环境变量,
name不存在就添加,存在就覆盖其value。

setenv - 根据name设置value,注意最后一个参数表示,
若name已存在是否覆盖其value。

unsetenv - 删除环境变量。

clearenv - 清空环境变量,environ==NULL。

范例:env.c

#include <stdio.h>
#include <stdlib.h>
void printenv (void) {
    printf ("---- 环境变量 ----\n");
    extern char** environ;
    char** env;
    for (env = environ; env && *env; ++env)
        printf ("%s\n", *env);
    printf ("------------------\n");
}
int main (void) {
    char env[256];
    const char* name="MYNAME";
    // 添加环境变量
    sprintf (env, "%s=roselind", name);
    putenv (env);
    printf ("%s=%s\n", name, getenv (name));
    // 修改环境变量
    sprintf (env, "%s=bjarne", name);
    putenv (env);
    printf ("%s=%s\n", name, getenv (name));
    // 不存在就添加,存在不覆盖
    setenv (name, "roselind", 0);
    printf ("%s=%s\n", name, getenv (name));
    // 不存在就添加,存在就覆盖
    setenv (name, "roselind", 1);
    printf ("%s=%s\n", name, getenv (name));
    printenv ();
    // 删除环境变量
    unsetenv (name);
    printenv ();
    // 清空环境变量
    clearenv ();
    printenv ();
    return 0;
}

三、内存管理


+—-+——–+—————————-+———-+
| 用 | 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   | … |
| 层 |   …   |   …   |  …   |
+—-+——–+—————————-+———-+

四、进程映像


  1. 程序是保存在磁盘上的可执行文件。

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

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

  4. 进程在内存空间中的布局就是进程映像。
    从低地址到高地址依次为:

代码区(text):可执行指令、字面值常量、 具有常属性的全局和静态局部变量。只读。

数据区(data):初始化的全局和静态局部变量。

BSS区:未初始化的全局和静态局部变量。 进程一经加载此区即被清0。

数据区和BSS区有时被合称为全局区或静态区。

堆区(heap):动态内存分配。从低地址向高地址扩展。

栈区(stack):非静态局部变量, 包括函数的参数和返回值。从高地址向低地址扩展。

堆区和栈区之间存在一块间隙, 一方面为堆和栈的增长预留空间, 同时共享库、共享内存等亦位于此。

命令行参数与环境区:命令行参数和环境变量。

图示:maps.bmp

范例:maps.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
const int const_global = 10; // 常全局变量
int init_global = 10; // 初始化全局变量
int uninit_global; // 未初始化全局变量
int main (int argc, char* argv[]) {
    const static int const_static = 10; // 常静态变量
    static int init_static = 10; // 初始化静态变量
    static int uninit_static; // 未初始化静态变量
    const int const_local = 10; // 常局部变量
    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 ("---- 命令行参数与环境变量 ---- <高>\n");
    printf ("         环境变量:%p\n", environ);
    printf ("       命令行参数:%p\n", argv);
    printf ("-------------- 栈 ------------\n");
    printf ("       常局部变量:%p\n", &const_local);
    printf ("       前局部变量:%p\n", &prev_local);
    printf ("       后局部变量:%p\n", &next_local);
    printf ("-------------- 堆 ------------\n");
    printf ("         后堆变量:%p\n", next_heap);
    printf ("         前堆变量:%p\n", prev_heap);
    printf ("------------- BSS ------------\n");
    printf (" 未初始化全局变量:%p\n", &uninit_global);
    printf (" 未初始化静态变量:%p\n", &uninit_static);
    printf ("------------ 数据 ------------\n");
    printf ("   初始化静态变量:%p\n", &init_static);
    printf ("   初始化全局变量:%p\n", &init_global);
    printf ("------------ 代码 ------------\n");
    printf ("       常静态变量:%p\n", &const_static);
    printf ("       字面值常量:%p\n", literal);
    printf ("       常全局变量:%p\n", &const_global);
    printf ("             函数:%p\n", main);
    printf ("------------------------------ <低>\n");
    printf ("查看/proc/%u/maps,按<回车>退出...",
        getpid ());
    getchar ();
    return 0;
}

比对/proc//maps

》#size a.out

text data   bss        dec      hex      filename
2628 268   28        2924     b6c      a.out
 |       |      |           |         |         |
+———+———+  (10)    +———+———+ (16)
        V                         ^
        +————————————+
                   (+)

五、虚拟内存


  1. 每个进程都有各自互独立的4G字节虚拟地址空间。

  2. 用户程序中使用的都是虚拟地址空间中的地址,
    永远无法直接访问实际物理内存地址。

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

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

图示:vm.png

  1. 4G进程地址空间分成两部分:
    [0, 3G)为用户空间,
    如某栈变量的地址0xbfc7fba0=3,217,554,336,约3G;
    [3G, 4G)为内核空间。

  2. 用户空间中的代码,
    不能直接访问内核空间中的代码和数据,
    但可以通过系统调用进入内核态,
    间接地与系统内核交互。

图示:kernel.png
这里写图片描述
7. 对内存的越权访问,
或试图访问没有映射到物理内存的虚拟内存,
将导致段错误。

  1. 用户空间对应进程,进程一切换,用户空间即随之变化。
    内核空间由操作系统内核管理,不会随进程切换而改变。
    内核空间由内核根据独立且唯一的页表init_mm.pgd
    进行内存映射,而用户空间的页表则每个进程一份。

  2. 每个进程的内存空间完全独立。
    不同进程之间交换虚拟内存地址是毫无意义的。

范例:vm.c

#include <stdio.h>
int g_vm = 0;
int main (void) {
    printf ("&g_vm = %p\n", &g_vm);
    printf ("整数:");
    scanf ("%d%*c", &g_vm);
    printf ("启动另一个进程,输入不同的数据,"
        "按<回车>继续...");
    getchar ();
    printf ("g_vm = %d\n", g_vm);
    return 0;
}
  1. 标准库内部通过一个双向链表,
    管理在堆中动态分配的内存。
    malloc函数分配内存时会附加若干(通常是12个)字节,
    存放控制信息。
    该信息一旦被意外损坏,可能在后续操作中引发异常。

范例:crash.c

#include <stdio.h>
#include <stdlib.h>
int main (void) {
    int* p1 = malloc (sizeof (int));
    int* p2 = malloc (sizeof (int));
    printf ("%p, %p\n", p1, p2);
    free (p2);
    p1[3] = 0;
    free (p1);
    return 0;
}
  1. 虚拟内存到物理内存的映射以页(4K=4096字节)为单位。
    通过malloc函数首次分配内存,至少映射33页。
    即使通过free函数释放掉全部内存,
    最初的33页仍然保留。

图示:address_space.png

#include <unistd.h>

int getpagesize (void);

返回内存页的字节数。

范例:page.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void presskey (void) {
    printf ("查看/proc/%u/maps,按<回车>继续...",
        getpid ());
    getchar ();
}
int main (void) {
    printf ("1页 = %d字节\n", getpagesize ());
    char* pc = malloc (sizeof (char));
    printf ("pc = %p\n", pc);
    presskey ();
    free (pc);
    printf ("free(%p)\n", pc);
    presskey ();
    pc = malloc (sizeof (char) * 100);
    printf ("pc = %p\n", pc);
    presskey ();
    setbuf (stdout, NULL);
    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 后续错误 不稳定 段错误

六、内存管理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、malloc.c

  1. brk.c
#include <stdio.h>
#include <unistd.h>
void presskey (void) {
    printf ("查看/proc/%u/maps,按<回车>继续...",
        getpid ());
    getchar ();
}
int main (void) {
    void* p1 = sbrk (4); // RXXX ---- ---- ---- -
    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 (4); // XXXX XXXX XXXX RXXX -
    printf ("p4 = %p\n", p4);
    void* p5 = sbrk (0); // XXXX XXXX XXXX XXXX R
    printf ("p5 = %p\n", p5);
    int* pn = (int*)p1;
    pn[0] = 0;
    pn[1] = 1;
    pn[2] = 2;
    pn[3] = 3;
    pn[1023] = 1023;
    printf ("%d, %d, %d, %d, %d\n",
        pn[0], pn[1], pn[2], pn[3], pn[1023]);
//  pn[1024] = 1024;
    void* p6 = sbrk (-8); // XXXX XXXX ---- ---- R
    printf ("p6 = %p\n", p6);
    void* p7 = sbrk (-8); // ---- ---- R--- ---- -
    printf ("p7 = %p\n", p7);
    printf ("--------\n");
    int page = getpagesize ();
    printf ("%p\n", sbrk (page));
    presskey ();
    printf ("%p\n", sbrk (1));
    presskey ();
    printf ("%p\n", sbrk (-1));
    presskey ();
    printf ("%p\n", sbrk (-page));
    presskey ();
    printf ("--------\n");
    p1 = sbrk (0);     // R--- ---- ---- ---- -
    printf ("p1 = %p\n", p1);
    brk (p2 = p1 + 4); // XXXX S--- ---- ---- -
    printf ("p2 = %p\n", p2);
    brk (p3 = p2 + 4); // XXXX XXXX S--- ---- -
    printf ("p3 = %p\n", p3);
    brk (p4 = p3 + 4); // XXXX XXXX XXXX S--- -
    printf ("p4 = %p\n", p4);
    brk (p5 = p4 + 4); // XXXX XXXX XXXX XXXX S
    printf ("p5 = %p\n", p5);
    pn = (int*)p1;
    pn[0] = 0;
    pn[1] = 1;
    pn[2] = 2;
    pn[3] = 3;
    pn[1023] = 1023;
    printf ("%d, %d, %d, %d, %d\n",
        pn[0], pn[1], pn[2], pn[3], pn[1023]);
//  pn[1024] = 1024;
    brk (p3); // XXXX XXXX S--- ---- -
    brk (p1); // S--- ---- ---- ---- -
//  pn[0] = 0;
    printf ("--------\n");
    void* begin = sbrk (sizeof (int));
    if ((int)begin == -1) {
        perror ("sbrk");
        return -1;
    }
    pn = (int*)begin;
    *pn = 1234;
    double* pd = (double*)sbrk (sizeof (double));
    if ((int)pd == -1) {
        perror ("sbrk");
        return -1;
    }
    *pd = 3.14;
    char* psz = (char*)sbrk (256 * sizeof (char));
    if ((int)psz == -1) {
        perror ("sbrk");
        return -1;
    }
    sprintf (psz, "Hello, World !");
    printf ("%d, %g, %s\n", *pn, *pd, psz);
    if (brk (begin) == -1) {
        perror ("brk");
        return -1;
    }
    return 0;
}
  1. malloc.c
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
// 内存控制块
typedef struct mem_control_block {
    bool                      free; // 自由标志
    struct mem_control_block* prev; // 前块指针
    size_t                    size; // 本块大小
}   MCB;
MCB* g_top = NULL; // 栈顶指针
// 分配内存
void* my_malloc (size_t size) {
    MCB* mcb;
    for (mcb = g_top; mcb; mcb = mcb->prev)
        if (mcb->free && mcb->size >= size)
            break;
    if (! mcb) {
        mcb = sbrk (sizeof (MCB) + size);
        if (mcb == (void*)-1)
            return NULL;
        mcb->prev = g_top;
        mcb->size = size;
        g_top = mcb;
    }
    mcb->free = false;
    return mcb + 1;
}
// 释放内存
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 (mcb != g_top) {
        g_top = mcb;
        brk ((void*)mcb + sizeof (mcb) + mcb->size);
    }
}
int main (void) {
    int* pa[10];
    size_t size = sizeof (pa) / sizeof (pa[0]), i, j;
    for (i = 0; i < size; ++i) {
        if (! (pa[i] = my_malloc((i+1)*sizeof(int)))) {
            perror ("my_malloc");
            return -1;
        }
        for (j = 0; j <= i; ++j)
            pa[i][j] = j;
    }
    for (i = 0; i < size; ++i) {
        for (j = 0; j <= i; ++j)
            printf ("%d ", pa[i][j]);
        printf ("\n");
    }
    /*
    for (i = 0; i < size; ++i)
        my_free (pa[i]);
    */
    for (;;) {
        my_free (pa[--i]);
        if (! i)
            break;
    }
    return 0;
}

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

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#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 - 锁定映射区域,保证其不被置换。

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

int munmap (
void* start, // 映射区内存起始地址
size_t length, // 字节长度,自动按页(4K)对齐
);

成功返回0,失败返回-1。

范例:mmap.c

#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#define MAX_TEXT 256
int main (void) {
    char* psz = (char*)mmap (
        NULL,
        MAX_TEXT * sizeof (char),
        PROT_READ | PROT_WRITE,
        MAP_PRIVATE | MAP_ANONYMOUS,
        0,
        0);
    if (psz == MAP_FAILED) {
        perror ("mmap");
        return -1;
    }
    sprintf (psz, "Hello, Memory !");
    printf ("%s\n", psz);
    printf ("psz = %p\n", psz);
    printf ("查看/proc/%u/maps,按<回车>退出...",
        getpid ());
    getchar ();
    if (munmap (psz, MAX_TEXT * sizeof (char)) == -1) {
        perror ("munmap");
        return -1;
    }
    return 0;
}

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

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

malloc底层维护一个双向链表和必要的控制信息,
不可越界访问,所分配内存位于堆中,底层调用brk/sbrk。

每个进程都有4G的虚拟内存空间,
虚拟内存地址只是一个数字,
并没有和实际的物理内存将关联。
所谓内存分配与释放,
其本质就是建立或取消虚拟内存和物理内存间的映射关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值