看流星社区

 找回密码
 注册账号
查看: 3475|回复: 1

在看雪找到一篇不错的文章,希望对找游戏函数有帮助!!!!

[复制链接]

该用户从未签到

发表于 2011-3-14 14:38:46 | 显示全部楼层 |阅读模式
堆和栈的区别  
一、预备知识—程序的内存分配  
一个由c/C++编译的程序占用的内存分为以下几个部分  
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。  
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。  
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻
的另一块区域。 - 程序结束后有系统释放   
4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放  
5、程序代码区—存放函数体的二进制代码。  
二、例子程序   
这是一个前辈写的,非常详细   
//main.cpp  
int a = 0; 全局初始化区   
char *p1; 全局未初始化区   
main()  
{  
int b; 栈   
char s[] = "abc"; 栈   
char *p2; 栈   
char *p3 = "123456"; 123456\0在常量区,p3在栈上。   
static int c =0; 全局(静态)初始化区   
p1 = (char *)malloc(10);  
p2 = (char *)malloc(20);  
分配得来得10和20字节的区域就在堆区。   
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。   
}  


二、堆和二、堆和栈栈的理的理论论知知识识      
2.1申请方式   
stack:  
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间   
heap:  
需要程序员自己申请,并指明大小,在c中malloc函数   
如p1 = (char *)malloc(10);   
在C++中用new运算符   
如p2 = (char *)malloc(10);   
但是注意p1、p2本身是在栈中的。   


2.2  
申请后系统的响应   
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。   
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,   
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中
的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的
那部分重新放入空闲链表中。   

2.3申请大小的限制   
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2
M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。   
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大
小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。   


2.4申请效率的比较:   
栈由系统自动分配,速度较快。但程序员是无法控制的。   
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.   
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵
活。   

2.5堆和栈中的存储内容   
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往
左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。   
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。   
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。   

2.6存取效率的比较   

char s1[] = "aaaaaaaaaaaaaaa";  
char *s2 = "bbbbbbbbbbbbbbbbb";  
aaaaaaaaaaa是在运行时刻赋值的;   
而bbbbbbbbbbb是在编译时就确定的;   
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。   
比如:   
#include  
void main()  
{  
char a = 1;  
char c[] = "1234567890";  
char *p ="1234567890";  
a = c[1];  
a = p[1];  
return;  
}  
对应的汇编代码   
10: a = c[1];  
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]  
0040106A 88 4D FC mov byte ptr [ebp-4],cl  
11: a = p[1];  
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]  
00401070 8A 42 01 mov al,byte ptr [edx+1]  
00401073 88 45 FC mov byte ptr [ebp-4],al  
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。   


2.7小结:   
堆和栈的区别可以用如下的比喻来看出:   
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是
自由度小。   
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。   


windows进程中的内存结构  


在阅读本文之前,如果你连堆栈是什么多不知道的话,请先阅读文章后面的基础知识。   

接触过编程的人都知道,高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢?程序又是如何使用这些变量的呢?下面就会对此进行深入的讨
论。下文中的C语言代码如没有特别声明,默认都使用VC编译的release版。   

首先,来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方
式。先来看下面这段代码:   

#include <stdio.h>  

int g1=0, g2=0, g3=0;  

int main()  
{  
static int s1=0, s2=0, s3=0;  
int v1=0, v2=0, v3=0;  

//打印出各个变量的内存地址   

printf("0x%08x\n",&v1); //打印各本地变量的内存地址   
printf("0x%08x\n",&v2);  
printf("0x%08x\n\n",&v3);  
printf("0x%08x\n",&g1); //打印各全局变量的内存地址   
printf("0x%08x\n",&g2);  
printf("0x%08x\n\n",&g3);  
printf("0x%08x\n",&s1); //打印各静态变量的内存地址   
printf("0x%08x\n",&s2);  
printf("0x%08x\n\n",&s3);  
return 0;  
}  

编译后的执行结果是:   

0x0012ff78  
0x0012ff7c  
0x0012ff80  

0x004068d0  
0x004068d4  
0x004068d8  

0x004068dc  
0x004068e0  
0x004068e4  

输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量,g1,g2,g3是全局变量,s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的,但是本地变量和全局变量
分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内
存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈(stack)”和“堆(heap)”是两种不同的动态数据区,栈是一
种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”
地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。   

  
├———————┤低端内存区域   
││ ……  …… ││      
├———————┤   
│ 动态数据区 │   
├├——————————————┤┤      
│ …… │   
├├——————————————┤┤      
│ 代码区 │   
├├——————————————┤┤      
│ 静态数据区 │   
├├——————————————┤┤      
│ …… │   
├├——————————————┤┤高端高端内内存存区区域 域     


堆堆栈栈是一是一个个先先进进后出的后出的数数据据结结构构,,栈顶栈顶地址地址总总是小于等于是小于等于栈栈的基地址。我的基地址。我们们可以先了解一下函可以先了解一下函数数调调用的用的过过程,以便程,以便对对堆堆栈栈在程序中的作用有更深入的了解。不同的在程序中的作用有更深入的了解。不同的语语言有不同的言有不同的
函数调用规定,这些因素有参数的压入规则和堆栈的平衡。windows API的调用规则和ANSI C的函数调用规则是不一样的,前者由被调函数调整堆栈,后者由调用者调整堆栈。
两两者通者通过过““____stdcallstdcall””和和““____cdeclcdecl””前前缀缀区区分。先看下面分。先看下面这这段代段代码码: :     

#include <stdio.h> #include <stdio.h>   

void __stdcall func(int param1,int param2,int param3) void __stdcall func(int param1,int param2,int param3)   
{  
int var1=param1; int var1=param1;   
int var2=param2;  
int var3=param3;  
printf("0x%08x\n",printf("0x%08x\n",&para;&para;m1); //m1); //打印出各打印出各个个变变量的量的内内存地址 存地址     
printf("0x%08x\n",&para;m2);   
printf("0x%08x\n\n",printf("0x%08x\n\n",&para;&para;m3); m3);     
printf("0x%08x\n",&var1);  
printf("0x%08x\n",&var2); printf("0x%08x\n",&var2);   
printf("0x%08x\n\n",&var3);  
return; return;   
}  
  
int main()  
{  
func(1,2,3); func(1,2,3);   
return 0;  
} }   

编译编译后的后的执执行行结结果是: 果是:     

0x0012ff78 0x0012ff78   
0x0012ff7c  
0x0012ff80 0x0012ff80   

0x0012ff68  
0x0012ff6c 0x0012ff6c   
0x0012ff70  
  

├├——————————————┤┤<<——函函数数执执行行时时的的栈顶栈顶((ESPESP)、低端)、低端内内存存区区域 域     
│ …… │   
├├——————————————┤┤      
│ var 1 │   
├├——————————————┤┤      
│ var 2 │   
├———————┤   
││  var 3 var 3 ││      
├———————┤   
││  RET RET ││      
├———————┤<—“__cdecl”函数返回后的栈顶(ESP)   
││  parameter 1 parameter 1 ││      
├———————┤   
││  parameter 2 parameter 2 ││      
├———————┤   
││  parameter 3 parameter 3 ││      
├———————┤<—“__stdcall”函数返回后的栈顶(ESP)   
│ …… │   
├├——————————————┤┤<<——栈栈底(基地址 底(基地址 EBPEBP)、高端)、高端内内存存区区域 域     

  
上图就是函数调用过程中堆栈的样子了。首先,三个参数以从又到左的次序压入堆栈,先压“param3”,再压“param2”,最后压入“param1”;然后压入函数的返回地址
((RET)RET),接,接着着跳跳转转到函到函数数地址接地址接着着执执行(行(这这里要里要补补充一充一点点,介,介绍绍UNIXUNIX下的下的缓缓冲冲溢出原理的文章中都提到在溢出原理的文章中都提到在压压入入RETRET后,后,继续压继续压入入当当前前EBPEBP,然后用,然后用当当前前ESPESP代替代替EBPEBP。然而,有。然而,有
一篇介绍windows下函数调用的文章中说,在windows下的函数调用也有这一步骤,但根据我的实际调试,并未发现这一步,这还可以从param3和var1之间只有4字节的间隙这
点点看出看出来来);第三);第三步步,,将将栈顶栈顶((ESP)ESP)减减去一去一个数个数,,为为本地本地变变量分配量分配内内存空存空间间,上例中是,上例中是减减去去1212字字节节((ESP=ESPESP=ESP--3*43*4,,每每个个intint变变量占用量占用44个个字字节节));接;接着着就初始化本地就初始化本地变变量的量的内内存空存空
间。由于“__stdcall”调用由被调函数调整堆栈,所以在函数返回前要恢复堆栈,先回收本地变量占用的内存(ESP=ESP+3*4),然后取出返回地址,填入EIP寄存器,回收先前
压压入入参数参数占用的占用的内内存存((ESP=ESP+3*4)ESP=ESP+3*4),,继续执继续执行行调调用者的代用者的代码码。。参参见见下列下列汇编汇编代代码码: :     

;--------------func 函数的汇编代码-------------------   
  
:00401000 83EC0C sub esp, 0000000C //创建本地变量的内存空间   
:00401003 8B442410 mov eax, dword ptr [esp+10] :00401003 8B442410 mov eax, dword ptr [esp+10]   
:00401007 8B4C2414 mov ecx, dword ptr [esp+14]  
:0040100B 8B542418 mov edx, dword ptr [esp+18] :0040100B 8B542418 mov edx, dword ptr [esp+18]   
:0040100F 89442400 mov dword ptr [esp], eax  
:00401013 8D442410 lea eax, dword ptr [esp+10] :00401013 8D442410 lea eax, dword ptr [esp+10]   
:00401017 894C2404 mov dword ptr [esp+04], ecx  
  
……………………(省略若干代码)   

:00401075 83C43C add esp, 0000003C ;:00401075 83C43C add esp, 0000003C ;恢恢复复堆堆栈栈,回收本地,回收本地变变量的量的内内存空存空间间      
:00401078 C3 ret 000C ;函数返回,恢复参数占用的内存空间   
;;如果是如果是““____cdeclcdecl””的的话话,,这这里是里是““retret””,堆,堆栈栈将将由由调调用者恢用者恢复复      

;;--------------------------------------函函数数结结束束--------------------------------------------------      

  
;--------------主程序调用func函数的代码--------------   
  
:00401080 6A03 push 00000003 //压入参数param3   
:00401082 6A02 push 00000002 //压入参数param2   
:00401084 6A01 push 00000001 //:00401084 6A01 push 00000001 //压压入入参数参数param1 param1     
:00401086 E875FFFFFF call 00401000 //调用func函数   
;;如果是如果是““____cdeclcdecl””的的话话,,将将在在这这里恢里恢复复堆堆栈栈,,““add esp, 0000000Cadd esp, 0000000C””      

聪聪明的明的读读者看到者看到这这里,差不多就明白里,差不多就明白缓缓冲冲溢出的原理了。先溢出的原理了。先来来看下面的代看下面的代码码: :     

#include <stdio.h> #include <stdio.h>   
#include <string.h>  
  
void __stdcall func()  
{  
char lpBuff[8]="\0"; char lpBuff[8]="\0";   
strcat(lpBuff,"AAAAAAAAAAA");  
return; return;   
}  
  
int main()  
{ {   
func();  
return 0; return 0;   
}  

编译编译后后执执行一下回行一下回怎怎么么样样?哈,?哈,““""0x00414141"0x00414141"指令引用的指令引用的""0x00000000"0x00000000"内内存。存。该该内内存不能存不能为为""read"read"。。””,,““非法操作非法操作””喽喽!!""41"41"就是就是""A"A"的的1616进进制的制的ASCIIASCII码码了,那明了,那明显显就是就是stst
rcat这句出的问题了。"lpBuff"的大小只有8字节,算进结尾的\0,那strcat最多只能写入7个"A",但程序实际写入了11个"A"外加1个\0。再来看看上面那幅图,多出来的4个字节
正好覆正好覆盖盖了了RETRET的所在的的所在的内内存空存空间间,,导导致函致函数数返回到一返回到一个个错误错误的的内内存地址,存地址,执执行了行了错误错误的指令。如果能精心的指令。如果能精心构构造造这这个个字符串,使字符串,使它它分成三部分,前一部分成三部分,前一部份份仅仅仅仅是是填填充的充的无无意意义义数数
据以达到溢出的目的,接着是一个覆盖RET的数据,紧接着是一段shellcode,那只要着个RET地址能指向这段shellcode的第一个指令,那函数返回时就能执行shellcode了。但
是是软软件的不同版本和不同的件的不同版本和不同的运运行行环环境都可能影境都可能影响这响这段段shellcodeshellcode在在内内存中的位置,那存中的位置,那么么要要构构造造这这个个RETRET是十分困是十分困难难的。一般都在的。一般都在RETRET和和shellcodeshellcode之之间间填填充大量的充大量的NOPNOP指令,使得指令,使得
exploit有更强的通用性。   
  

├├——————————————┤┤<<——低端低端内内存存区区域 域     
│ …… │   
├———————┤<—由exploit填入数据的开始   
││  ││      
│ buffer │<—填入无用的数据   
││  ││      
├———————┤   
││  RET RET ││<<——指向指向shellcodeshellcode,或,或NOPNOP指令的范指令的范围围      
├———————┤   
││  NOP NOP ││      
│ …… │<—填入的NOP指令,是RET可指向的范围   
││  NOP NOP ││      
├———————┤   
│ │   
││  shellcode shellcode ││      
│ │   
├├——————————————┤┤<<——由由exploitexploit填填入入数数据的据的结结束 束     
│ …… │   
├├——————————————┤┤<<——高端高端内内存存区区域 域     

  
windows下的动态数据除了可存放在栈中,还可以存放在堆中。了解C++的朋友都知道,C++可以使用new关键字来动态分配内存。来看下面的C++代码:   
  
#include <stdio.h>  
#include <iostream.h>  
#include <windows.h> #include <windows.h>   

void func() void func()   
{  
char *buffer=new char[128]; char *buffer=new char[128];   
char bufflocal[128];  
static char buffstatic[128]; static char buffstatic[128];   
printf("0x%08x\n",buffer); //打印堆中变量的内存地址   
printf("0x%08x\n",bufflocal); //printf("0x%08x\n",bufflocal); //打印本地打印本地变变量的量的内内存地址 存地址     
printf("0x%08x\n",buffstatic); //打印静态变量的内存地址   
}  
  
void main()  
{ {   
func();  
return; return;   
}  
  
程序执行结果为:   
  
0x004107d0  
0x0012ff04  
0x004068c0 0x004068c0   

可以可以发现发现用用newnew关键关键字分配的字分配的内内存即不在存即不在栈栈中,也不在中,也不在静静态态数数据据区区。。VCVC编译编译器是通器是通过过windowswindows下的下的““堆堆((heap)heap)””来来实现实现newnew关键关键字的字的内内存存动态动态分配。在分配。在讲讲““堆堆””之前,先之前,先来来了解了解
一下和“堆”有关的几个API函数:   
  
HeapAlloc 在堆中申请内存空间   
HeapCreate HeapCreate 创创建一建一个个新的堆新的堆对对象 象     
HeapDestroy 销毁一个堆对象   
HeapFree HeapFree 释释放申放申请请的的内内存 存     
HeapWalk 枚举堆对象的所有内存块   
GetProcessHeap 取得进程的默认堆对象   
GetProcessHeaps GetProcessHeaps 取得取得进进程所有的堆程所有的堆对对象 象     
LocalAlloc  
GlobalAlloc GlobalAlloc   

当当进进程初始化程初始化时时,系,系统统会会自自动为进动为进程程创创建一建一个个默默认认堆,堆,这这个个堆默堆默认认所占所占内内存的大小存的大小为为1M1M。堆。堆对对象由系象由系统进统进行管理,行管理,它它在在内内存中以存中以链链式式结结构构存在。通存在。通过过下面的代下面的代码码可以通可以通过过堆堆动动
态申请内存空间:   
  
HANDLE hHeap=GetProcessHeap();  
char *buff=HeapAlloc(hHeap,0,8); char *buff=HeapAlloc(hHeap,0,8);   

其中hHeap是堆对象的句柄,buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢?它的值有什么意义吗?看看下面这段代码吧:   
  
#pragma comment(linker,"/entry:main") //定义程序的入口   
#include <windows.h> #include <windows.h>   

_CRTIMP int (__cdecl *printf)(const char *, ...); //_CRTIMP int (__cdecl *printf)(const char *, ...); //定定义义STLSTL函函数数printf printf     
/*---------------------------------------------------------------------------   
写写到到这这里,我里,我们顺们顺便便来来复复习习一下前面所一下前面所讲讲的知的知识识: :     
(*注)printf函数是C语言的标准函数库中函数,VC的标准函数库由msvcrt.dll模块实现。   
由函由函数数定定义义可可见见,,printfprintf的的参数个数参数个数是可是可变变的,函的,函数内数内部部无无法法预预先知道先知道调调用者用者压压入的入的参数个数参数个数,函,函数数只能通只能通过过分析第一分析第一个参数个参数字符串的格式字符串的格式来来获获得得压压入入参数参数的信息,由于的信息,由于这这里里参参
数的个数是动态的,所以必须由调用者来平衡堆栈,这里便使用了__cdecl调用规则。BTW,Windows系统的API函数基本上是__stdcall调用形式,只有一个API例外,那就是ws
printf,它使用__cdecl调用规则,同printf函数一样,这是由于它的参数个数是可变的缘故。   
------------------------------------------------------------------------------------------------------------------------------------------------------*/ */     
void main()  
{ {   
HANDLE hHeap=GetProcessHeap();  
char *buff=HeapAlloc(hHeap,0,0x10); char *buff=HeapAlloc(hHeap,0,0x10);   
char *buff2=HeapAlloc(hHeap,0,0x10);  
HMODULE hMsvcrt=LoadLibrary("msvcrt.dll"); HMODULE hMsvcrt=LoadLibrary("msvcrt.dll");   
printf=(void *)GetProcAddress(hMsvcrt,"printf");  
printf("0x%08x\n",hHeap); printf("0x%08x\n",hHeap);   
printf("0x%08x\n",buff);  
printf("0x%08x\n\n",buff2);  
} }   

执执行行结结果果为为: :     

0x00130000 0x00130000   
0x00133100  
0x00133118 0x00133118   

hHeaphHeap的的值值怎怎么么和那和那个个buffbuff的的值值那那么么接近接近呢呢?其?其实实hHeaphHeap这这个个句柄就是指向句柄就是指向HEAPHEAP首部的地址。在首部的地址。在进进程的用程的用户户区区存存着着一一个个叫叫PEB(PEB(进进程程环环境境块块))的的结结构构,,这这个个结结构构中存放中存放着着一些有一些有关关
进程的重要信息,其中在PEB首地址偏移0x18处存放的ProcessHeap就是进程默认堆的地址,而偏移0x90处存放了指向进程所有堆的地址列表的指针。windows有很多API都使
用进程的默认堆来存放动态数据,如windows 2000下的所有ANSI版本的函数都是在默认堆中申请内存来转换ANSI字符串到Unicode字符串的。对一个堆的访问是顺序进行的,
同一同一时时刻只能有一刻只能有一个个线线程程访问访问堆中的堆中的数数据,据,当当多多个个线线程同程同时时有有访问访问要求要求时时,只能排,只能排队队等待,等待,这样这样便造成程序便造成程序执执行效率下降。 行效率下降。     

最后最后来来说说说说内内存中的存中的数数据据对齐对齐。所位。所位数数据据对齐对齐,是指,是指数数据所在的据所在的内内存地址必存地址必须须是是该该数数据据长长度的整度的整数数倍,倍,DWORDDWORD数数据的据的内内存起始地址能被存起始地址能被44除除尽尽,,WORDWORD数数据的据的内内存起始地址能存起始地址能
被2除尽,x86 CPU能直接访问对齐的数据,当他试图访问一个未对齐的数据时,会在内部进行一系列的调整,这些调整对于程序来说是透明的,但是会降低运行速度,所以编
译译器在器在编译编译程序程序时时会尽会尽量保量保证证数数据据对齐对齐。同。同样样一段代一段代码码,我,我们们来来看看用看看用VCVC、、DevDev--C++C++和和lcclcc三三个个不同不同编译编译器器编译编译出出来来的程序的的程序的执执行行结结果: 果:     

#include <stdio.h> #include <stdio.h>   

int main() int main()   
{  
int a;  
char b; char b;   
int c;  
printf("0x%08x\n",&a); printf("0x%08x\n",&a);   
printf("0x%08x\n",&b);  
printf("0x%08x\n",&c); printf("0x%08x\n",&c);   
return 0;  
} }   

这这是用是用VCVC编译编译后的后的执执行行结结果: 果:     
0x0012ff7c  
0x0012ff7b  
0x0012ff80 0x0012ff80   
变量在内存中的顺序:b(1字节)-a(4字节)-c(4字节)。   
  
这是用Dev-C++编译后的执行结果:   
0x0022ff7c 0x0022ff7c   
0x0022ff7b  
0x0022ff74 0x0022ff74   
变量在内存中的顺序:c(4字节)-中间相隔3字节-b(占1字节)-a(4字节)。   
  
这是用lcc编译后的执行结果:   
0x0012ff6c  
0x0012ff6b 0x0012ff6b   
0x0012ff64  
变变量在量在内内存中的存中的顺顺序:同上。 序:同上。     

三三个个编译编译器都做到了器都做到了数数据据对齐对齐,但是后,但是后两两个个编译编译器器显显然然没没VCVC““聪聪明明””,,让让一一个个charchar占了占了44字字节节,浪,浪费费内内存哦。 存哦。     

  
基础知识:   
堆堆栈栈是一是一种种简单简单的的数数据据结结构构,是一,是一种种只允只允许许在其一端在其一端进进行行插插入或入或删删除的除的线线性表。允性表。允许许插插入或入或删删除操作的一端除操作的一端称称为栈顶为栈顶,,另另一端一端称称为栈为栈底,底,对对堆堆栈栈的的插插入和入和删删除操作被除操作被称称为为入入栈栈和和
出栈。有一组CPU指令可以实现对进程的内存实现堆栈访问。其中,POP指令实现出栈操作,PUSH指令实现入栈操作。CPU的ESP寄存器存放当前线程的栈顶指针,EBP寄存
器中保存当前线程的栈底指针。CPU的EIP寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执
行。 行。     

  
参考:《Windows下的HEAP溢出及其利用》by: isno   
《《windowswindows核心核心编编程》程》by: Jeffrey Richter by: Jeffrey Richter     

  

摘要: 讨论常见的堆性能问题以及如何防范它们。(共 9 页)  
  
前言  
您您是否是是否是动态动态分配的 分配的 C/C++ C/C++ 对对象忠象忠实实且幸且幸运运的用的用户户??您您是否在模是否在模块间块间的往返通信中的往返通信中频频繁地使用了繁地使用了““自自动动化化””??您您的程序是否因堆分配而的程序是否因堆分配而运运行起行起来来很慢?不很慢?不仅仅仅仅您您遇到遇到这样这样的的问问
题。几乎所有项目迟早都会遇到堆问题。大家都想说,“我的代码真正好,只是堆太慢”。那只是部分正确。更深入理解堆及其用法、以及会发生什么问题,是很有用的。  
  
什么是堆?  
(如果(如果您您已已经经知道什知道什么么是堆,可以跳到是堆,可以跳到““什什么么是常是常见见的堆性能的堆性能问题问题??””部分)部分)   

在程序中,使用堆在程序中,使用堆来来动态动态分配和分配和释释放放对对象。在下列情象。在下列情况况下,下,调调用堆操作: 用堆操作:     

事先不知道程序所需对象的数量和大小。  
  

对对象太大而不象太大而不适适合堆合堆栈栈分配程序。分配程序。   
堆使用了在运行时分配给代码和堆栈的内存之外的部分内存。下图给出了堆分配程序的不同层。  
  

此主此主题题相相关图关图片如下:片如下:   

  
GlobalAlloc/GlobalFree:Microsoft Win32 堆调用,这些调用直接与每个进程的默认堆进行对话。  

LocalAlloc/LocalFreeLocalAlloc/LocalFree::Win32 Win32 堆堆调调用(用(为为了了与与  Microsoft Windows NT Microsoft Windows NT 兼容),兼容),这这些些调调用直接用直接与与每每个个进进程的默程的默认认堆堆进进行行对话对话。。   

COM COM 的 的 IMalloc IMalloc 分配程序(或 分配程序(或 CoTaskMemAlloc / CoTaskMemFreeCoTaskMemAlloc / CoTaskMemFree):函):函数数使用使用每每个个进进程的默程的默认认堆。自堆。自动动化程序使用化程序使用““组组件件对对象模型 象模型 ((COM)COM)””的分配程序,而申的分配程序,而申请请的程序使的程序使
用每个进程堆。  
  
C/C++ 运行时 (CRT) 分配程序:提供了 malloc() 和 free() 以及 new 和 delete 操作符。如 Microsoft Visual Basic 和 Java 等语言也提供了新的操作符并使用GHOFFICE过滤
词语词语收集收集来来代替堆。代替堆。CRT CRT 创创建自己的私有堆,建自己的私有堆,驻驻留在 留在 Win32 Win32 堆的堆的顶顶部。部。   

Windows NT Windows NT 中,中,Win32 Win32 堆是 堆是 Windows NT Windows NT 运运行行时时分配程序周分配程序周围围的薄的薄层层。所有 。所有 API API 转发转发它它们们的的请请求求给给  NTDLLNTDLL。。   

Windows NT 运行时分配程序提供 Windows NT 内的核心堆分配程序。它由具有 128 个大小从 8 到 1,024 字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存
来来保留和提交保留和提交页页。。   

在在图图表的底部是表的底部是““虚虚拟拟内内存分配程序存分配程序””,操作系,操作系统统使用使用它它来来保留和提交保留和提交页页。所有分配程序使用。所有分配程序使用虚虚拟拟内内存存进进行行数数据的存取。据的存取。   

分配和分配和释释放放块块不就那不就那么么简单吗简单吗??为为何花何花费这费这么么长时间长时间??   

堆堆实现实现的注意事的注意事项项   
传统上,操作系统和运行时库是与堆的实现共存的。在一个进程的开始,操作系统创建一个默认堆,叫做“进程堆”。如果没有其他堆可使用,则块的分配使用“进程堆”。语
言言运运行行时时也能在也能在进进程程内内创创建建单单独独的堆。(例如,的堆。(例如,C C 运运行行时创时创建建它它自己的堆。)除自己的堆。)除这这些些专专用的堆外,用的堆外,应应用程序或用程序或许许多已多已载载入的入的动态链动态链接接库库  ((DLL) DLL) 之一可以之一可以创创建和使用建和使用单单独独的堆。的堆。
Win32 提供一整套 API 来创建和使用私有堆。有关堆函数(英文)的详尽指导,请参见 MSDN。  

当当应应用程序或 用程序或 DLL DLL 创创建私有堆建私有堆时时,,这这些堆存在于些堆存在于进进程空程空间间,,并并且在且在进进程程内内是可是可访问访问的。的。从从给给定堆分配的定堆分配的数数据据将将在同一在同一个个堆上堆上释释放。(不能放。(不能从从一一个个堆分配而在堆分配而在另另一一个个堆堆释释放。)放。)  

在所有在所有虚虚拟拟内内存系存系统统中,堆中,堆驻驻留在操作系留在操作系统统的的““虚虚拟拟内内存管理器存管理器””的的顶顶部。部。语语言言运运行行时时堆也堆也驻驻留在留在虚虚拟拟内内存存顶顶部。某些情部。某些情况况下,下,这这些堆是操作系些堆是操作系统统堆中的堆中的层层,而,而语语言言运运行行时时堆堆则则
通过大块的分配来执行自己的内存管理。不使用操作系统堆,而使用虚拟内存函数更利于堆的分配和块的使用。  
  
典型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用,堆尝试从前端列表找到一个自由块。如果失败,堆被迫从后端(保留和
提交提交虚虚拟拟内内存)分配一存)分配一个个大大块块来来满满足足请请求。通用的求。通用的实现实现有有每每块块分配的分配的开销开销,,这这将将耗耗费执费执行周期,也行周期,也减减少了可使用的存少了可使用的存储储空空间间。。   

Knowledge Base Knowledge Base 文章 文章 Q10758Q10758,,““用 用 calloc() calloc() 和 和 malloc() malloc() 管理管理内内存存””  (搜索文章(搜索文章编编号号)), , 包含了有包含了有关这关这些主些主题题的更多背景知的更多背景知识识。。另另外,有外,有关关堆堆实现实现和和设计设计的的详细讨论详细讨论也可在也可在
下列著作中找到:“Dynamic Storage Allocation: A Survey and Critical Review”,作者 Paul R. Wilson、Mark S. Johnstone、Michael Neely 和 David Boles;“Internationa
l Workshop on Memory Management”, 作者 Kinross, Scotland, UK, 1995 年 9 月(http://www.cs.utexas.edu/users/oops/papers.html)(英文)。  
  
Windows NT 的实现(Windows NT 版本 4.0 和更新版本) 使用了 127 个大小从 8 到 1,024 字节的 8 字节对齐块空闲列表和一个“大块”列表。“大块”列表(空闲列表
[[0]0]) 保存大于 ) 保存大于 1,024 1,024 字字节节的的块块。空。空闲闲列表容列表容纳纳了用了用双双向向链链表表链链接在一起的接在一起的对对象。默象。默认认情情况况下,下,““进进程堆程堆””执执行收集操作。(收集是行收集操作。(收集是将将相相邻邻空空闲块闲块合合并并成一成一个个大大块块的操作。)收的操作。)收
集耗费了额外的周期,但减少了堆块的内部碎片。  
  
单一全局锁保护堆,防止多线程式的使用。(请参见“Server Performance and Scalability Killers”中的第一个注意事项, George Reilly 所著,在 “MSDN Online Web Works
hophop””上(站上(站点点::[url=http://msdn.microsoft.com/workshop/server/iis/tencom.asphttp://msdn.microsoft.com/workshop/server/iis/tencom.asp]http://msdn.microsoft.com/workshop/server/iis/tencom.asphttp://msdn.microsoft.com/workshop/server/iis/tencom.asp[/url](英文)。)(英文)。)单单一全局一全局锁锁本本质质上是用上是用来来保保护护堆堆数数据据结结构构,防止跨多,防止跨多线线程的程的随随机存取。若堆操作太机存取。若堆操作太频频
繁,单一全局锁会对性能有不利的影响。  
  
什么是常见的堆性能问题?  
以下是您使用堆时会遇到的最常见问题:   
  
分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块,所以运行时分配程序代码会耗费周期寻找较大的空闲块,或从后端分配程
序分配新序分配新块块。。   

  
释放操作造成的速度减慢。释放操作耗费较多周期,主要是启用了收集操作。收集期间,每个释放操作“查找”它的相邻块,取出它们并构造成较大块,然后再把此较大块插入
空空闲闲列表。在列表。在查查找期找期间间,,内内存可能存可能会随会随机机碰碰到,到,从从而而导导致高速致高速缓缓存不能命中,性能降低。存不能命中,性能降低。   

  
堆竞争造成的速度减慢。当两个或多个线程同时访问数据,而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争。竞争总是导致麻烦;这也是目前多处理器系统遇
到的最大问题。当大量使用内存块的应用程序或 DLL 以多线程方式运行(或运行于多处理器系统上)时将导致速度减慢。单一锁定的使用—常用的解决方案—意味着使用堆的
所有操作是序列化的。所有操作是序列化的。当当等待等待锁锁定定时时序列化序列化会会引起引起线线程切程切换换上下文。可以想象交叉路口上下文。可以想象交叉路口闪烁闪烁的的红红灯灯处处走走停停走走停停导导致的速度致的速度减减慢。 慢。     
竞争通常会导致线程和进程的上下文切换。上下文切换的开销是很大的,但开销更大的是数据从处理器高速缓存中丢失,以及后来线程复活时的数据重建。  
  
堆破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情形包括释放已释放的堆块或使用已释放的堆块,以及块的越界重写等明显问题。(破坏不在
本文本文讨论讨论范范围围之之内内。有。有关关内内存重存重写写和泄漏等其他和泄漏等其他细节细节,,请请参参见见  Microsoft Visual C++(R) Microsoft Visual C++(R) 调试调试文文档档 。) 。)   

  
频繁的分配和重分配造成的速度减慢。这是使用脚本语言时非常普遍的现象。如字符串被反复分配,随重分配增长和释放。不要这样做,如果可能,尽量分配大字符串和使用缓
冲冲区区。。另另一一种种方法就是方法就是尽尽量少用量少用连连接操作。接操作。   
竞争是在分配和释放操作中导致速度减慢的问题。理想情况下,希望使用没有竞争和快速分配/释放的堆。可惜,现在还没有这样的通用堆,也许将来会有。  

在所有的服在所有的服务务器系器系统统中(如 中(如 IISIIS、、MSProxyMSProxy、、DatabaseStacksDatabaseStacks、、网网络络服服务务器、 器、 Exchange Exchange 和其他)和其他), , 堆堆锁锁定定实实在是在是个个大大瓶瓶颈颈。。处处理器理器数数越多,越多,竞竞争争就越就越会会恶恶化。化。   

尽尽量量减减少堆的使用少堆的使用   
现在您明白使用堆时存在的问题了,难道您不想拥有能解决这些问题的超级魔棒吗?我可希望有。但没有魔法能使堆运行加快—因此不要期望在产品出货之前的最后一星期能够
大大为为改改观观。如果提前。如果提前规规划划堆策略,情堆策略,情况将会况将会大大好大大好转转。。调调整使用堆的方法,整使用堆的方法,减减少少对对堆的操作是提高性能的良方。堆的操作是提高性能的良方。   

如何如何减减少使用堆操作?通少使用堆操作?通过过利用利用数数据据结结构构内内的位置可的位置可减减少堆操作的次少堆操作的次数数。。请请考考虑虑下列下列实实例:例:   

struct ObjectA {struct ObjectA {  
  // objectA 的数据   
}
  
struct ObjectB {
  // objectB   // objectB 的的数数据 据     
}
  
// 同时使用 objectA 和 objectB  
  
//
// // 使用指使用指针针      
//
struct ObjectB {
  struct ObjectA * pObjA;  struct ObjectA * pObjA;  
  // objectB 的数据   
}}  

////  
// 使用嵌入  
////  
struct ObjectB {
  struct ObjectA pObjA;  struct ObjectA pObjA;  
  // objectB 的数据   
}
  
//
// // 集合 集合 ––  在在另另一一对对象象内内使用 使用 objectA objectA 和 和 objectBobjectB   
//
  
struct ObjectX {
    struct ObjectAstruct ObjectA objA; objA;  
  struct ObjectB objB;
}}  

避免使用指针关联两个数据结构。如果使用指针关联两个数据结构,前面实例中的对象 A 和 B 将被分别分配和释放。这会增加额外开销—我们要避免这种做法。  
  

把把带带指指针针的子的子对对象嵌入父象嵌入父对对象。象。当当对对象中有指象中有指针时针时,,则则意味意味着着对对象中有象中有动态动态元素(百分之八十)和元素(百分之八十)和没没有引用的新位置。嵌入增加了位置有引用的新位置。嵌入增加了位置从从而而减减少了少了进进一一步步分配分配//释释放的需求。放的需求。这这
将提高应用程序的性能。  
  

合合并并小小对对象形成大象形成大对对象(聚合)。聚合象(聚合)。聚合减减少分配和少分配和释释放的放的块块的的数数量。如果有几量。如果有几个个开发开发者,各自者,各自开发设计开发设计的不同部分,的不同部分,则则最最终终会会有有许许多小多小对对象需要合象需要合并并。集成的挑。集成的挑战战就是要找到正就是要找到正
确的聚合边界。  
  

内联缓冲区能够满足百分之八十的需要(aka 80-20 规则)。个别情况下,需要内存缓冲区来保存字符串/二进制数据,但事先不知道总字节数。估计并内联一个大小能满足百分
之八十需要的之八十需要的缓缓冲冲区区。。对对剩余的百分之二十,可以分配一剩余的百分之二十,可以分配一个个新的新的缓缓冲冲区区和指向和指向这这个个缓缓冲冲区区的指的指针针。。这样这样,就,就减减少分配和少分配和释释放放调调用用并并增加增加数数据的位置空据的位置空间间,,从从根本上提高代根本上提高代码码的性的性
能。  
  

在在块块中分配中分配对对象(象(块块化)。化)。块块化是以化是以组组的方式一次分配多的方式一次分配多个个对对象的方法。如果象的方法。如果对对列表的列表的项连续项连续跟跟踪踪,例如,例如对对一一个个  {{名名称称,,值值} } 对对的列表,有的列表,有两两种种选择选择::选择选择一是一是为为每每一一个个““名名称称--
值”对分配一个节点;选择二是分配一个能容纳(如五个)“名称-值”对的结构。例如,一般情况下,如果存储四对,就可减少节点的数量,如果需要额外的空间数量,则使
用附加的用附加的链链表指表指针针。 。     
块化是友好的处理器高速缓存,特别是对于 L1-高速缓存,因为它提供了增加的位置 —不用说对于块分配,很多数据块会在同一个虚拟页中。  
  
正确使用 _amblksiz。C 运行时 (CRT) 有它的自定义前端分配程序,该分配程序从后端(Win32 堆)分配大小为 _amblksiz 的块。将 _amblksiz 设置为较高的值能潜在地减少
对后端的调用次数。这只对广泛使用 CRT 的程序适用。  
使用上述技使用上述技术术将将获获得的好得的好处处会会因因对对象象类类型、大小及工作量而有所不同。但型、大小及工作量而有所不同。但总总能在性能和可升能在性能和可升缩缩性方面有所收性方面有所收获获。。另另一方面,代一方面,代码码会会有有点点特殊,但如果特殊,但如果经过经过深思熟深思熟虑虑,代,代码还码还是很是很
容易管理的。  
  
其他提高性能的技术  
下面是一些提高速度的技下面是一些提高速度的技术术: :     

使用 使用 Windows NT5 Windows NT5 堆 堆     
由于几个同事的努力和辛勤工作,1998 年初 Microsoft Windows(R) 2000 中有了几个重大改进:  
  
改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构,防止多线程式的使用。但不幸的是,在高通信量的情况下,堆仍受困于全局锁,导致高竞争和低性能。
Windows 2000 中,锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。  
  

使用 使用 ““LookasideLookaside””列表。堆列表。堆数数据据结结构构对块对块的所有空的所有空闲项闲项使用了大小在 使用了大小在 8 8 到 到 1,024 1,024 字字节节(以 (以 88--字字节递节递增)的快速高速增)的快速高速缓缓存。快速高速存。快速高速缓缓存最初保存最初保护护在全局在全局锁锁内内。。现现在,使用 在,使用
ookaside 列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定,而是使用 64 位的互锁操作,因此提高了性能。  
  

内内部部数数据据结结构构算法也得到改算法也得到改进进。。   
这些改进避免了对分配高速缓存的需求,但不排除其他的优化。使用 Windows NT5 堆评估您的代码;它对小于 1,024 字节 (1 KB) 的块(来自前端分配程序的块)是最佳的。
GlobalAlloc() GlobalAlloc() 和 和 LocalAlloc() LocalAlloc() 建立在同一堆上,是存取建立在同一堆上,是存取每每个个进进程堆的通用机制。如果希望程堆的通用机制。如果希望获获得高的局部性能,得高的局部性能,则则使用 使用 Heap(R) API Heap(R) API 来来存取存取每每个个进进程堆,或程堆,或为为分配操作分配操作创创建自己建自己
的堆。如果需要对大块操作,也可以直接使用 VirtualAlloc() / VirtualFree() 操作。  

上述改上述改进进已在 已在 Windows 2000 beta 2 Windows 2000 beta 2 和 和 Windows NT 4.0 SP4 Windows NT 4.0 SP4 中使用。改中使用。改进进后,堆后,堆锁锁的的竞竞争争率率显显著降低。著降低。这这使所有 使所有 Win32 Win32 堆的直接用堆的直接用户户受益。受益。CRT CRT 堆建立于 堆建立于 Win32 Win32 堆的堆的顶顶
部,但它使用自己的小块堆,因而不能从 Windows NT 改进中受益。(Visual C++ 版本 6.0 也有改进的堆分配程序。)  
  
使用分配高速缓存   
分配高速分配高速缓缓存允存允许许高速高速缓缓存分配的存分配的块块,以便,以便将来将来重用。重用。这这能能够减够减少少对进对进程堆(或全局堆)的分配程堆(或全局堆)的分配//释释放放调调用的次用的次数数,也允,也允许许最大限度的重用曾最大限度的重用曾经经分配的分配的块块。。另另外,分配高速外,分配高速缓缓存存
允许收集统计信息,以便较好地理解对象在较高层次上的使用。  
  
典型地,自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设计成一套
固定大小(如 固定大小(如 32 32 字字节节、、64 64 字字节节、、128 128 字字节节等)。等)。这这一一个个很好的策略,但很好的策略,但这这种种自定自定义义堆分配程序堆分配程序丢丢失失与与分配和分配和释释放的放的对对象相象相关关的的““语义语义信息信息””。 。     

与自定义堆分配程序相反,“分配高速缓存”作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外,它们还能够保留大量语义信息。每个分配高速缓存
处处理程序理程序与与一一个个目目标标二二进进制制对对象象关联关联。。它它能能够够使用一套使用一套参数参数进进行初始化,行初始化,这这些些参数参数表示表示并并发级别发级别、、对对象大小和保持在空象大小和保持在空闲闲列表中的元素的列表中的元素的数数量等。分配高速量等。分配高速缓缓存存处处理程序理程序对对象象维维
持自己的私有空闲实体池(不超过指定的阀值)并使用私有保护锁。合在一起,分配高速缓存和私有锁减少了与主系统堆的通信量,因而提供了增加的并发、最大限度的重用和
较较高的可伸高的可伸缩缩性。性。   

需要使用需要使用清清理程序理程序来来定期定期检检查查所有分配高速所有分配高速缓缓存存处处理程序的活理程序的活动动情情况况并并回收未用的回收未用的资资源。如果源。如果发现发现没没有活有活动动,,将将释释放分配放分配对对象的池,象的池,从从而提高性能。而提高性能。   

可以可以审审核核每每个个分配分配//释释放活放活动动。第一。第一级级信息包括信息包括对对象、分配和象、分配和释释放放调调用的用的总总数数。通。通过过查查看看它它们们的的统计统计信息可以得出各信息可以得出各个个对对象之象之间间的的语义关语义关系。利用以上介系。利用以上介绍绍的的许许多技多技术术之一,之一,这这
种关系可以用来减少内存分配。  
  
分配高速缓存也起到了调试助手的作用,帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名,甚至能够找到确切的失败的调用
者。  
  
MP 堆   
MP MP 堆是堆是对对多多处处理器友好的分布式分配的程序包,在 理器友好的分布式分配的程序包,在 Win32 SDKWin32 SDK((Windows NT 4.0 Windows NT 4.0 和更新版本)中可以得到。最初由 和更新版本)中可以得到。最初由 JVert JVert 实现实现,此,此处处堆抽象建立在 堆抽象建立在 Win32 Win32 堆程序包的堆程序包的顶顶
部。MP 堆创建多个 Win32 堆,并试图将分配调用分布到不同堆,以减少在所有单一锁上的竞争。  
  
本程序包是好的步骤 —一种改进的 MP-友好的自定义堆分配程序。但是,它不提供语义信息和缺乏统计功能。通常将 MP 堆作为 SDK 库来使用。如果使用这个 SDK 创建可
重用重用组组件,件,您您将将大大受益。但是,如果在大大受益。但是,如果在每每个个  DLL DLL 中建立中建立这这个个  SDK SDK 库库,,将将增加工作增加工作设设置。置。   

重新思考算法和重新思考算法和数数据据结结构构      
要在多处理器机器上伸缩,则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问,“我能用不同的数据结构完成此工作吗?”例如,如果在
应用程序初始化时加载了只读项的列表,这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎片,从而增强性能。  
  
减少需要的小对象的数量减少堆分配程序的负载。例如,我们在服务器的关键处理路径上使用五个不同的对象,每个对象单独分配和释放。一起高速缓存这些对象,把堆调用从
五五个个减减少到一少到一个个,,显显著著减减少了堆的少了堆的负载负载,特,特别别当当每每秒秒钟处钟处理 理 1,000 1,000 个个以上的以上的请请求求时时。。   

如果大量使用如果大量使用““AutomationAutomation””结结构构,,请请考考虑虑从从主主线线代代码码中中删删除除““Automation BSTRAutomation BSTR””,或至少避免重,或至少避免重复复的 的 BSTR BSTR 操作。(操作。(BSTR BSTR 连连接接导导致致过过多的重分配和分配多的重分配和分配//释释放操作。)放操作。)   

摘要摘要   
对所有平台往往都存在堆实现,因此有巨大的开销。每个单独代码都有特定的要求,但设计能采用本文讨论的基本理论来减少堆之间的相互作用。   
  
评价您的代码中堆的使用。  

  
改进您的代码,以使用较少的堆调用:分析关键路径和固定数据结构。  
  

在在实现实现自定自定义义的包的包装装程序之前使用量化堆程序之前使用量化堆调调用成本的方法。用成本的方法。   

  
如果对性能不满意,请要求 OS 组改进堆。更多这类请求意味着对改进堆的更多关注。  
  

要求 C 运行时组针对 OS 所提供的堆制作小巧的分配包装程序。随着 OS 堆的改进,C 运行时堆调用的成本将减小。  
  

操作系操作系统统((Windows NT Windows NT 家族)正在不家族)正在不断断改改进进堆。堆。请请随随时关时关注和利用注和利用这这些改些改进进。。   
Murali Krishnan 是 Internet Information Server (IIS) 组的首席软件设计工程师。从 1.0 版本开始他就设计 IIS,并成功发行了 1.0 版本到 4.0 版本。Murali 组织并领导 IIS 性
能能组组三年 三年 ((19951995--1998), 1998), 从从一一开开始就影始就影响响  IIS IIS 性能。他性能。他拥拥有威斯康星州 有威斯康星州 Madison Madison 大大学学的 的 M.S.M.S.和印度 和印度 Anna Anna 大大学学的 的 B.S.B.S.。工作之外,他喜。工作之外,他喜欢阅读欢阅读、打排球和家庭烹、打排球和家庭烹饪饪。。   

  

[url=http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835]http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835[/url]  
我在学习对象的生存方式的时候见到一种是在堆栈(stack)之中,如下   
CObject object;  
还还有一有一种种是在堆是在堆((heap)heap)中中 如下 如下      
CObject* pobject=new CObject();  
  
请问   
((11))这两这两种种方式有什方式有什么么区区别别??      
(2)堆栈与堆有什么区别??   
  

------------------------------------------------------------------------------------------------------------------------------      

1) about stack, system will allocate memory to the instance of object automatically, and to the heap, you must allocate memory to the instance of object with new or malloc m
anually.anually.   
2) when function ends, system will automatically free the memory area of stack, but to the heap, you must free the memory area manually with free or delete, else it will resu
tt in in memory memory leak. leak.   
3)栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。   
44)堆上分配的)堆上分配的内内存可以有我存可以有我们们自己自己决决定,使用非常定,使用非常灵灵活。活。      
---------------------------------------------------------------   
  

堆和堆和栈栈的比的比较较      

  从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的:   
  
  在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像
工工厂厂中的中的传传送送带带((conveyorconveyor belt) belt)一一样样,,StackStack Pointer Pointer会会自自动动指引指引你你到放到放东东西的位置西的位置,,你你所要做的只是把所要做的只是把东东西放下西放下来来就行就行..退出函退出函数数的的时时候,修改候,修改栈栈指指针针就可以把就可以把栈栈中的中的内内容容销销毁毁..这样这样
的模式速度最快,当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序
运运行行时进时进行的行的,,但是分配的大小多少是但是分配的大小多少是确确定的定的,,不不变变的的,,而而这这个个""大小多少大小多少""是在是在编译时编译时确确定的定的,,不是在不是在运运行行时时..      

    堆是堆是应应用程序在用程序在运运行的行的时时候候请请求操作系求操作系统统分配分配给给自己自己内内存,由于存,由于从从操作系操作系统统管理的管理的内内存分配存分配,,所以在分配和所以在分配和销销毁毁时时都要占用都要占用时间时间,因此用堆的效率非常低,因此用堆的效率非常低..但是堆的但是堆的优优点点在于在于,,编编
译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是
必不可少的必不可少的,,因因为为多多态变态变量所需的存量所需的存储储空空间间只有在只有在运运行行时创时创建了建了对对象之后才能象之后才能确确定定..在在C++C++中,要求中,要求创创建一建一个个对对象象时时,只需用,只需用newnew命令命令编编制相制相关关的代的代码码即可。即可。执执行行这这些代些代码时码时,,会会
在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致效率低的原因,   

我想我想你你现现在在该该明白了明白了吧吧。:)。:)

该用户从未签到

发表于 2011-3-14 14:39:07 | 显示全部楼层
相当不错,有很多基础的东西
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-5-3 13:52

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表