您的位置:首页 > 编程语言 > C语言/C++

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

2017-02-16 16:32 351 查看
内存管理

一错误处理
通过函数的返回值表示错误
范例badc

范例nullc

范例failc

代码errorc

通过errno表示错误
范例errnoc

范例iferrc

二环境变量
环境表

环境变量函数
范例envc

三内存管理

四进程映像

范例mapsc

五虚拟内存

范例vmc

范例crashc

范例pagec

六内存管理APIs
增量方式分配虚拟内存

修改虚拟内存块末尾地址
范例brkcmallocc

创建虚拟内存到物理内存或文件的映射
范例mmapc

内存管理

一、错误处理

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   | … |

| 层 |   …   |   …   |  …   |

+—-+——–+—————————-+———-+

四、进程映像

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

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

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

进程在内存空间中的布局就是进程映像。

从低地址到高地址依次为:

代码区(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                         ^

        +————————————+

                   (+)

五、虚拟内存

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

用户程序中使用的都是虚拟地址空间中的地址,

永远无法直接访问实际物理内存地址。

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

虚拟内存一方面保护了操作系统的安全,

另一方面允许应用程序,

使用比实际物理内存更大的地址空间。

图示:vm.png

4G进程地址空间分成两部分:

[0, 3G)为用户空间,

如某栈变量的地址0xbfc7fba0=3,217,554,336,约3G;

[3G, 4G)为内核空间。

用户空间中的代码,

不能直接访问内核空间中的代码和数据,

但可以通过系统调用进入内核态,

间接地与系统内核交互。

图示:kernel.png



7. 对内存的越权访问,

或试图访问没有映射到物理内存的虚拟内存,

将导致段错误。

用户空间对应进程,进程一切换,用户空间即随之变化。

内核空间由操作系统内核管理,不会随进程切换而改变。

内核空间由内核根据独立且唯一的页表init_mm.pgd

进行内存映射,而用户空间的页表则每个进程一份。

每个进程的内存空间完全独立。

不同进程之间交换虚拟内存地址是毫无意义的。

范例: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;
}


标准库内部通过一个双向链表,

管理在堆中动态分配的内存。

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;
}


虚拟内存到物理内存的映射以页(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

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;
}


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

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

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

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的虚拟内存空间,

虚拟内存地址只是一个数字,

并没有和实际的物理内存将关联。

所谓内存分配与释放,

其本质就是建立或取消虚拟内存和物理内存间的映射关系。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 内存管理 uc c++