程序是存在文件中的(硬盘),一个运行的程序是需要加载到内存中的,加载到内存中的程序叫进程。
STL --> 内存是自动分配和回收
|
C++ --> new/delete,会调用malloc和free
|
C语言 --> malloc/free
|
Unix/Linux系统调用 -> brk/sbrk
|
内存映射 -> mmap
| (用户层)
------------------------------------
kmalloc/vmalloc (内核层)
|
get_free_page()
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
class Sample{
public:
int x;
Sample(){cout<<"Sample()"<<endl;}
~Sample(){cout<<"~Sample()"<<endl;}
void show(){cout<<"x="<< x <<endl;}
void* operator new(size_t sz){
cout<<"new("<<sz<<")"<<endl;
return malloc(sz);
}
void* operator new[](size_t sz){
cout<<"new("<<sz<<")"<<endl;
return malloc(sz);
}
void operator delete(void* p){
cout << "delete"<<endl;
free(p);
}
void operator delete[](void* p){
cout << "delete"<<endl;
free(p);
}
};
int main(){
Sample* s = new Sample;
s->x = 100;
s->show();
delete s;
printf("---------------------\n");
Sample* s2 = (Sample*)malloc(sizeof(Sample));
s2->x = 200;
s2->show();
free(s2);
printf("-----------------------\n");
Sample* s3 = new Sample[5];
delete[] s3;
printf("-------------------\n");
Sample s4[5];
}
虚拟内存技术:
在Unix/Linux中,用虚拟内存技术管理内存。
每个进程都有0-4G的虚拟地址空间,虚拟地址必须映射到物理内存后才能使用,否则引发段错误。所谓的内存分配其实就是映射物理内存。程序员所接触到只能是虚拟内存,无法直接接触物理内存。
0-3G是用户层使用的,叫用户空间,3G-4G是内核使用的,叫内核空间。用户程序只能直接访问用户空间,无法直接访问内核空间,但可以通过Unix/Linux系统提供的系统调用(一系列的函数)进入内核空间。内存管理的最小单位是一个内存页,大小4096(4k)。
进程内存的分区:
代码区:程序代码(函数)放入代码区,只读区
全局区:保存全局变量/static变量
BSS段: 保存未初始化的全局变量
注:全局区和bss段 在main执行之前都会分配内存,但bss段会自动清0。
栈区:保存局部变量(非malloc分配),包括函数的参数,内存分配和回收自动进行
堆区:也叫自由区,内存管理是程序员执行。new/malloc分配的内存。
注:在代码区附近有一个只读变量区,一般和代码区合并。
#include <stdio.h>
#include <stdlib.h>
int i1 = 10;//全局
int i2 = 20;//全局
int i3;//bss
static int i4 = 40;//全局
const int i5 = 50;//代码(只读变量)
void fa(int i6){//栈
int i7 = 70;//栈
static int i8 = 80;//全局
const int i9 = 90;//栈
int *pi = malloc(4);//堆
char * str1 = "abc";//只读变量
char str2[] = "abc";//栈
printf("&i6=%p\n",&i6);
printf("&i7=%p\n",&i7);
printf("&i8=%p\n",&i8);
printf("&i9=%p\n",&i9);
printf("pi=%p\n",pi);
printf("str1=%p\n",str1);
printf("str2=%p\n",str2);
}
int main(){
printf("&i1=%p\n",&i1);
printf("&i2=%p\n",&i2);
printf("&i3=%p\n",&i3);
printf("&i4=%p\n",&i4);
printf("&i5=%p\n",&i5);
fa(100);
printf("fa=%p\n",fa);//函数地址,代码区
while(1);
return 0;
}
段错误的原因:
1 虚拟内存没有映射物理内存就使用
2 操作某些没有权限的内存区域(修改只读区)
3 释放内存时缺少必要的附加信息
malloc的特点:
1 申请小内存时(不足33个内存页),默认映射33个内存页。用完后再申请不一再给33个内存页。
2 申请大内存时,映射稍多一点的内存页。
3 申请内存时,会额外多分配一点内存,用于记录相关信息。
注:malloc不一定映射新的物理内存,free也不一定真正释放物理内存。
进程之间同样的虚拟地址对应 不同的物理内存。
#include <string.h>
#include <stdio.h>int main(){
char * str1 = "abcd";
char str2[] = "abcd";
str1 = "1234";//改变str1的指向,而不是改"abcd"
//str1[0] = '1';//改只读区 段错误
//str2 = "1234"; //错误
strcpy(str2,"1234");
printf("str1=%s\n",str1);
printf("str2=%s\n",str2);
int size = getpagesize();
printf("size=%d\n",size);
}
#include <stdio.h>
#include <stdlib.h>
int main(){
int a,b,c,d;
printf("a=%p,b=%p,c=%p,d=%p\n",&a,&b,&c,&d);
int * pi = malloc(4);//分配33个内存页
*pi = 100;
//*(pi+1) = 200;//内存不稳定(数据无保障)
int *pi2 = malloc(4);//内存稳定(数据有保障)
int *pi3 = malloc(4);//malloc会附加一些内存
int *pi4 = malloc(4);//附加内存记录信息
printf("*pi=%d\n",*pi);
//printf("*(pi+1)=%d\n",*(pi+1));
printf("pid=%d\n",getpid());//取进程id
printf("pi=%p\n",pi);
printf("pi2=%p\n",pi2);
printf("pi3=%p\n",pi3);
printf("pi4=%p\n",pi4);
*(pi+1) = 100; *(pi+2)=200;*(pi+3)=300;
free(pi);
free(pi2);
free(pi3); free(pi4);
while(1);
}
#include <stdio.h>
#include <stdlib.h>
int main(){
int x = 100;
int *p = malloc(4);
*p = 200;
printf("&x=%p\n",&x);
printf("p=%p\n",p);
while(1);
}
#include <stdio.h>
int main(){
int *p = 0x94fe008;//0xbf808e9c;
printf("*p=%d\n",*p);
}
#include <stdio.h>
#include <stdlib.h>
int main(){
int *pi = malloc(4096*2);
int *pi2 = malloc(4096*30);
int *pi3 = malloc(4097);
printf("pi=%p\n",pi);
printf("pi2=%p\n",pi2);
printf("pi3=%p\n",pi3);
printf("pid=%d\n",getpid());
sleep(20);
printf("sleep over\n");
free(pi); free(pi2); free(pi3);
while(1);
}
brk/sbrk 函数是系统提供分配内存/回收内存的。
一般用sbrk分配内存,用brk回收。
void* sbrk(int size)
size == 0 取当前位置
size >0 分配内存,并返回之前的位置
size <0 释放内存
int brk(void* p)
p就是指定位置,返回-1代表错误
brk/sbrk 底层需要维护一个位置
#include <stdio.h>
#include <unistd.h>
int main(){
void* p = sbrk(0);
void* p1 = sbrk(4);//以一个内存页作为映射的单位
void* p2 = sbrk(4);
void* p3 = sbrk(4);void* p4 = sbrk(4);
printf("p=%p,p1=%p,p2=%p,p3=%p,p4=%p\n",
p,p1,p2,p3,p4);
printf("pid=%d\n",getpid());
sbrk(-4);sbrk(-8);//释放内存
p = sbrk(0);
printf("p=%p\n",p);
sleep(20);
printf("all free\n");
sbrk(-4);
while(1);
}
#include <unistd.h>
#include <string.h>
int main(){
void* p = sbrk(0);//取位置
int r = brk(p+4);//分配了4个
if(r == -1) perror("brk");
brk(p+8);//再次分配4个
brk(p+4);//释放了4个
int *pi = p;
*pi = 100;
char *str = sbrk(0);
brk(str+10);
strcpy(str,"abcdef");
printf("int=%d,str=%s\n",*pi,str);
brk(p);//全部释放
}//练习:改良代码,用sbrk分配内存,用brk释放
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(){
void *p = sbrk(4);//分配内存,同时返回起始位置
int *pi = p;
*pi = 100;
char * str = sbrk(10);
strcpy(str,"abcdef");
printf("int=%d,str=%s\n",*pi,str);
brk(p);
}//一般情况下,sbrk分配内存,brk释放内存
mmap用于映射虚拟内存地址,可能用物理内存/文件
void* mmap(void* addr,size_t size,int prot,
int flags,int fd,off_t offset)
返回虚拟内存的首地址;addr可以指定首地址,一般NULL交给内核指定。size是映射大小,以内存页为单位。prot权限,flags指定配置,fd文件描述符,offset偏移量。
munmap 解除映射
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
int main(){
void* p = mmap(0,//内核指定首地址
4,//大小4个字节,会扩充到1页4k
PROT_READ|PROT_WRITE,//|经常用于连接多个选项
MAP_PRIVATE|MAP_ANONYMOUS,//映射物理内存
0,0 //文件描述符和文件偏移量
);
if(p == MAP_FAILED){perror("mmap");return -1; }
int *pi = p;
int i;
for(i=0;i<50;i++){
pi[i] = i+100;
}
char * str = p+250;
strcpy(str,"abcdefgh");
char* ch = p;
for(i=0;i<300;i++){
if(i%10==0) printf("\n");
printf("%d ",ch[i]);
}
munmap(p,4);//取消映射
}
系统调用 就是 内核对外的接口。