1 内存管理
1 .1 Linux对内存的描述
/proc/$(pid)/ proc存放进程运行时候所有的信息(包括内存结构)
进程查看: ps aue (all user effective)-->获取进程ID
查看内存结构:cd proc >> ps aue >>cd PID >> cat maps
结论:任何程序的内存空间分成4个基本部分 1代码区 2全局栈区 3堆 4局部栈
ldd main
/lib/ld-linux.so.2 main这个程序把我们自己程序加载到内存中,把指针指向首地址,分配全栈
1.2 变量与内存空间的关系
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int add(int a,int b)
{
return a+b;
}
int a1 = 1;
static int a2 = 2;
const int a3 = 3;
main()
{
auto int b4 = 4;
static int b5 = 5;
const int b6 = 6;
int *p = malloc(4);
printf("%d\n",getpid());
printf("a1:%p\n",&a1);
printf("a2:%p\n",&a2);
printf("a3:%p\n",&a3);
printf("b4:%p\n",&b4);
printf("b5:%p\n",&b5);
printf("b6:%p\n",&b6);
printf("p:%p\n",p);
printf("p1:%p\n",add);
printf("p2:%p\n",main);
while(1);
}
1 代码区:add main a3(const全局)
2 全局区:a1(全局) a2(全局静态) b5(局部静态)
3 堆区 :p(malloc分配)
4 栈区 :b4(局部变量/自动) b6(const局部变量)
1.3 malloc工作原理
malloc使用一个数据结构(链表)维护分配空间
链表的构成:分配的空间/上一个空间数据/下一个空间/空间大小等信息.
对malloc分配的空间不要越界访问.因为容易破坏后台维护结构.导致malloc/free/calloc/realloc不正常工作.
#include<stdio.h>
#include<stdlib.h>
int main()
{
int *p1 =(int*)malloc(4);
int *p2 =(int*)malloc(4);
int *p3 =(int*)malloc(4);
int *n1 = new int ;
int *n2 = new int ;
int *n3 = new int ;
printf("malloc add:\n");
printf("%p\n",p1);
printf("%p\n",p2);
printf("%p\n",p3);
printf("new add:\n");
printf("%p\n",n1);
printf("%p\n",n2);
printf("%p\n",n3);
return 0;
}
1.4 new与malloc的关系
new的实现使用的是malloc(<stdlib.h>)来实现的.
区别:
new&&malloc:new使用malloc后,还要初始化空间. 基本类型,直接初始化成默认值. UDT类型,调用指定的构造器
delete&&free:delete调用free实现. delete负责调用析构器.然后在调用free
new&&new[]:new只调用一个构造器初始化. new[]循环对每个区域调用构造器.
同理delete 与delete[]
1.5 函数调用栈空间的分配与释放
函数执行时使用栈空间作为自己的临时栈,3种方式决定编译器清空栈的方式
1.函数执行的时候有自己的临时栈(在局部栈里). [临时数据/形参的存储]
C++成员函数拥有两个栈空间,一个是函数本身的栈空间,另一个是对象的栈空间
因为函数本身是在对象的栈空间里运行 ,C里函数只有一个栈空间
2.函数的参数就在临时栈中.如果函数传递实参.则用来初始化临时参数变量. 不管哪种传递,实质传递的都是值,一般变量传的是数据,而指针传的是地址值
3.通过寄存器返回值.(使用返回值返回数据)
4.通过参数返回值.(参数必须是指针), 指针指向的区域必须事先分配.
5.如果参数返回指针.参数就是双指针.
#include<stdio.h>
int add(int a, int b)
{
return a+b;
}
main()
{
typedef int(*p)(int);
p pf= (p)add;
int r = pf(20);
printf("r = %d\n",r);
return 0;
}
说明:如果一个函数形参只有两个的话,它会在自己的临时栈里开辟两个空间;如果给它传递的实参是一个,则另一个形参就不会被初始化,最后会得到一个不确定的数字 如果给它传递的实参多余两个,则多余的部分不会被使用,不会影响结果
__stdcall __cdecl __fastcall
1.决定函数栈压栈的参数顺序.
2.决定函数栈的清空方式:不管是哪种方式,都是在编译时编译器负责产生清空栈的函数代码
_stdcall表示每个调用者负责清空自己调用的函数的临时栈
_fastcall函数自己在返回前自己清空临时栈,然后返回值退出
_cdecl表示所有调用者只有一个清空函数来负责清空所有被调用函数的临时栈
3.决定了函数的名字转换方式.
C++中的重载实质是在编译的时候悄悄的把函数名改了,一般是_Z函数名长度,参数类型缩写;在window下,函数名前加_stdcall
1.6 far near huge指针
near 16位
far 32位
huge综合
Win32统一采用far指针,这三个指针在window下会碰到
2 虚拟内存
1.每个程序的开始地址0x80084000
2.程序中使用的地址不是物理地址,而是逻辑地址(虚拟内存). 逻辑地址仅仅是编号.编号使用int 4字节整数表示. 4294967296=4G 每个程序提供了4G的访问能力
2.1 内存映射
逻辑地址与物理地址关联过程. 虚拟内存的提出:禁止用户直接访问物理存储设备.一个程序不能访问另外一个程序的地址指向的空间,有助于系统的稳定.
虚拟地址与物理地址映射的时候有一个基本单位:至少会映射4K/1000-->内存页.
2.2 内存访问
区别两种错误的内存访问方式:
段错误:无效访问. 那段内存没有映射
非法访问:比如malloc分配的空间之外的空间可以访问,但访问非法类,似于虚表指针
#include<stdio.h>
#include<stdlib.h>
main()
{
int *p = malloc(0); //完成映射?多少页?
*p = 9999; //可以访问但非法
printf("*p = %d\n",*p);
}
3 虚拟内存的分配
栈:编译器自动生成代码维护
堆:地址是否映射,映射的空间是否被管理
内存中堆是程序员自己负责释放,自己维护
局部栈里的东西不管是对象栈还是函数栈,怎么样被释放取决于是哪种清空函数栈的方式,编译器产生额外的代码来维护,而函数前面的修饰符则影响着编译器编译方式,栈的清空方式
3.1 brk/sbrk 内存映射函数
分配释放内存:
int brk(void *end);//分配空间,释放空间
void *sbrk(int size);//返回空间地址
sbrk与brk后台系统维护一个指针,指针默认是null.
调用sbrk,判定指针是否是0:
是:得到大块空闲空间的首地址初始化指针,同时把指针+size;
否:返回指针,并且把指针位置+size
调用brk,参数使用绝对地址指针。
#include<stdio.h>
#include<unistd.h>
main()
{
int *p = sbrk(0); //返回空闲空间首页地址
printf("首页地址 p = %p\n",p);
//*p = 800; 段错误
int *p1 = sbrk(4);//先返回分配空间指针,再将指针+4
int *p2 = sbrk(0);//返回分配后的地址,当前尾地址
printf("分配的内存大小:p2 - p1 = %p - %p = %d\n",p2,p1,(int)p2-(int)p1);
int *p3 = sbrk(-4);//先将指针返回,再-4
printf("释放的内存大小:p3 - sbrk(0) = %p - %p = %d\n",p3,sbrk(0),(int)p3-(int)sbrk(0));
//brk分配内存的方法
int *p4 = sbrk(0);
printf("分配前:sbrk(0) = %p\n",sbrk(0));
brk(p4+4); //参数为绝对地址,分配:当前指针+size;释放:首地址指针
printf("分配后:sbrk(0) = %p\n",sbrk(0));
*p3 = 256;
brk(p4); //释放到指针p3所指位置
printf("释放后:sbrk(0) = %p\n",sbrk(0));
*p3 = 128; //段错误
}
3.2 异常处理
int brk(void*) /void *sbrk(int);
如果成功.brk返回0 sbrk返回指针 ;失败 brk返回-1 sbrk返回(void*)-1
Unix函数错误,修改内部变量:errno
#include<unistd.h>
#include<errno.h> //errno
#include<string.h> //strerror
extern int errno; //外部变量声明
main()
{
int n = 0;
void *p = 0;
while(1)
{
void *p = sbrk(n++);
if(p == (void*)-1)
{
printf("n = %d\n",n);
printf("Memory:%m\n");
perror("hello");
printf("::%s\n",strerror(errno));
break;
}
}
}
3.3 mmap(分配)/unmap(释放)
void *mmap( //#include <sys/mman.h>
void *start, //指定映射的虚拟地址 0由系统指定开始位置)
size_t length, //映射空间大小 pagesize倍数
int prot, //映射权限
int flags, //映射方式
int fd, //文件描述符号
offset_t off); //文件中的映射开始位置(必须是pagesize的倍数)
映射权限:PROT_NONE | PROT_READ PROT_WRITE PROT_EXEC
映射方式:
内存映射:匿名映射。 MAP_ANONYMOUS
文件映射:映射到某个文件 ,只有文件映射最后两个参数有效。
MAP_SHARED MAP_PRIVATE(二选一)
#include<sys/mman.h>
#include<stdio.h>
//#include<stdlib.h>
#include<unistd.h>
main()
{
int *p = mmap(
NULL,
getpagesize(),
PROT_WRITE, //可写则可读,但可读不可写
MAP_SHARED|MAP_ANONYMOUS,
0,0
);
*p = 44;
*(p+1) = 45;
*(p+2) = 46;
printf("%d\n",p[2]);
munmap(p,4096); //释放 4k = 4096
}
内存管理方法的选择:
智能指针(指针池)
STL
new
malloc (小而多数据)
brk/sbrk (同类型的大块数据,动态移动指针)
mmap/munmap(控制内存访问/使用文件映射/控制内存共享)
参考:http://blog.chinaunix.net/uid-25968088-id-3782681.html
http://blog.csdn.net/gaoxin12345679/article/details/21300089