内存是操作系统的核心部分,所以我们非常有必要了解内存的分配机制。在DOS下,访问内存的指针是用段地址:偏移量来表示,所有程序共用一个内存空间,由低向高分配内存空间,所以任何程序都可以随便修改内存中的数据,包括不属于自己程序的内存空间和中断向量表。而且所有程序被局限在1M的基本内存(Base Memory)中,不能直接访问扩充内存。对于Windows下的程序来说,它所访问的内存地址不再是真实的。而是虚拟的、独立的全平坦式(flat)的内存空间。如一个32位的程序可访问内存地址是0x00000000到0xffffffff(4G),指针不再存储段地址。所谓独立,指的是当进程A加载到内存0x400000处时,进程B加载到内存的地址时一样是0x400000,两者的地址空间是相互独立的。程序访问内存地址时,由Windows自动换算为真实的内存地址。这样,程序A是无法直接访问程序B的内存空间的,也就提高了系统的稳定性。 其实,在这4G的内存地址中,我们能使用的内存地址不到一半。我们的程序是无法分配到0x80000000以上的内存地址的,这2G的内存地址都是被系统占用,是只读的。如果一个应用程序企图对大于0x80000000的内存地址进行写操作,则会产生"非法操作"。例如: char *pstr; if(pstr!=NULL) *ptr='a'; 这里声明了一个指针,在没有对其初始化时,它指向0xCCCCCCCC(这里很奇怪不知为什么所有没初始化的变量,其值都为0xCCCCCCCC),所以不为空,因而执行 *pstr=“a” 。由于0xCCCCCCCC在0x80000000以上,产生了一个“非法操作”,这就是有的朋友常犯的错误。 在Win2000中,用户可以使用0x80000000以上的1G地址(在Boot.ini 中加上参数 /3G) 不过似乎对于普通程序员来说,是没有什么意义的。 Win9x内存中与Win2000的内存分配略有不同。在Win98下,应用程序可使用的内存是从0x400000开始,而在Win2000下,是从0x100000开始。可是加载应用程序时,都是加载到0x400000 。试试这段代码: BYTE buff[0x300]; DWORD dwRead; ReadProccessMemory(GetCurrentProcess(),(PVOID)0x400000,buff,0x300,&dwRead); 请把Buff输出到文件中,你会发现正好是你的应用程序的文件头。 Windows的内存分配是以页(page)为单位(与磁盘的扇区很相似),每一页的大小因CPU而异。我们常用的PC机是x86构架,每页为4096字节,可以用GetSystemInfo()函数得到页面大小。一个内存块由多个页面组成。每个页都有一个访问属性: PAGE_NOACCESS 不可访问 PAGE_READONLY 只读 PAGE_READWRITE 读写 PAGE_EXECUTE 可执行 …… 我们可以用: DWORD VirtualQuery( LPCVOID lpAddress, // 内存地址 PMEMORY_BASIC_INFORMATION lpBuffer, // 内存地址信息 DWORD dwLength // lpBuffer 的大小,也就是结构 MEMORY_BASIC_INFORMATION 的大小 ); 来得到某个内存块的状态。使用 VirtualQueryEx() 还可以访问其它进程的内存分配信息。要注意的是这两个函数是得到一个内存块的信息,而不是一个页的信息。在Win9x,当对一个低于0x80000000地址进行写操作时,即使此内存地址为只读(PAGE_READONLY)或是此内存根本就没有分配,Win9x一般也不会报错,这样使得Win9x兼容性好了些,但也使Win9x非常脆弱。在Win2000下则严格遵守页面属性规则,如果你企图对非写属性的内存地址进行操作时,会立刻被中断。这就是为什么很多程序能在Win9x下运行,而转到Win2000下就出现非法操作的原因。虽然我们不能直接读取其它进程的内存空间,但Windows也还是给我们留下了一个后门: ReadProcessMemory()、WriteProcessMemory()。这两个函数可以直接对其它进程的内存进行读写操作。也许你会发现,如果Windows同时启动两次一个相同的应用程序(如同时启动两个Visual Studio),第二次起动的速度明显快于第一次。这是因为,虽然每个程序的内存空间是独立的,但它们有一部分内存是只读的,是可以几个进程共享包括放在0x80000000内存空间以上的系统服务。Windows提供这个功能可以很有效的节省内存空间,提高系统的效率。谈到这里,我们又有问题了,如何在两个进程之间的正常的进行数据交换,而不是用ReadProcessMemory的WriteProcessMemory去强行读写呢? 常用的方法是用MapViewOfFile()把文件映射到内存中,其它进程可以用OpenViewOfFile()来访问,或是用Windows的DDE数据传输协议。还有一种访问是放到DLL中,通过: #pragma data_seg("AllUser") ....//数据 #pragma data_seg() #pragma comment(linker,"/SECTION:AllUser,SRW") // SRW 为共享读写 把某个内存块设置为公用内存,那么就可象使用全局变量一样访问此内存空间。 如果有这样一个问题,下面的Struct 在内存中占用多大的字节。 struct{ char a[2]; int b; }abc; 也许你会这样回答,在Windows下Char占一个字节,Int占4个字节,所以是6个字节。其实如果我们用Sizeof得到的大小是8个字节。因为在Windows下对结构变量分配内存空间缺省大小是8的倍数。它不再象DOS一样按本身结构的大小分配,这是由于CPU访问对齐的数据速度要比访问非对齐数据速度要快好几倍。如果你的数据量太大,希望按真实大小对结构分配空间,则可以设置 Project => Setting => C/C++ => Category => Code Generation => struct member alignment 将其设为1就行了。分配非内存对齐的变量同样也会影响执行速度,如: char *pstr=(char *)malloc(5); DWORD *pdw=(DWORD *)(pstr+1); PDW指针指向的就是一个奇数地址,对PDW操作时就很费时。 小结:对于一个VC程序员来说,很多程序的错误都出现在内存分配上,如未经检查就使用一个空指针等,如果能很好的管理、分配你程序的内存空间,你的程序将会少去一些不必要的错误。 |