由于博客换了,之前的文章不想搬过来,所以准备重写一遍之前的博客,正好之前的内容比较杂乱,这次用系统一点的方式来写,争取新手也能看得懂。
目录
-
指针和虚拟内存
-
PE文件结构
-
汇编和函数栈帧
简介
本篇将会说明C语言函数调用均为按值传递。指针也是值。虚拟内存的概念,指针和虚拟内存的关系。
指针
常规的指针定义就不说明了。在C语言中,指针和其他变量一样,通过标识符定义后,标识符就占据了一块内存。
int a = 0;int*p;此时p是否有空间呢,有的,因为指针本身需要存放地址,而地址也是值。
说到地址,我们可以把内存当作一个数组Memory[index],对p解引用就是在取Memory[p]的值。
那么可以想象,所谓的按指针传递:
void swap(int*a,int*b){ *a+=*b; *b=*a-*b; *a=*a-*b;}类似上面这个代码,实际上传递的是a的地址的值,b的地址的值,这种地址必须用解引用表示取地址值。
再看下面这个例子:
void test(int a,int b,int*isAbiggerThanB){ if(a>b) { *isAbiggerThanB = 1; return; } *isAbiggerThanB = 0; return;}
int main1(void){ int* flag; test(1,2,flag);}
int main2(void){ int flag; test(1,2,&flag);}上面两种调用方式哪一个正确呢,很明显,是第二个。那么这里不是要传一个指针进去吗?为什么不直接传指针,而是传一个值的地址呢?这就是我刚刚说的,传指针实际就是传值,只不过这个值表示了一个地址。main1中,传入了值吗?没有,因为flag只是一个指针,它指向的地址都没有初始化,如果改成int*flag=a,那就可以了。而main2直接传入值的地址,反而保证了test函数对地址写值时的正确性。注意:传指针不是想写入指针,而是写入指针指向的地址。
虚拟内存
这个概念有两个关键点,一个是虚拟,一个是内存。
先看内存:最开始的计算机,是没有外存的,也就是没有硬盘存放数据,需要执行代码时,直接输入放入内存执行即可。因此内存最重要的属性就是和CPU直接交互。那么它们怎么交互呢?通过总线交互,有数据总线,控制总线,地址总线等等。每次CPU需要读存数据,就将内存地址通过地址总线发给内存控制器,然后将固定长度的值的数据放到/让内存放到数据总线上,进行数据流动,而这个固定值,就是计算机的位数(也是总线长度),比如现在常见的64位计算机,就是可以一次读取64位,一个longlong长度的数据。因此,绝大部分指令都会基于这个数来操作。那么问题来了。刚才说了,计算机需要将对应的内存地址放到地址总线,那么现在可以算算,CPU可以访问多大的内存:16EB,也就是1600万tb。如果是32位机,则是:4G,这也是为什么32位机应用的内存大小为4g。好了,对于32位来说感觉好像内存有点小,对于64位来说,内存也太大了,有没有什么方法来解决这个问题呢?现在的目标是:让64位机可以在内存容量少的情况下访问更多内存,让32位机在应用多的情况下,尽可能一直运行,而不会内存耗尽。
有的。虚拟内存就是来解决这个问题的。简单来说:虚拟内存就是一个控制器(MMU)+内存+外存。当内存中程序多时,通过换页操作(swap page)将暂时不需要的内存中的数据置入硬盘,再在需要的时候拿回来。那么这样即加大了CPU寻址利用率(部分寻址空间可映射到辅存)又让每个程序可以分到更多内存(根据运行时动态调整哪些内存数据在内存或者在辅存)
因为它不完全是内存,而是基于内外存,所以叫虚拟?实际上,还因为一点:每个应用程序的虚拟内存是相互独立的。即每个程序都能有4GB的虚拟内存空间(这里是32位下,64位下也没有16EB那么多。。。)每个虚拟地址都会经过转换得到真实物理内存地址因此每个程序就不需要在意其他进程所在的位置了(自然形成隔离)。
指针和虚拟内存
实际上,程序中的指针使用的内存空间就是虚拟内存,在64位上,可以索引0x7fff ffff ffff的地址,其上为内核空间。因此一个指针的长度为8字节(不然装不下)。可以通过指针任意索引
总结
本节说明了指针和虚拟内存的关系。重点说明了虚拟内存的概念和指针的实现。在下下节,会从汇编的角度看指针。
部分信息可能已经过时









