内存可以说是C++中很重要很重要的一部分了,我相信这也是C++能够排在编程语言前列的一个原因,因为有了内存管理,使得C++在处理一些底层得程序时,能表现得更加得优秀。
1.进程内存应该分为几部分(内存管理)
对于这一个问题的答案本人觉得不唯一,网上大部分人说内存因该分为五部分,分别为数据段、代码段、BSS段、堆区、栈区,本人在阅读了大量的材料之后,发现有人将内存分为6部分,感觉也是非常合理的。第六部分为文本映射区。
名称 | 存储内容 | ||||
数据段 | 已初始化的全局变量和静态变量 | ||||
代码段 |
| ||||
BSS段 | 未初始化以及被初始化为0的全局变量和静态变量 | ||||
堆区 | 调用new/malloc来进行动态内存分配,同时需要调用free/delete来进行释放 | ||||
栈区 | 存储函数的返回地址、参数、局部变量、返回值 | ||||
映射区 | 存储动态链接库以及调用mmap函数进行的文件映射 |
其中数据段、BSS段以及代码段都属于静态区域。32位模式下进程内存的经典布局如下图所示:
映射区域一般从TASK_SIZE/3的地方开始,在不同的机器上,映射区域的开始位置可能不同。
64位模式下进程内存的布局如下图所示:
在64位下,进程的栈和mmap区域并不是从一个固定的地址开始,每次启动时的值都不一样。Linux环境下可以设置randomize_va_space的值来更改这个属性。
2.内存分配
1.Heap操作的相关函数(brk和sbrk)
Heap操作函数主要有两个,brk()为系统调用,sbrk()为库函数。系统调用提供最小的功能,而函数会提供比较复杂的功能。malloc
、realloc、calloc三个函数就是通过调用sbrk()函数将数据段的下界移动,sbrk函数在内核的管理下将虚拟地址空间映射到内存供malloc函数族来使用。在内核数据结构mm_struct中有以下几个变量:
unsigned long start_code;//代码段的起始地址
unsigned long end_code;//代码段的结束地址
unsigned long start_data;//数据段的起始地址
unsigned long end_data;//数据段的结束地址
unsigned long start_brk;//堆的起始地址
unsigned long end_brk;//堆的结束地址
unsigned long brk;//堆的当前最后地址,也就是动态内存分配的其实地址
malloc函数族是通过系统调用brk实现的,brk()是一个非常简单的系统调用,通过改变mm_struct中的brk的值来实现的。
brk和sbrk的定义如下:
# include<unistd.h>
int brk(void*addr);
void *sbrk(intptr_t increment);
当sbrk()的参数increment为0时,sbrk返回的是进程的当前brk值,increment为整数时扩展brk值,为负数时收缩brk值。
2.Mmap映射区操作的相关函数(mmap和munmap)
mmap()函数是将一个文件或者其他对象映射进内存,实现文件的磁盘地址和进程虚拟地址空间的一段虚拟地址的一一对应关系。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一
页不被使用的空间将会被清零。munmap执行相反的操作,删除特定地址区域的对象映射。当映射解除之后,再对原来的地址进行访问将会产生段错误,两个函数的定义如下:
# include<sys/mman.h>
void *mmap(void*addr,size_t length,int port,int flgs,int fd,off_t offet);
int munmap(void *addr,size_t length);
3.内存常见错误
1.指针没有指向合法的内存
2.为指针分配的内存太小
3.内存分配成功后,未进行初始化
4.内存越界
5.内存泄漏
6.内存已经被释放,但是继续通过指针来使用
4.常见动态内存错误
1.对NULL指针进行解引用操作
2.对分哦欸的内存操作时越过边界
3.释放并非动态分配的内存
4.释放一块动态分配的内存的一部分
5.动态内存释放之后继续使用