Windows内存管理的方法
内存管理是操作系统中的重要部分,两三句话恐怕谁也说不清楚吧~~
当程序运行时需要从内存中读出这段程序的代码。代码的位置必须在物理内存中才能被运行,由于现在的操作系统中有非常多的程序运行着,内存中不能够完全放下,所以引出了虚拟内存的概念。把那些不常用的程序片断就放入虚拟内存,当需要用到它的时候在load入主存(物理内存)中。这个就是内存管理所要做的事。内存管理还有另外一件事需要做:计算 程序片段 在主存中的物理位置,以便CPU调度。
内存管理有块式管理,页式管理,段式和段页式管理。现在常用段页式管理
块式管理:把主存分为一大块、一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程 序片断load入主存,就算所需的程序片度只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但时易于管理。
页式管理:把主存分为一页一页的,每一页的空间要比一块一块的空间小很多,显然这种方法的空间利用率要比块式管理高很多。
段式管理:把主存分为一段一段的,每一段的空间又要比一页一页的空间小很多,这种方法在空间利用率上又比页式管理高很多,但是也有另外一个缺点。一个程序片断可能会被分为几十段,这样很多时间就会被浪费在计算每一段的物理地址上(计算机最耗时间的大家都知道是I/O吧)。
段页式管理:结合了段式管理和页式管理的优点。把主存分为若干页,每一页又分为若干段。好处就很明显,不用我多说了吧。
各种内存管理都有它自己的方法来计算出程序片断在主存中的物理地址,其实都很相似。
Question
(0.)编译链接
在VC或VS上编写完代码,点击编译按钮准备生成exe文件时,编译器做了两步工作:
第一步,将每个.cpp(.c)和相应的.h文件编译成obj文件,一个obj文件就是一个编译单元[语法错误];
第二步,将工程中所有的obj文件进行LINK,生成最终.exe文件[重复定义变量]。
如果你在编译单元中 引用的外部变量 没有在整个工程中 任何一个地方定义 的话,那么即使它在编译时可以通过,在连接时也会报错,因为程序在内存中找不到这个变量 。 函数或变量可以声明多次,但定义只能有一次。
(1.)野指针是什么?
野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针。
访问了一段 已经删除的内存空间,或者访问了一段 没有访问、读写权限的内存空间。
(2.)sizeof()相关
使用sizeof()确定变量长度(单位是字节),变量长度指 声明变量时 预留内存大小。
bool-1byte,char-1byte,int-4byte,float-4byte,double-4byte。
C++ 11引入“指定整数宽度(单位是bit)”的类型,如int8_t存储 8位有符号整数。
(3)指针和引用的区别
1.指针有自己的一块空间,而引用只是一个别名;
2.使用sizeof看一个指针的大小是4,而引用是被引用对象的大小;
3.指针可以被初始化为NULL,而引用必须被初始化 且必须是一个已有对象的引用;
4.作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;
5.可以有const指针,但是没有const引用;
6.指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;
7.指针可以有多级指针(**p),而引用只有一级;
8.指针和引用使用++运算符的意义不一样;
9.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。
(4)如何判断内存泄漏
内存泄漏通常是由于调用了malloc/new等内存申请的操作,但是缺少了对应的 free/delete。为了判断内存是否泄露,我们一方面可以使用linux环境下的内存泄漏检查工具Valgrind,另一方面我们在写代码时可以添加内存申请和释放的统计,统计当前申请和释放的内存是否一致,以此来判断内存是否泄露。
(5)include头文件的顺序、双引号和尖括号的区别
对于include的头文件来说,如果在文件a.h中 声明一个在文件b.h中定义的变量,而不引用b.h。那么要在a.cpp文件中 引用b.h文件,并且要“先引用b.h,后引用a.h”,否则会报变量类型未声明错误。
双引号和尖括号的区别:编译器预处理阶段查找头文件的路径不一样。
(6)extern和static
extern作用一:当它与"C"一起连用时,如extern “C” void fun(int a, int b);,则编译器在编译fun这个函数名时按C的规则去翻译相应的函数名而不是C++的。
extern作用二:当它不与"C"在一起修饰变量或函数时,如在头文件中,extern int g_nNum;,它的作用就是声明函数或变量的作用范围的关键字,其声明的函数和变量可以在本编译单元或其他编译单元中使用。
即B编译单元要引用 A编译单元中 定义的全局变量或函数时,B编译单元只要包含A编译单元的头文件即可,在编译阶段,B编译单元虽然找不到该函数或变量,但它不会报错,它会在链接时从A编译单元生成的目标代码中找到此函数。
(7)fork函数
多进程,成功调用fork( )会创建一个新的进程,它几乎与调用fork( )的进程一模一样,这两个进程都会继续运行。
(8)静态数据&动态数组
静态数组int arr [],在编译阶段包含的元素个数及占用的内存里就是固定的。预留内存量sizeof(int)*元素个数。
动态数组vector,在执行阶段确定的数组。
C++可以模拟多维数组,但存储数组的内存是一维的,内存延一个方向延伸。
字符数组,字符串结束符‘\0’告诉编译器字符串到此为止,但该字符不会改变数组的长度。
char sayhello[] = {'H','e','l','l','o','\0','Y'};
cout << strlen(sayhello) << endl; //5
cout << sizeof(sayhello) << endl; //7
(9)逻辑运算符、位运算符
逻辑运算符:!、&&、||
&& 运算,当前面为0时,后面则不进行计算,发生短路
|| 运算,当前面为1时,后面则不进行计算,发生短路
位运算符:~、&、|、^
0x0F = 0×16+15 = 15 = 0000 1111
bitset<4> bitset1; //无参构造,长度为4,默认每一位为0
bitset<8> bitset2(12); //长度为8,二进制保存,前面用0补充
string s = "100101";
bitset<10> bitset3(s); //长度为10,前面用0补充
char s2[] = "10101";
bitset<13> bitset4(s2); //长度为13,前面用0补充
cout << bitset1 << endl; //0000
cout << bitset2 << endl; //00001100
cout << bitset3 << endl; //0000100101
cout << bitset4 << endl; //0000000010101
(10)重载
函数重载:名称和返回值类型相同,但参数不同的函数。
(11)内联函数
内联函数被调用的地方就地展开函数。将函数声明为内联,会导致代码急剧膨胀。
(12)返回值是auto的函数
对于依赖返回类型自动推断的函数,必须先定义(即实现)再调用。因为调用函数时,编译器必须知道其返回类型。如果该函数有多条return语句,则必须确保根据它们推断的返回类型都相同。
(13)函数栈
函数调用意味着微处理器跳转到属于被调用函数的下一条指令处执行。执行完函数的指令后,将返回到最初离开的地方。为了实现这种逻辑,编译器将函数调用转换为一条供微处理器执行的CALL指令,该指令指出了接下来要获取的指令所在地址,该地址归函数所有。遇到CALL指令时,微处理器将调用函数后 将要执行的指令的位置 保存到栈中,再跳转到CALL指令包含的内存单元处。该内存单元包含属于函数的指令。微处理器执行它们,直到到达RET语句(与函数里return语句对应的微处理器代码)。RET语句导致微处理器从栈中弹出 执行CALL指令时 存储的地址。该地址包含调用函数中接下来要执行的语句的位置。这样,微处理器将返回到调用函数,从离开的地方继续执行。
(14)函数指针
函数指针是指向函数的指针变量。函数指针本身首先是一个指针变量,该指针变量指向一个具体的函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。
char fun(char *a) { //参数指针-将内存空间传递给函数
return *a;
}
char a = 'a';
char* p = &a;
char (*pf)(char *p); //*pf是指向函数char fun(char *p)的指针
pf = fun;
cout << pf(p) << endl;
// O:a
(15)new、delete、malloc、free关系;delete与 delete []的区别
new和delete对应、malloc和free对应。new和delete是c++语言的标准库函数,而malloc和free是c++的运算符。它们都可用于申请动态内存和释放内存,区别在对非内部数据类型的对象而言,malloc和free无法满足动态对象的要求(因为对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数)
delete只会调用一次析构函数,而delete[]会调用每一个成员的析构函数。delete和new对应,delete []和new []对应。