??xml version="1.0" encoding="utf-8" standalone="yes"?>99久久精品免费看国产免费,www亚洲欲色成人久久精品,97久久精品人人做人人爽http://m.shnenglu.com/zjl-1026-2001/category/10405.htmlzh-cnTue, 19 May 2009 12:21:12 GMTTue, 19 May 2009 12:21:12 GMT60堆和栈的区别 (转脓(chung))http://m.shnenglu.com/zjl-1026-2001/archive/2009/05/19/83385.html沙漠里的沙漠里的Tue, 19 May 2009 08:53:00 GMThttp://m.shnenglu.com/zjl-1026-2001/archive/2009/05/19/83385.htmlhttp://m.shnenglu.com/zjl-1026-2001/comments/83385.htmlhttp://m.shnenglu.com/zjl-1026-2001/archive/2009/05/19/83385.html#Feedback0http://m.shnenglu.com/zjl-1026-2001/comments/commentRss/83385.htmlhttp://m.shnenglu.com/zjl-1026-2001/services/trackbacks/83385.html

堆和栈的区别 (转脓(chung))

非本Z?因非常经?所以收归旗?与众人阅?原作者不?

堆和栈的区别
一、预备知识—程序的内存分配
一个由c/C++~译的程序占用的内存分ؓ以下几个部分
1、栈区(stackQ?nbsp;q译器自动分配释放 Q存攑և数的参数|局部变量的值等。其操作方式cM于数据结构中的栈?br>2、堆区(heapQ?nbsp;?nbsp;一般由E序员分配释放, 若程序员不释放,E序l束时可能由O(jin)S回收 。注意它与数据结构中的堆是两回事Q分配方式倒是cM于链表,呵呵?br>3、全局区(静态区Q(staticQ—,全局变量和静态变量的存储是放在一块的Q初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在盔R的另一块区域?nbsp;- E序l束后有pȝ释放 
4、文字常量区—常量字W串是攑֜q里的?nbsp;E序l束后由pȝ释放
5、程序代码区—存攑ևC的二q制代码?br>二、例子程?nbsp;
q是一个前辈写的,非常详细 
//main.cpp 
int a = 0; 全局初始化区 
char *p1; 全局未初始化?nbsp;
main() 

int b; ?nbsp;
char s[] = "abc"; ?nbsp;
char *p2; ?nbsp;
char *p3 = "123456"; 123456\0在常量区Qp3在栈上?nbsp;
static int c =0Q?nbsp;全局Q静态)初始化区 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); 
分配得来?0?0字节的区域就在堆区?nbsp;
strcpy(p1, "123456"); 123456\0攑֜帔R区,~译器可能会它与p3所指向?123456"优化成一个地斏V?nbsp;

 


二、堆和栈的理论知?nbsp;
2.1甌方式 
stack: 
ql自动分配?nbsp;例如Q声明在函数中一个局部变?nbsp;int b; pȝ自动在栈中ؓb开辟空?nbsp;
heap: 
需要程序员自己甌Qƈ指明大小Q在c中malloc函数 
如p1 = (char *)malloc(10); 
在C++中用newq算W?nbsp;
如p2 = (char *)malloc(10); 
但是注意p1、p2本n是在栈中的?nbsp;


2.2 
甌后系l的响应 
栈:只要栈的剩余I间大于所甌I间Q系l将为程序提供内存,否则报异常提示栈溢出?nbsp;
堆:首先应该知道操作pȝ有一个记录空闲内存地址的链表,当系l收到程序的甌Ӟ 
会遍历该链表Q寻扄一个空间大于所甌I间的堆l点Q然后将该结点从I闲l点链表中删除,q将该结点的I间分配l程序,另外Q对于大多数pȝQ会在这块内存空间中的首地址处记录本ơ分配的大小Q这P代码中的delete语句才能正确的释放本内存I间。另外,׃扑ֈ的堆l点的大不一定正好等于申L大小Q系l会自动的将多余的那部分重新攑օI闲链表中?nbsp;

2.3甌大小的限?nbsp;
栈:在Windows?栈是向低地址扩展的数据结构,是一块连l的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是pȝ预先规定好的Q在WINDOWS下,栈的大小?MQ也有的说是1MQM是一个编译时q定的常数Q,如果甌的空间超q栈的剩余空间时Q将提示overflow。因此,能从栈获得的I间较小?nbsp;
堆:堆是向高地址扩展的数据结构,是不q箋的内存区域。这是由于系l是用链表来存储的空闲内存地址的,自然是不q箋的,而链表的遍历方向是由低地址向高地址。堆的大受限于计算机系l中有效的虚拟内存。由此可见,堆获得的I间比较灉|Q也比较大?nbsp;


2.4甌效率的比较: 
栈由pȝ自动分配Q速度较快。但E序员是无法控制的?nbsp;
堆是由new分配的内存,一般速度比较慢,而且Ҏ(gu)产生内存片,不过用v来最方便. 
另外Q在WINDOWS下,最好的方式是用VirtualAlloc分配内存Q他不是在堆Q也不是在栈是直接在q程的地址I间中保留一快内存,虽然用v来最不方ѝ但是速度快,也最灉|?nbsp;

2.5堆和栈中的存储内?nbsp;
栈: 在函数调用时Q第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句Q的地址Q然后是函数的各个参敎ͼ在大多数的C~译器中Q参数是由右往左入栈的Q然后是函数中的局部变量。注意静态变量是不入栈的?nbsp;
当本ơ函数调用结束后Q局部变量先出栈Q然后是参数Q最后栈指针指向最开始存的地址Q也是dC的下一条指令,E序p点l运行?nbsp;
堆:一般是在堆的头部用一个字节存攑֠的大。堆中的具体内容有程序员安排?nbsp;

2.6存取效率的比?nbsp;

char s1[] = "aaaaaaaaaaaaaaa"; 
char *s2 = "bbbbbbbbbbbbbbbbb"; 
aaaaaaaaaaa是在q行时刻赋值的Q?nbsp;
而bbbbbbbbbbb是在~译时就定的; 
但是Q在以后的存取中Q在栈上的数l比指针所指向的字W串(例如?快?nbsp;
比如Q?nbsp;
#include 
void main() 

char a = 1; 
char c[] = "1234567890"; 
char *p ="1234567890"; 
a = c[1]; 
a = p[1]; 
return; 

对应的汇~代?nbsp;
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 
W一U在d时直接就把字W串中的元素d寄存器cl中,而第二种则要先把指针D到edx中,在根据edxd字符Q显然慢了?nbsp;


2.7结Q?nbsp;
堆和栈的区别可以用如下的比喻来看出: 
使用栈就象我们去饭馆里吃饭,只管点菜Q发出申P、付钱、和吃(使用Q,吃饱了就赎ͼ不必理会切菜、洗菜等准备工作和洗、刷锅等扫尾工作Q他的好处是快捷Q但是自由度?nbsp;
使用堆就象是自己动手做喜Ƣ吃的菜_比较ȝQ但是比较符合自q口味Q而且自由度大?nbsp;



windowsq程中的内存l构


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

接触q编E的人都知道Q高U语a都能通过变量名来讉K内存中的数据。那么这些变量在内存中是如何存放的呢Q程序又是如何用这些变量的呢?下面׃Ҏ(gu)q行深入的讨论。下文中的C语言代码如没有特别声明,默认都用VC~译的release版?nbsp;

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

#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; 

~译后的执行l果是: 

0x0012ff78 
0x0012ff7c 
0x0012ff80 

0x004068d0 
0x004068d4 
0x004068d8 

0x004068dc 
0x004068e0 
0x004068e4 

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


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


堆栈是一个先q后出的数据l构Q栈地址L于{于栈的基地址。我们可以先了解一下函数调用的q程Q以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定Q这些因素有参数的压入规则和堆栈的^衡。windows API的调用规则和ANSI C的函数调用规则是不一LQ前者由被调函数调整堆栈Q后者由调用者调整堆栈。两者通过“__stdcall”?#8220;__cdecl”前缀区分。先看下面这D代码: 

#include <stdio.h> 

void __stdcall func(int param1,int param2,int param3) 

int var1=param1; 
int var2=param2; 
int var3=param3; 
printf("0x%08x\n",¶m1); //打印出各个变量的内存地址 
printf("0x%08x\n",¶m2); 
printf("0x%08x\n\n",¶m3); 
printf("0x%08x\n",&var1); 
printf("0x%08x\n",&var2); 
printf("0x%08x\n\n",&var3); 
return; 

int main() 

func(1,2,3); 
return 0; 

~译后的执行l果是: 

0x0012ff78 
0x0012ff7c 
0x0012ff80 

0x0012ff68 
0x0012ff6c 
0x0012ff70 


├———————┤<—函数执行时的栈ӞESPQ、低端内存区?nbsp;
?nbsp;…… ?nbsp;
├———————┤ 
?nbsp;var 1 ?nbsp;
├———————┤ 
?nbsp;var 2 ?nbsp;
├———————┤ 
?nbsp;var 3 ?nbsp;
├———————┤ 
?nbsp;RET ?nbsp;
├———————┤<?#8220;__cdecl”函数q回后的栈顶QESPQ?nbsp;
?nbsp;parameter 1 ?nbsp;
├———————┤ 
?nbsp;parameter 2 ?nbsp;
├———————┤ 
?nbsp;parameter 3 ?nbsp;
├———————┤<?#8220;__stdcall”函数q回后的栈顶QESPQ?nbsp;
?nbsp;…… ?nbsp;
├———————┤<—栈底(基地址 EBPQ、高端内存区?nbsp;


上图是函数调用q程中堆栈的样子了。首先,三个参数以从又到左的ơ序压入堆栈Q先?#8220;param3”Q再?#8220;param2”Q最后压?#8220;param1”Q然后压入函数的q回地址(RET)Q接着跌{到函数地址接着执行Q这里要补充一点,介绍UNIX下的~冲溢出原理的文章中都提到在压入RET后,l箋压入当前EBPQ然后用当前ESP代替EBP。然而,有一介lwindows下函数调用的文章中说Q在windows下的函数调用也有q一步骤Q但Ҏ(gu)我的实际调试Qƈ未发现这一步,q还可以从param3和var1之间只有4字节的间隙这点看出来Q;W三步,栈?ESP)减去一个数Qؓ本地变量分配内存I间Q上例中是减?2字节(ESP=ESP-3*4Q每个int变量占用4个字?Q接着初始化本地变量的内存空间。由?#8220;__stdcall”调用p调函数调整堆栈,所以在函数q回前要恢复堆栈Q先回收本地变量占用的内?ESP=ESP+3*4)Q然后取回地址Q填入EIP寄存器,回收先前压入参数占用的内?ESP=ESP+3*4)Ql执行调用者的代码。参见下列汇~代码: 

;--------------func 函数的汇~代?------------------ 

:00401000 83EC0C sub esp, 0000000C //创徏本地变量的内存空?nbsp;
:00401003 8B442410 mov eax, dword ptr [esp+10] 
:00401007 8B4C2414 mov ecx, dword ptr [esp+14] 
:0040100B 8B542418 mov edx, dword ptr [esp+18] 
:0040100F 89442400 mov dword ptr [esp], eax 
:00401013 8D442410 lea eax, dword ptr [esp+10] 
:00401017 894C2404 mov dword ptr [esp+04], ecx 

……………………Q省略若q代码) 

:00401075 83C43C add esp, 0000003C ;恢复堆栈Q回收本地变量的内存I间 
:00401078 C3 ret 000C ;函数q回Q恢复参数占用的内存I间 
;如果?#8220;__cdecl”的话Q这里是“ret”Q堆栈将p用者恢?nbsp;

;-------------------函数l束------------------------- 


;--------------ȝ序调用func函数的代?------------- 

:00401080 6A03 push 00000003 //压入参数param3 
:00401082 6A02 push 00000002 //压入参数param2 
:00401084 6A01 push 00000001 //压入参数param1 
:00401086 E875FFFFFF call 00401000 //调用func函数 
;如果?#8220;__cdecl”的话Q将在这里恢复堆栈,“add esp, 0000000C” 

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

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

void __stdcall func() 

char lpBuff[8]="\0"; 
strcat(lpBuff,"AAAAAAAAAAA"); 
return; 

int main() 

func(); 
return 0; 

~译后执行一下回怎么P哈,“"0x00414141"指o引用?0x00000000"内存。该内存不能?read"?#8221;Q?#8220;非法操作”喽!"41"是"A"?6q制的ASCII码了Q那明显是strcatq句出的问题了?lpBuff"的大只?字节Q算q结\0Q那strcat最多只能写??A"Q但E序实际写入?1?A"外加1个\0。再来看看上面那q图Q多出来?个字节正好覆盖了RET的所在的内存I间Q导致函数返回到一个错误的内存地址Q执行了错误的指令。如果能_ֿ构造这个字W串Q它分成三部分Q前一部䆾仅仅是填充的无意义数据以辑ֈ溢出的目的,接着是一个覆盖RET的数据,紧接着是一DshellcodeQ那只要着个RET地址能指向这Dshellcode的第一个指令,那函数返回时p执行shellcode了。但是Y件的不同版本和不同的q行环境都可能媄响这Dshellcode在内存中的位|,那么要构造这个RET是十分困隄。一般都在RET和shellcode之间填充大量的NOP指oQ得exploit有更强的通用性?nbsp;


├———————┤<—低端内存区?nbsp;
?nbsp;…… ?nbsp;
├———————┤<—由exploit填入数据的开?nbsp;
?nbsp;?nbsp;
?nbsp;buffer ?lt;—填入无用的数据 
?nbsp;?nbsp;
├———————┤ 
?nbsp;RET ?lt;—指向shellcodeQ或NOP指o的范?nbsp;
├———————┤ 
?nbsp;NOP ?nbsp;
?nbsp;…… ?lt;—填入的NOP指oQ是RET可指向的范围 
?nbsp;NOP ?nbsp;
├———————┤ 
?nbsp;?nbsp;
?nbsp;shellcode ?nbsp;
?nbsp;?nbsp;
├———————┤<—由exploit填入数据的结?nbsp;
?nbsp;…… ?nbsp;
├———————┤<—高端内存区?nbsp;


windows下的动态数据除了可存放在栈中,q可以存攑֜堆中。了解C++的朋友都知道QC++可以使用new关键字来动态分配内存。来看下面的C++代码Q?nbsp;

#include <stdio.h> 
#include <iostream.h> 
#include <windows.h> 

void func() 

char *buffer=new char[128]; 
char bufflocal[128]; 
static char buffstatic[128]; 
printf("0x%08x\n",buffer); //打印堆中变量的内存地址 
printf("0x%08x\n",bufflocal); //打印本地变量的内存地址 
printf("0x%08x\n",buffstatic); //打印静态变量的内存地址 

void main() 

func(); 
return; 

E序执行l果为: 

0x004107d0 
0x0012ff04 
0x004068c0 

可以发现用new关键字分配的内存即不在栈中,也不在静态数据区。VC~译器是通过windows下的“?heap)”来实现new关键字的内存动态分配。在?#8220;?#8221;之前Q先来了解一下和“?#8221;有关的几个API函数Q?nbsp;

HeapAlloc 在堆中申请内存空?nbsp;
HeapCreate 创徏一个新的堆对象 
HeapDestroy 销毁一个堆对象 
HeapFree 释放甌的内?nbsp;
HeapWalk 枚D堆对象的所有内存块 
GetProcessHeap 取得q程的默认堆对象 
GetProcessHeaps 取得q程所有的堆对?nbsp;
LocalAlloc 
GlobalAlloc 

当进E初始化Ӟpȝ会自动ؓq程创徏一个默认堆Q这个堆默认所占内存的大小?M。堆对象ql进行管理,它在内存中以铑ּl构存在。通过下面的代码可以通过堆动态申请内存空_ 

HANDLE hHeap=GetProcessHeap(); 
char *buff=HeapAlloc(hHeap,0,8); 

其中h(hun)Heap是堆对象的句柄,buff是指向申L内存I间的地址。那q个hHeapI竟是什么呢Q它的值有什么意义吗Q看看下面这D代码吧Q?nbsp;

#pragma comment(linker,"/entry:main") //定义E序的入?nbsp;
#include <windows.h> 

_CRTIMP int (__cdecl *printf)(const char *, ...); //定义STL函数printf 
/*--------------------------------------------------------------------------- 
写到q里Q我们顺便来复习一下前面所讲的知识Q?nbsp;
(*?printf函数是C语言的标准函数库中函敎ͼVC的标准函数库由msvcrt.dll模块实现?nbsp;
由函数定义可见,printf的参C数是可变的,函数内部无法预先知道调用者压入的参数个数Q函数只能通过分析W一个参数字W串的格式来获得压入参数的信息,׃q里参数的个数是动态的Q所以必ȝ调用者来q堆栈Q这里便使用了__cdecl调用规则。BTWQWindowspȝ的API函数基本上是__stdcall调用形式Q只有一个API例外Q那是wsprintfQ它使用__cdecl调用规则Q同printf函数一Pq是׃它的参数个数是可变的~故?nbsp;
---------------------------------------------------------------------------*/ 
void main() 

HANDLE hHeap=GetProcessHeap(); 
char *buff=HeapAlloc(hHeap,0,0x10); 
char *buff2=HeapAlloc(hHeap,0,0x10); 
HMODULE hMsvcrt=LoadLibrary("msvcrt.dll"); 
printf=(void *)GetProcAddress(hMsvcrt,"printf"); 
printf("0x%08x\n",hHeap); 
printf("0x%08x\n",buff); 
printf("0x%08x\n\n",buff2); 

执行l果为: 

0x00130000 
0x00133100 
0x00133118 

hHeap的值怎么和那个buff的值那么接q呢Q其实hHeapq个句柄是指向HEAP首部的地址。在q程的用户区存着一个叫PEB(q程环境?的结构,q个l构中存攄一些有兌E的重要信息Q其中在PEB首地址偏移0x18处存攄ProcessHeap是q程默认堆的地址Q而偏U?x90处存放了指向q程所有堆的地址列表的指针。windows有很多API都用进E的默认堆来存放动态数据,如windows 2000下的所有ANSI版本的函数都是在默认堆中甌内存来{换ANSI字符串到Unicode字符串的。对一个堆的访问是序q行的,同一时刻只能有一个线E访问堆中的数据Q当多个U程同时有访问要求时Q只能排队等待,q样侉K成E序执行效率下降?nbsp;

最后来说说内存中的数据寚w。所位数据对齐,是指数据所在的内存地址必须是该数据长度的整数倍,DWORD数据的内存v始地址能被4除尽QWORD数据的内存v始地址能被2除尽Qx86 CPU能直接访问对齐的数据Q当他试图访问一个未寚w的数据时Q会在内部进行一pd的调_q些调整对于E序来说是透明的,但是会降低运行速度Q所以编译器在编译程序时会尽量保证数据对齐。同样一D代码,我们来看看用VC、Dev-C++和lcc三个不同~译器编译出来的E序的执行结果: 

#include <stdio.h> 

int main() 

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

q是用VC~译后的执行l果Q?nbsp;
0x0012ff7c 
0x0012ff7b 
0x0012ff80 
变量在内存中的顺序:b(1字节)-a(4字节)-c(4字节)?nbsp;

q是用Dev-C++~译后的执行l果Q?nbsp;
0x0022ff7c 
0x0022ff7b 
0x0022ff74 
变量在内存中的顺序:c(4字节)-中间盔R3字节-b(?字节)-a(4字节)?nbsp;

q是用lcc~译后的执行l果Q?nbsp;
0x0012ff6c 
0x0012ff6b 
0x0012ff64 
变量在内存中的顺序:同上?nbsp;

三个~译器都做到了数据对齐,但是后两个编译器昄没VC“聪明”Q让一个char占了4字节Q浪费内存哦?nbsp;


基础知识Q?nbsp;
堆栈是一U简单的数据l构Q是一U只允许在其一端进行插入或删除的线性表。允许插入或删除操作的一端称为栈Ӟ另一端称为栈底,对堆栈的插入和删除操作被UCؓ入栈和出栈。有一lCPU指o可以实现对进E的内存实现堆栈讉K。其中,POP指o实现出栈操作QPUSH指o实现入栈操作。CPU的ESP寄存器存攑ֽ前线E的栈顶指针QEBP寄存器中保存当前U程的栈底指针。CPU的EIP寄存器存放下一个CPU指o存放的内存地址Q当CPU执行完当前的指o后,从EIP寄存器中d下一条指令的内存地址Q然后l执行?nbsp;


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



摘要Q?nbsp;讨论常见的堆性能问题以及如何防范它们。(?nbsp;9 )

前言
(zhn)是否是动态分配的 C/C++ 对象忠实且幸q的用户Q?zhn)是否在模块间的往q通信中频J地使用?#8220;自动?#8221;Q?zhn)的程序是否因堆分配而运行v来很慢?不仅仅?zhn)遇到q样的问题。几乎所有项目迟早都会遇到堆问题。大安惌Q?#8220;我的代码真正好,只是堆太?#8221;。那只是部分正确。更深入理解堆及其用法、以及会发生什么问题,是很有用的?/p>

什么是堆?
Q如果?zhn)已经知道什么是堆,可以跛_“什么是常见的堆性能问题Q?#8221;部分Q?/p>

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

事先不知道程序所需对象的数量和大小?/p>


对象太大而不适合堆栈分配E序?br>堆用了在运行时分配l代码和堆栈的内存之外的部分内存。下囄Z堆分配程序的不同层?br>

GlobalAlloc/GlobalFreeQMicrosoft Win32 堆调用,q些调用直接与每个进E的默认堆进行对话?/p>

LocalAlloc/LocalFreeQWin32 堆调用(Z?nbsp;Microsoft Windows NT 兼容Q,q些调用直接与每个进E的默认堆进行对话?/p>

COM ?nbsp;IMalloc 分配E序Q或 CoTaskMemAlloc / CoTaskMemFreeQ:函数使用每个q程的默认堆。自动化E序使用“lg对象模型 (COM)”的分配程序,而申LE序使用每个q程堆?/p>

C/C++ q行?nbsp;(CRT) 分配E序Q提供了 malloc() ?nbsp;free() 以及 new ?nbsp;delete 操作W。如 Microsoft Visual Basic ?nbsp;Java {语a也提供了新的操作Wƈ使用垃圾攉来代替堆。CRT 创徏自己的私有堆Q驻留在 Win32 堆的剙?/p>

Windows NT 中,Win32 堆是 Windows NT q行时分配程序周围的薄层。所?nbsp;API 转发它们的请求给 NTDLL?/p>

Windows NT q行时分配程序提?nbsp;Windows NT 内的核心堆分配程序。它由具?nbsp;128 个大从 8 ?nbsp;1,024 字节的空闲列表的前端分配E序l成。后端分配程序用虚拟内存来保留和提交页?/p>

在图表的底部?#8220;虚拟内存分配E序”Q操作系l用它来保留和提交c所有分配程序用虚拟内存进行数据的存取?/p>

分配和释攑֝不就那么单吗Qؓ何花费这么长旉Q?/p>

堆实现的注意事项
传统上,操作pȝ和运行时库是与堆的实现共存的。在一个进E的开始,操作pȝ创徏一个默认堆Q叫?#8220;q程?#8221;。如果没有其他堆可用,则块的分配?#8220;q程?#8221;。语aq行时也能在q程内创建单独的堆。(例如QC q行时创建它自己的堆。)除这些专用的堆外Q应用程序或许多已蝲入的动态链接库 (DLL) 之一可以创徏和用单独的堆。Win32 提供一整套 API 来创建和使用U有堆。有兛_函数Q英文)的详指|请参?nbsp;MSDN?/p>

当应用程序或 DLL 创徏U有堆时Q这些堆存在于进E空_q且在进E内是可讉K的。从l定堆分配的数据在同一个堆上释放。(不能从一个堆分配而在另一个堆释放。)

在所有虚拟内存系l中Q堆ȝ在操作系l的“虚拟内存理?#8221;的顶部。语aq行时堆也驻留在虚拟内存剙。某些情况下Q这些堆是操作系l堆中的层,而语aq行时堆则通过大块的分配来执行自己的内存管理。不使用操作pȝ堆,而用虚拟内存函数更利于堆的分配和块的用?/p>

典型的堆实现由前、后端分配程序组成。前端分配程序维持固定大块的空闲列表。对于一ơ分配调用,堆尝试从前端列表扑ֈ一个自由块。如果失败,堆被q从后端Q保留和提交虚拟内存Q分配一个大块来满h。通用的实现有每块分配的开销Q这耗费执行周期Q也减少了可使用的存储空间?/p>

Knowledge Base 文章 Q10758Q?#8220;?nbsp;calloc() ?nbsp;malloc() 理内存” Q搜索文章编P, 包含了有兌些主题的更多背景知识。另外,有关堆实现和设计的详l讨Z可在下列著作中找刎ͼ“Dynamic Storage Allocation: A Survey and Critical Review”Q作?nbsp;Paul R. Wilson、Mark S. Johnstone、Michael Neely ?nbsp;David BolesQ?#8220;International Workshop on Memory Management”, 作?nbsp;Kinross, Scotland, UK, 1995 q?nbsp;9 ?http://www.cs.utexas.edu/users/oops/papers.html)Q英文)?/p>

Windows NT 的实玎ͼWindows NT 版本 4.0 和更新版本) 使用?nbsp;127 个大从 8 ?nbsp;1,024 字节?nbsp;8 字节寚w块空闲列表和一?#8220;大块”列表?#8220;大块”列表Q空闲列表[0]Q?nbsp;保存大于 1,024 字节的块。空闲列表容U了用双向链表链接在一L对象。默认情况下Q?#8220;q程?#8221;执行攉操作。(攉是将盔RI闲块合q成一个大块的操作。)攉耗费了额外的周期Q但减少了堆块的内部片?/p>

单一全局锁保护堆Q防止多U程式的使用。(请参?#8220;Server Performance and Scalability Killers”中的W一个注意事? George Reilly 所著,?nbsp;“MSDN Online Web Workshop”上(站点Q?img src="" align=absMiddle border=0>http://msdn.microsoft.com/workshop/server/iis/tencom.aspQ英文)。)单一全局锁本质上是用来保护堆数据l构Q防止跨多线E的随机存取。若堆操作太频繁Q单一全局锁会Ҏ(gu)能有不利的影响?/p>

什么是常见的堆性能问题Q?br>以下是?zhn)使用堆时会遇到的最常见问题Q?nbsp;

分配操作造成的速度减慢。光分配p费很长旉。最可能Dq行速度减慢原因是空闲列表没有块Q所以运行时分配E序代码会耗费周期L较大的空闲块Q或从后端分配程序分配新块?/p>


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


堆竞争造成的速度减慢。当两个或多个线E同时访问数据,而且一个线El进行之前必ȝ待另一个线E完成时发生竞争。竞争LDȝQ这也是目前多处理器pȝ遇到的最大问题。当大量使用内存块的应用E序?nbsp;DLL 以多U程方式q行Q或q行于多处理器系l上Q时导致速度减慢。单一锁定的用—常用的解决Ҏ(gu)—意味着使用堆的所有操作是序列化的。当{待锁定时序列化会引LE切换上下文。可以想象交叉\口闪烁的U灯处走走停停导致的速度减慢?nbsp;
竞争通常会导致线E和q程的上下文切换。上下文切换的开销是很大的Q但开销更大的是数据从处理器高速缓存中丢失Q以及后来线E复zL的数据重建?/p>

堆破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情Ş包括释放已释攄堆块或用已释放的堆块,以及块的界重写{明N题。(破坏不在本文讨论范围之内。有兛_存重写和泄漏{其他细节,请参?nbsp;Microsoft Visual C++(R) 调试文档 。)


频繁的分配和重分配造成的速度减慢。这是用脚本语a旉常普遍的现象。如字符串被反复分配Q随重分配增长和释放。不要这样做Q如果可能,量分配大字W串和用缓冲区。另一U方法就是尽量少用连接操作?br>竞争是在分配和释放操作中D速度减慢的问题。理x况下Q希望用没有竞争和快速分?释放的堆。可惜,现在q没有这L通用堆,也许来会有?/p>

在所有的服务器系l中Q如 IIS、MSProxy、DatabaseStacks、网l服务器?nbsp;Exchange 和其他), 堆锁定实在是个大瓉。处理器数越多,竞争p会恶化?/p>

量减少堆的使用
现在(zhn)明白用堆时存在的问题了,N(zhn)不x有能解决q些问题的超U魔吗Q我可希望有。但没有法能堆运行加快—因此不要期望在产品之前的最后一星期能够大ؓ改观。如果提前规划堆{略Q情况将会大大好转。调整用堆的方法,减少对堆的操作是提高性能的良斏V?/p>

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

struct ObjectA {
   // objectA 的数?nbsp;
}

struct ObjectB {
   // objectB 的数?nbsp;
}

// 同时使用 objectA ?nbsp;objectB

//
// 使用指针 
//
struct ObjectB {
   struct ObjectA * pObjA;
   // objectB 的数?nbsp;
}

//
// 使用嵌入
//
struct ObjectB {
   struct ObjectA pObjA;
   // objectB 的数?nbsp;
}

//
// 集合 – 在另一对象内?nbsp;objectA ?nbsp;objectB
//

struct ObjectX {
   struct ObjectA  objA;
   struct ObjectB  objB;
}

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


把带指针的子对象嵌入父对象。当对象中有指针Ӟ则意味着对象中有动态元素(癑ֈ之八十)和没有引用的C|。嵌入增加了位置从而减了q一步分?释放的需求。这提高应用程序的性能?/p>


合ƈ对象Ş成大对象Q聚合)。聚合减分配和释放的块的数量。如果有几个开发者,各自开发设计的不同部分Q则最l会有许多小对象需要合q。集成的挑战是要找到正的聚合边界?/p>


内联~冲够满百分之八十的需要(aka 80-20 规则Q。个别情况下Q需要内存缓冲区来保存字W串/二进制数据,但事先不知道d节数。估计ƈ内联一个大能满癑ֈ之八十需要的~冲区。对剩余的百分之二十Q可以分配一个新的缓冲区和指向这个缓冲区的指针。这P减分配和释放调用q增加数据的位置I间Q从Ҏ(gu)上提高代码的性能?/p>


在块中分配对象(块化Q。块化是以组的方式一ơ分配多个对象的Ҏ(gu)。如果对列表的项q箋跟踪Q例如对一?nbsp;{名称Q值} 对的列表Q有两种选择Q选择一是ؓ每一?#8220;名称-?#8221;对分配一个节点;选择二是分配一个能容纳Q如五个Q?#8220;名称-?#8221;对的l构。例如,一般情况下Q如果存储四对,可减少节点的数量,如果需要额外的I间数量Q则使用附加的链表指针?nbsp;
块化是友好的处理器高速缓存,特别是对?nbsp;L1-高速缓存,因ؓ它提供了增加的位|?nbsp;—不用说对于块分配,很多数据块会在同一个虚拟页中?/p>

正确使用 _amblksiz。C q行?nbsp;(CRT) 有它的自定义前端分配E序Q该分配E序从后端(Win32 堆)分配大小?nbsp;_amblksiz 的块。将 _amblksiz 讄高的D潜在地减对后端的调用次数。这只对q泛使用 CRT 的程序适用?br>使用上述技术将获得的好处会因对象类型、大及工作量而有所不同。但总能在性能和可升羃性方面有所收获。另一斚wQ代码会有点Ҏ(gu)Q但如果l过深思熟虑,代码q是很容易管理的?/p>

其他提高性能的技?br>下面是一些提高速度的技术: 

使用 Windows NT5 ?nbsp;
׃几个同事的努力和辛勤工作Q?998 q初 Microsoft Windows(R) 2000 中有了几个重大改q:

改进了堆代码内的锁定。堆代码Ҏ(gu)堆一个锁。全局锁保护堆数据l构Q防止多U程式的使用。但不幸的是Q在高通信量的情况下,堆仍受困于全局锁,D高竞争和低性能。Windows 2000 中,锁内代码的界区竞争的可能性减到最?从而提高了可׾~性?/p>


使用 “Lookaside”列表。堆数据l构对块的所有空闲项使用了大在 8 ?nbsp;1,024 字节Q以 8-字节递增Q的快速高速缓存。快速高速缓存最初保护在全局锁内。现在,使用 lookaside 列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定Q而是使用 64 位的互锁操作Q因此提高了性能?/p>


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

上述改进已在 Windows 2000 beta 2 ?nbsp;Windows NT 4.0 SP4 中用。改q后Q堆锁的竞争率显著降低。这使所?nbsp;Win32 堆的直接用户受益。CRT 堆徏立于 Win32 堆的剙Q但它用自q块堆,因而不能从 Windows NT 改进中受益。(Visual C++ 版本 6.0 也有改进的堆分配E序。)

使用分配高速缓?nbsp;
分配高速缓存允讔R速缓存分配的块,以便来重用。这能够减少对进E堆Q或全局堆)的分?释放调用的次敎ͼ也允许最大限度的重用曄分配的块。另外,分配高速缓存允许收集统计信?以便较好地理解对象在较高层次上的使用?/p>

典型圎ͼ自定义堆分配E序在进E堆的顶部实现。自定义堆分配程序与pȝ堆的行ؓ很相伹{主要的差别是它在进E堆的顶部ؓ分配的对象提供高速缓存。高速缓存设计成一套固定大(?nbsp;32 字节?4 字节?28 字节{)。这一个很好的{略Q但q种自定义堆分配E序丢失与分配和释放的对象相关的“语义信息”?nbsp;

与自定义堆分配程序相反,“分配高速缓?#8221;作ؓ每类分配高速缓存来实现。除能够提供自定义堆分配E序的所有好处之外,它们q能够保留大量语义信息。每个分配高速缓存处理程序与一个目标二q制对象兌。它能够使用一套参数进行初始化Q这些参数表Cƈ发别、对象大和保持在空闲列表中的元素的数量{。分配高速缓存处理程序对象维持自qU有I闲实体池(不超q指定的阀|q用私有保护锁。合在一P分配高速缓存和U有锁减了与主pȝ堆的通信量,因而提供了增加的ƈ发、最大限度的重用和较高的可׾~性?/p>

需要用清理程序来定期查所有分配高速缓存处理程序的zd情况q回收未用的资源。如果发现没有活动,释攑ֈ配对象的池,从而提高性能?/p>

可以审核每个分配/释放zd。第一U信息包括对象、分配和释放调用的L。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介l的许多技术之一Q这U关pd以用来减内存分配?/p>

分配高速缓存也起到了调试助手的作用Q帮助?zhn)跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪q和除没有清除的对象之外的签名,甚至能够扑ֈ切的失败的调用者?/p>

MP ?nbsp;
MP 堆是对多处理器友好的分布式分配的E序包,?nbsp;Win32 SDKQWindows NT 4.0 和更新版本)中可以得到。最初由 JVert 实现Q此处堆抽象建立?nbsp;Win32 堆程序包的顶部。MP 堆创建多?nbsp;Win32 堆,q试囑ְ分配调用分布C同堆Q以减少在所有单一锁上的竞争?/p>

本程序包是好的步?nbsp;—一U改q的 MP-友好的自定义堆分配程序。但是,它不提供语义信息和缺乏统计功能。通常?nbsp;MP 堆作?nbsp;SDK 库来使用。如果用这?nbsp;SDK 创徏可重用组Ӟ(zhn)将大大受益。但是,如果在每?nbsp;DLL 中徏立这?nbsp;SDK 库,增加工作设|?/p>

重新思考算法和数据l构 
要在多处理器机器上׾~,则算法、实现、数据结构和g必须动态׾~。请看最l常分配和释攄数据l构。试问,“我能用不同的数据l构完成此工作吗Q?#8221;例如Q如果在应用E序初始化时加蝲了只读项的列表,q个列表不必是线性链接的列表。如果是动态分配的数组非常好。动态分配的数组减内存中的堆块和片Q从而增强性能?/p>

减少需要的对象的数量减少堆分配程序的负蝲。例如,我们在服务器的关键处理\径上使用五个不同的对象,每个对象单独分配和释放。一起高速缓存这些对象,把堆调用从五个减到一个,显著减少了堆的负载,特别当每U钟处理 1,000 个以上的h时?/p>

如果大量使用“Automation”l构Q请考虑从主U代码中删除“Automation BSTR”Q或臛_避免重复?nbsp;BSTR 操作。(BSTR q接Dq多的重分配和分?释放操作。)

摘要
Ҏ(gu)有^台往往都存在堆实现Q因此有巨大的开销。每个单独代码都有特定的要求Q但设计能采用本文讨论的基本理论来减堆之间的相互作用?nbsp;

评h(hun)(zhn)的代码中堆的用?/p>


改进(zhn)的代码Q以使用较少的堆调用Q分析关键\径和固定数据l构?/p>


在实现自定义的包装程序之前用量化堆调用成本的方法?/p>


如果Ҏ(gu)能不满意,误?nbsp;OS l改q堆。更多这c请求意味着Ҏ(gu)q堆的更多关注?/p>


要求 C q行时组针对 OS 所提供的堆制作y的分配包装程序。随着 OS 堆的改进QC q行时堆调用的成本将减小?/p>


操作pȝQWindows NT 家族Q正在不断改q堆。请随时x和利用这些改q?br>Murali Krishnan ?nbsp;Internet Information Server (IIS) l的首席软g设计工程师。从 1.0 版本开始他p?nbsp;IISQƈ成功发行?nbsp;1.0 版本?nbsp;4.0 版本。Murali l织q?nbsp;IIS 性能l三q?nbsp;(1995-1998), 从一开始就影响 IIS 性能。他拥有威斯h?nbsp;Madison 大学?nbsp;M.S.和印?nbsp;Anna 大学?nbsp;B.S.。工作之外,他喜Ƣ阅诅R打排球和家庭烹饪?br>


http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835
我在学习对象的生存方式的时候见CU是在堆?stack)之中Q如?nbsp; 
CObject  object;  
q有一U是在堆(heap)?nbsp; 如下  
CObject*  pobject=new  CObject();  
 
请问  
Q?Q这两种方式有什么区别?  
Q?Q堆栈与堆有什么区别?Q?nbsp; 
 
 
---------------------------------------------------------------  
 
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  manually.  
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  result  in  memory
leak.  
3)栈内存分配运内|于处理器的指o集中Q效率很高,但是分配的内存容量有限?nbsp; 
4Q堆上分配的内存可以有我们自己决定,使用非常灉|?nbsp; 
---------------------------------------------------------------  



沙漠里的 2009-05-19 16:53 发表评论
]]>
详解函数调用U定http://m.shnenglu.com/zjl-1026-2001/archive/2009/05/15/83062.html沙漠里的沙漠里的Fri, 15 May 2009 09:30:00 GMThttp://m.shnenglu.com/zjl-1026-2001/archive/2009/05/15/83062.htmlhttp://m.shnenglu.com/zjl-1026-2001/comments/83062.htmlhttp://m.shnenglu.com/zjl-1026-2001/archive/2009/05/15/83062.html#Feedback0http://m.shnenglu.com/zjl-1026-2001/comments/commentRss/83062.htmlhttp://m.shnenglu.com/zjl-1026-2001/services/trackbacks/83062.html在编写windowsE序Ӟ我们l常发现一些函数的前面带有WINAPI{的关键?不知道这hq是否准,h白的读者联pL人更?img height=20 src="http://m.shnenglu.com/Emoticons/QQ/13.gif" width=20 border=0>)Q如windows的消息响应函数定义如下:

1 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

q里的LRESULT在windows中被定义为long型,而CALLBACK则被定义成了__stacallQ仔l看了一下,在WINDEF.H中还包含如下定义Q?br>
1#define CALLBACK    __stdcall
2#define WINAPI      __stdcall
3#define WINAPIV     __cdecl
4#define APIENTRY    WINAPI
5#define APIPRIVATE  __stdcall
6#define PASCAL      __stdcall

那么Q这里的__stacall、__cdecl到底是什么意思呢Q又有什么作用呢Q我l过查找相关资料对其有了些许显的了解,q里与大家一起分享?br>
我们知道Q在C语言中假设我们有q样一个函数定义:
int function_add(int a, int b);
那么只要?br>
1int x = 3, y = 5;
2int result = function_add(x, y);
q样的方式就可以对函数进行调用了。但是,在计机中,当高U语aE序被编译成计算机可以识别的机器码时Q有一个问题就凸现出来Q在CPU中,计算机没有办法知道一个函数调用需要多个参数、这些参数是什么样的,也没有硬件可以保存这些参数。也是_计算机ƈ不知道应该怎么l这个函C递参敎ͼ传递参数的工作必须由函数调用者和函数本n来协调。ؓ此,计算机提供了一U被UCؓ栈的数据l构来支持函数的参数传递?br>
栈是一U先q后出的数据l构Q栈有一个存储区、一个栈指针。栈指针指向堆栈中W一个可用的数据?被称为栈?。用户可以在栈顶的上方向堆栈中加入数据,q个操作被称为压?Push)Q压栈以后,栈顶自动变成新加入数据项的位|,栈顶指针也随之修攏V用户也可以从堆栈中取出栈顶元素Q这个操作被UCؓ弹出?pop)Q弹出栈以后Q栈的下一个元素变成栈Ӟ栈顶指针随之修改?br>
函数调用Ӟ调用者依ơ把参数压栈Q然后调用函敎ͼ函数被调用以后,在堆栈中取出数据Qƈq行计算。函数计结束以后,或者调用者、或者函数本w修改堆栈,使堆栈恢复原状。问题的关键在q里Q到底应该如何清除栈呢?

函数调用需要进行参C递,在参C递过E中有两个很重要的问题必d到明说明:
    1. 当参C数多于一个时Q按照什么样的顺序把参数压入栈中
    2. 函数调用后,p来负责把堆栈恢复原状

在高U语a中,函数调用U定是用来说明q两个问题的。常见的函数调用U定有:
        stdcall
        cdecl
        fastcall
        thiscall
        naked call

下面一一q行介绍?br>
一、stdcall调用U定

stdcallQ也可写作__stdcallQ很多时候被UCؓpascal调用U定Q因为pascal是早期很常见的一U教学用计算机程序设计语aQ其语法严}Q用的函数调用U定是stdcall。几乎我们写的每一个WINDOWS API函数都是__stdcallcd的。在Microsoft C++pd的C/C++~译器中Q常常用PASCAL宏来声明q个调用U定Q类似的宏还有WINAPI和CALLBACK(如文章开头引用的在WINDEF.H头文件中的定??br>
stdcall调用U定声明的语法ؓQ以前面的function_add函数ZQ:
int __stdcall function_add(int a,int b);

stdcall调用U定意味着Q?br>(1) 参数从右向左压入堆栈
(2) 函数自n修改堆栈
(3) 函数名自动加前导的下划线Q后面紧跟一个@W号Q其后紧跟着参数的大?br>
以上q这个函Cؓ例,参数b首先被压栈,然后是参数aQ函数调用function_add(1, 2)调用处翻译成汇编语言变成:

      push       2                      // W二个参数入?br>      push       1                      // W一个参数入?br>      call         function_add    // 调用参数Q注意此时自动把cs:eip入栈

而对于函数自w,则可译为:
      push       ebp         // 保存ebp寄存器,该寄存器用来保存堆栈的栈顶指针Q可以在函数退出时恢复
      mov    ebp, esp        // 保存堆栈指针
      mov    eax,[ebp + 8H]      // 堆栈中ebp指向位置之前依次保存有ebp, cs:eip, a, b, ebp +8指向a
      add     eax,[ebp + 0CH]      // 堆栈中ebp + 12处保存了b
      mov    esp, ebp       //  恢复esp
      pop     ebp
      ret       8

而在~译Ӟq个函数的名字被译?a href="mailto:_function@8">_function@8

  注意不同~译器会插入自己的汇~代码以提供~译的通用性,但是大体代码如此。其中在函数开始处保留esp到ebp中,在函数结束恢复是~译器常用的Ҏ(gu)?/p>

  从函数调用看Q??依次被pushq堆栈,而在函数中又通过相对于ebp(卛_q函数时的堆栈指针)的偏U量存取参数。函数结束后Qret 8表示清理8个字节的堆栈Q函数自己恢复了堆栈?br>
׃不同的编译器产生栈的方式不尽相同Q调用者就不一定能够正常的完成堆栈的清除工作,但函数本w自己可以解x除工作,所以,在跨q_的程序开发中的函数调用,我们通常都用__stdcallU定Qwindows下的l大多数函数也都是stdcall调用。既然如此,Z么还需要__cdecl呢?别着急,接着往下看?br>
二、cdecl调用U定

cdeclQ也可写作__cdeclQ又UCؓC调用U定Q是C/C++语言和MFCE序默认~省的调用约定,它的定义语法是:

    int function (int a ,int b)         //不加修饰是C调用U定
     int __cdecl function(int a,int b)  //明确指出C调用U定


采用__cdeclU定Ӟ函数参数按照从右到左的顺序入栈,q且p用函数者把参数弹出栈以清理堆栈。因此,实现可变参数的函数只能用该调用U定。由于这U变化,C调用U定允许函数的参数的个数是不固定的,q也是C语言的一大特艌Ӏ同Ӟ׃每一个用__cdeclU定的函数都要包含清理堆栈的代码Q所以生的可执行文件大会比较大。__cdecl可以写成_cdecl?

对于前面的function函数Q用cdecl后的汇编码变成:

调用?br>  push   1
  push   2
  call     function
  add esp, 8      // 注意Q这里调用者在恢复堆栈

  被调用函数_function?br>  push    ebp      // 保存ebp寄存器,该寄存器用来保存堆栈的栈顶指针Q可以在函数退出时恢复
  mov    ebp,esp     // 保存堆栈指针
  mov eax,[ebp + 8H]  // 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
  add eax,[ebp + 0CH]    // 堆栈中ebp + 12处保存了b
  mov esp,ebp     // 恢复esp
  pop ebp
  ret         //  注意Q这里没有修改堆?/p>


不写了,累得慌,呵呵 转蝲两篇文章?br>

__stdcall,__cdecl,_cdecl,_stdcall,。__fastcall,_fastcall 区别?nbsp;

1.

今天写线E函数时Q发现msdn中对ThreadProc的定义有要求QDWORD WINAPI ThreadProc(LPVOID lpParameter);

不解Z么要用WINAPI宏定义,查了后发C面的定义。于是乎需要区别__stdcall和__cdecl两者的区别Q?#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
#define cdecl _cdecl
#ifndef CDECL
#define CDECL _cdecl
#endif

几乎我们写的每一个WINDOWS API函数都是__stdcallcd的,首先Q需要了解两者之间的区别Q?WINDOWS的函数调用时需要用到栈QSTACKQ一U先入后出的存储l构Q。当函数调用完成后,栈需要清楚,q里是问题的关键,如何清除Q? 如果我们的函C用了_cdeclQ那么栈的清除工作是p用者,用COM的术语来讲就是客h完成的。这样带来了一个棘手的问题Q不同的~译器生栈的方式不相同,那么调用者能否正常的完成清除工作呢?{案是不能?如果使用__stdcallQ上面的问题p决了Q函数自px除工作。所以,在跨Q开发)q_的调用中Q我们都使用__stdcallQ虽然有时是以WINAPI的样子出玎ͼ。那么ؓ什么还需要_cdecl呢?当我们遇到这L函数如fprintf()它的参数是可变的Q不定长的,被调用者事先无法知道参数的长度Q事后的清除工作也无法正常的q行Q因此,q种情况我们只能使用_cdecl。到q里我们有一个结论,如果你的E序中没有涉及可变参敎ͼ最好用__stdcall关键字?/p>

2.

__cdecl,__stdcall是声明的函数调用协议.主要是传参和Ҏ(gu)斚w的不?一般c++用的是__cdecl,windows里大都用的是__stdcall(API)

__cdecl是C/C++和MFCE序默认使用的调用约定,也可以在函数声明时加上__cdecl关键字来手工指定。采用__cdeclU定Ӟ函数参数按照从右到左的顺序入栈,q且p用函数者把参数弹出栈以清理堆栈。因此,实现可变参数的函数只能用该调用U定。由于每一个用__cdeclU定的函数都要包含清理堆栈的代码Q所以生的可执行文件大会比较大。__cdecl可以写成_cdecl?
__stdcall调用U定用于调用Win32 API函数。采用__stdcallU定Ӟ函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定。由于函C本n知道传进来的参数个数Q因此被调用的函数可以在q回前用一条ret n指o直接清理传递参数的堆栈。__stdcall可以写成_stdcall?
__fastcallU定用于Ҏ(gu)能要求非常高的场合。__fastcallU定函数的从左边开始的两个大小不大?个字节(DWORDQ的参数分别攑֜ECX和EDX寄存器,其余的参C旧自叛_左压栈传送,被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall

3.

__stdcall:

_stdcall 调用U定相当?6位动态库中经怋用的PASCAL调用U定?/p>

 
?2位的VC++5.0中PASCAL调用U定不再被支持(实际上它已被定义为__stdcall。除了__pascal外,__fortran和__syscall也不被支持)Q取而代之的是__stdcall调用U定。两者实质上是一致的Q即函数的参数自叛_左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明Q?/p>

_stdcall是PascalE序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC函数编译后会在函数名前面加上下划线前缀Q在函数名后加上"@"和参数的字节数?/p>

_cdecl:

_cdecl c调用U定, 按从双左的序压参数入栈,p用者把参数弹出栈。对于传送参数的内存栈是p用者来l护的(正因为如此,实现可变参数的函数只能用该调用U定Q。另外,在函数名修饰U定斚w也有所不同?/p>

_cdecl是C和CQ+E序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以生的可执行文件大会比调用_stdcall函数的大。函数采用从叛_左的压栈方式。VC函数编译后会在函数名前面加上下划线前缀。是MFC~省调用U定?/p>

__fastcall:

__fastcall调用U定??如其名,它的主要特点是快,因ؓ它是通过寄存器来传送参数的Q实际上Q它用ECX和EDX传送前两个双字QDWORDQ或更小的参敎ͼ剩下的参C旧自叛_左压栈传送,被调用的函数在返回前清理传送参数的内存栈)Q在函数名修饰约定方面,它和前两者均不同?/p>

_fastcall方式的函数采用寄存器传递参敎ͼVC函数编译后会在函数名前面加?@"前缀Q在函数名后加上"@"和参数的字节数?/p>

thiscall:

thiscall仅仅应用?C++"成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定?/p>

naked call:

采用1-4的调用约定时Q如果必要的话,q入函数时编译器会生代码来保存ESIQEDIQEBXQEBP寄存器,退出函数时则生代码恢复这些寄存器的内宏V?/p>

naked call不生这L代码。naked call不是cd修饰W,故必d_declspec共同使用?/p>

另附:

关键?__stdcall、__cdecl和__fastcall可以直接加在要输出的函数前,也可以在~译环境的Setting...\C/C++ \Code Generationw择。当加在输出函数前的关键字与~译环境中的选择不同Ӟ直接加在输出函数前的关键字有效。它们对应的命o行参数分别ؓ/Gz?Gd?Gr。缺省状态ؓ/GdQ即__cdecl?/p>

要完全模仿PASCAL调用U定首先必须使用__stdcall调用U定Q至于函数名修饰U定Q可以通过其它Ҏ(gu)模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏Q它可以出函数译成适当的调用约定,在WIN32中,它被定义为__stdcall。用WINAPI宏可以创qAPIs?/p>

名字修饰U定

1、修饰名(Decoration name)
“C”或?#8220;C++”函数在内部(~译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字W串。有些情况下使用函数的修饰名是必要的Q如在模块定义文仉头指定输?#8220;C++”重蝲函数、构造函数、析构函敎ͼ又如在汇~代码里调用“C””?#8220;C++”函数{?

修饰名由函数名、类名、调用约定、返回类型、参数等共同军_?

2、名字修饰约定随调用U定和编译种c?C或C++)的不同而变化。函数名修饰U定随编译种cd调用U定的不同而不同,下面分别说明?

a、C~译时函数名修饰U定规则Q?

__stdcall调用U定在输出函数名前加上一个下划线前缀Q后面加上一?#8220;@”W号和其参数的字节数Q格式ؓ_functionname@number?

__cdecl调用U定仅在输出函数名前加上一个下划线前缀Q格式ؓ_functionname?/p>

__fastcall调用U定在输出函数名前加上一?#8220;@”W号Q后面也是一?#8220;@”W号和其参数的字节数Q格式ؓ@functionname@number?

它们均不改变输出函数名中的字W大写Q这和PASCAL调用U定不同QPASCALU定输出的函数名无Q何修C全部大写?

b、C++~译时函数名修饰U定规则Q?

__stdcall调用U定Q?
1、以“?”标识函数名的开始,后跟函数名;
2、函数名后面?#8220;@@YG”标识参数表的开始,后跟参数表;
3、参数表以代可C:
X--void Q?
D--charQ?
E--unsigned charQ?
F--shortQ?
H--intQ?
I--unsigned intQ?
J--longQ?
K--unsigned longQ?
M--floatQ?
N--doubleQ?
_N--boolQ?
....
PA--表示指针Q后面的代号表明指针cdQ如果相同类型的指针q箋出现Q以“0”代替Q一?#8220;0”代表一ơ重复;
4、参数表的第一ؓ该函数的q回值类型,其后依次为参数的数据cd,指针标识在其所指数据类型前Q?
5、参数表后以“@Z”标识整个名字的结束,如果该函数无参数Q则?#8220;Z”标识l束?

其格式ؓ“?functionname@@YG*****@Z”?#8220;?functionname@@YG*XZ”Q例?
int Test1Qchar *var1,unsigned longQ?----“?Test1@@YGHPADK@Z”
void Test2Q) -----“?Test2@@YGXXZ”

__cdecl调用U定Q?
规则同上面的_stdcall调用U定Q只是参数表的开始标识由上面?#8220;@@YG”变ؓ“@@YA”?

__fastcall调用U定Q?
规则同上面的_stdcall调用U定Q只是参数表的开始标识由上面?#8220;@@YG”变ؓ“@@YI”?
VC++对函数的省缺声明?#8220;__cedcl“,只能被C/C++调用.

CB在输出函数声明时使用4U修饰符?
//__cdecl
cb的默认|它会在输出函数名前加_Qƈ保留此函数名不变Q参数按照从叛_左的序依次传递给栈,也可以写成_cdecl和cdecl形式?
//__fastcall
她修饰的函数的参数将肯呢感C用寄存器来处理,其函数名前加@Q参数按照从左到右的序压栈Q?
//__pascal
它说明的函数名用Pascal格式的命名约定。这时函数名全部大写。参数按照从左到右的序压栈Q?
//__stdcall
使用标准U定的函数名。函数名不会改变。用__stdcall修饰时。参数按照由叛_左的序压栈Q也可以是_stdcallQ?/p>

VC++对函数的省缺声明?__cedcl",只能被C/C++调用.

 

 

注意Q?/p>

1、_beginthread需要__cdecl的线E函数地址Q_beginthreadex和CreateThread需要__stdcall的线E函数地址?/p>

2、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定义:

 #define CALLBACK __stdcall

 #define WINAPI  __stdcall

3、extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);

   typedef int (__cdecl*FunPointer)(int a, int b);

   修饰W的书写序如上?/p>

4、extern "C"的作用:如果Add(int a, int b)是在c语言~译器编译,而在c++文g使用Q则需要在c++文g中声明:extern "C" Add(int a, int b)Q因为c~译器和c++~译器对函数名的解释不一Pc++~译器解释函数名的时候要考虑函数参数Q这h了方便函数重载,而在c语言中不存在函数重蝲的问题)Q用extern "C"Q实质就是告诉c++~译器,该函数是c库里面的函数。如果不使用extern "C"则会出现链接错误?/p>

一般象如下使用Q?/p>

#ifdef _cplusplus

#define EXTERN_C extern "C"

#else

#define EXTERN_C extern

#endif

#ifdef _cplusplus

extern "C"{

#endif

 EXTERN_C int func(int a, int b);

#ifdef _cplusplus

}

#endif

5、MFC提供了一些宏Q可以用AFX_EXT_CLASS来代替__declspec(DLLexport)Qƈ修饰cdQ从而导出类QAFX_API_EXPORT来修饰函敎ͼAFX_DATA_EXPORT来修饰变?/p>

AFX_CLASS_IMPORTQ__declspec(DLLexport)

AFX_API_IMPORTQ__declspec(DLLexport)

AFX_DATA_IMPORTQ__declspec(DLLexport)

AFX_CLASS_EXPORTQ__declspec(DLLexport)

AFX_API_EXPORTQ__declspec(DLLexport)

AFX_DATA_EXPORTQ__declspec(DLLexport)

AFX_EXT_CLASSQ?ifdef _AFXEXT

   AFX_CLASS_EXPORT

        #else

   AFX_CLASS_IMPORT

6、DLLMain负责初始?Initialization)和结?Termination)工作Q每当一个新的进E或者该q程的新的线E访问DLLӞ或者访问DLL的每一个进E或者线E不再用DLL或者结束时Q都会调用DLLMain。但是,使用TerminateProcess或TerminateThreadl束q程或者线E,不会调用DLLMain?/p>

7、一个DLL在内存中只有一个实?/p>

DLLE序和调用其输出函数的程序的关系Q?/p>

1)、DLL与进E、线E之间的关系

DLL模块被映到调用它的q程的虚拟地址I间?/p>

DLL使用的内存从调用q程的虚拟地址I间分配Q只能被该进E的U程所讉K?/p>

DLL的句柄可以被调用q程使用Q调用进E的句柄可以被DLL使用?/p>

DLLDLL可以有自q数据D,但没有自q堆栈Q用调用进E的栈,与调用它的应用程序相同的堆栈模式?/p>

2)、关于共享数据段

DLL定义的全局变量可以被调用进E访问;DLL可以讉K调用q程的全局数据。用同一DLL的每一个进E都有自qDLL全局变量实例。如果多个线Eƈ发访问同一变量Q则需要用同步机Ӟ对一个DLL的变量,如果希望每个使用DLL的线E都有自q|则应该用线E局部存?TLSQThread Local Strorage)?br>



论函数调用约?/strong>


在C语言中,假设我们有这L一个函敎ͼ
  
  int function(int a,int b)
  
  调用时只要用result = function(1,2)q样的方式就可以使用q个函数。但是,当高U语a被编译成计算机可以识别的机器码时Q有一个问题就凸现出来Q在CPU中,计算机没有办法知道一个函数调用需要多个、什么样的参敎ͼ也没有硬件可以保存这些参数。也是_计算Z知道怎么l这个函C递参敎ͼ传递参数的工作必须由函数调用者和函数本n来协调。ؓ此,计算机提供了一U被UCؓ栈的数据l构来支持参C递?/p>

  栈是一U先q后出的数据l构Q栈有一个存储区、一个栈指针。栈指针指向堆栈中W一个可用的数据(被称为栈Ӟ。用户可以在栈顶上方向栈中加入数据,q个操作被称为压?Push)Q压栈以后,栈顶自动变成新加入数据项的位|,栈顶指针也随之修攏V用户也可以从堆栈中取走栈顶Q称为弹出栈(pop)Q弹出栈后,栈顶下的一个元素变成栈Ӟ栈顶指针随之修改?/p>

  函数调用Ӟ调用者依ơ把参数压栈Q然后调用函敎ͼ函数被调用以后,在堆栈中取得数据Qƈq行计算。函数计结束以后,或者调用者、或者函数本w修改堆栈,使堆栈恢复原装?/p>

  在参C递中Q有两个很重要的问题必须得到明确说明Q?br>  
  当参C数多于一个时Q按照什么顺序把参数压入堆栈
  函数调用后,p来把堆栈恢复原装
  在高U语a中,通过函数调用U定来说明这两个问题。常见的调用U定有:

  stdcall
  cdecl
  fastcall
  thiscall
  naked call

 

  stdcall调用U定
  stdcall很多时候被UCؓpascal调用U定Q因为pascal是早期很常见的一U教学用计算机程序设计语aQ其语法严}Q用的函数调用U定是stdcall。在Microsoft C++pd的C/C++~译器中Q常常用PASCAL宏来声明q个调用U定Q类似的宏还有WINAPI和CALLBACK?/p>

  stdcall调用U定声明的语法ؓ(以前文的那个函数ZQ:
  
  int __stdcall function(int a,int b)
  
  stdcall的调用约定意味着Q?Q参C叛_左压入堆栈,2Q函数自w修改堆?3)函数名自动加前导的下划线Q后面紧跟一个@W号Q其后紧跟着参数的尺?/p>

  以上q这个函Cؓ例,参数b首先被压栈,然后是参数aQ函数调用function(1,2)调用处翻译成汇编语言变成:

  push 2        W二个参数入?br>  push 1        W一个参数入?br>  call function    调用参数Q注意此时自动把cs:eip入栈

  而对于函数自w,则可以翻译ؓQ?
  push ebp       保存ebp寄存器,该寄存器用来保存堆栈的栈顶指针Q可以在函数退出时恢复
  mov ebp, esp    保存堆栈指针
  mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp, cs:eip, a, b, ebp +8指向a
  add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
  mov esp, ebp    恢复esp
  pop ebp
  ret 8

  而在~译Ӟq个函数的名字被译?a href="mailto:_function@8">_function@8

  注意不同~译器会插入自己的汇~代码以提供~译的通用性,但是大体代码如此。其中在函数开始处保留esp到ebp中,在函数结束恢复是~译器常用的Ҏ(gu)?/p>

  从函数调用看Q??依次被pushq堆栈,而在函数中又通过相对于ebp(卛_q函数时的堆栈指针)的偏U量存取参数。函数结束后Qret 8表示清理8个字节的堆栈Q函数自己恢复了堆栈?/p>

  
  cdecl调用U定
  cdecl调用U定又称为C调用U定Q是C语言~省的调用约定,它的定义语法是:

  int function (int a ,int b) //不加修饰是C调用U定
  int __cdecl function(int a,int b)//明确指出C调用U定

  在写本文ӞZ我的意料Q发现cdecl调用U定的参数压栈顺序是和stdcall是一LQ参数首先由叛_左压入堆栈。所不同的是Q函数本w不清理堆栈Q调用者负责清理堆栈。由于这U变化,C调用U定允许函数的参数的个数是不固定的,q也是C语言的一大特艌Ӏ对于前面的function函数Q用cdecl后的汇编码变成:

  调用?br>  push 1
  push 2
  call function
  add esp, 8     注意Q这里调用者在恢复堆栈

  被调用函数_function?br>  push ebp       保存ebp寄存器,该寄存器用来保存堆栈的栈顶指针Q可以在函数退出时恢复
  mov ebp,esp     保存堆栈指针
  mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a
  add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b
  mov esp,ebp     恢复esp
  pop ebp
  ret         注意Q这里没有修改堆?/p>

  MSDN中说Q该修饰自动在函数名前加前导的下划线Q因此函数名在符可中被记录为_functionQ但是我在编译时g没有看到q种变化?/p>

  ׃参数按照从右向左序压栈Q因此最开始的参数在最接近栈顶的位|,因此当采用不定个数参数时Q第一个参数在栈中的位|肯定能知道Q只要不定的参数个数能够Ҏ(gu)W一个后者后l的明确的参数确定下来,可以用不定参敎ͼ例如对于CRT中的sprintf函数Q定义ؓQ?
  int sprintf(char* buffer,const char* format,...)
  ׃所有的不定参数都可以通过format定Q因此用不定个数的参数是没有问题的?/p>

  fastcall
  fastcall调用U定和stdcallcMQ它意味着Q?
  
  函数的第一个和W二个DWORD参数Q或者尺寸更的Q通过ecx和edx传递,其他参数通过从右向左的顺序压?
  被调用函数清理堆?
  函数名修改规则同stdcall
  其声明语法ؓQint fastcall function(int a, int b)

  thiscall
  thiscall是唯一一个不能明指明的函数修饰Q因为thiscall不是关键字。它是C++cL员函数缺省的调用U定。由于成员函数调用还有一个this指针Q因此必ȝD处理,thiscall意味着Q?

  参数从右向左入栈
  如果参数个数定Qthis指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入堆栈。对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈Z说明q个调用U定Q定义如下类和用代码:

  class A
  {
  public:
    int function1(int a,int b);
    int function2(int a,...);
  };

  int A::function1 (int a,int b)
  {
    return a+b;
  }

  #include <stdarg.h>
  int A::function2(int a,...)
  {
    va_list ap;
    va_start(ap,a);
    int i;
    int result = 0;
    for(i = 0 ; i < a ; i ++)
    {
     result += va_arg(ap,int);
    }
    return result;
  }

  void callee()
  {
    A a;
    a.function1(1, 2);
    a.function2(3, 1, 2, 3);
  }

callee函数被翻译成汇编后就变成Q?
  //函数function1调用
  00401C1D  push    2
  00401C1F  push    1
  00401C21  lea     ecx,[ebp-8]
  00401C24  call    function1     注意Q这里this没有被入?/p>

  //函数function2调用
  00401C29  push    3
  00401C2B  push    2
  00401C2D  push    1
  00401C2F  push    3
  00401C31  lea     eax, [ebp-8]    q里引入this指针
  00401C34  push    eax
  00401C35  call    function2
  00401C3A  add     esp, 14h
  
  可见Q对于参C数固定情况下Q它cM于stdcallQ不定时则类似cdecl

  naked call
  q是一个很见的调用约定,一般程序设计者徏议不要用。编译器不会l这U函数增加初始化和清理代码,更特D的是,你不能用returnq回q回|只能用插入汇~返回结果。这一般用于实模式驱动E序设计Q假讑֮义一个求和的加法E序Q可以定义ؓQ?

  __declspec(naked) int add(int a,int b)
  {
    __asm mov eax,a
    __asm add eax,b
    __asm ret
  }

  注意Q这个函数没有显式的returnq回|q回通过修改eax寄存器实玎ͼ而且q退出函数的ret指o都必L式插入。上面代码被译成汇~以后变成:

  mov eax,[ebp+8]
  add eax,[ebp+12]
  ret 8

  注意q个修饰是和__stdcall及cdecll合使用的,前面是它和cdecll合使用的代码,对于和stdcalll合的代码,则变成:

  __declspec(naked) int __stdcall function(int a,int b)
  {
    __asm mov eax,a
    __asm add eax,b
    __asm ret 8    //注意后面?
  }

  至于q种函数被调用,则和普通的cdecl及stdcall调用函数一致?/p>

  函数调用U定D的常见问?br>  如果定义的约定和使用的约定不一_则将D堆栈被破坏,D严重问题Q下面是两种常见的问题:

  函数原型声明和函C定义不一?
  DLL导入函数时声明了不同的函数约?
  以后者ؓ例,假设我们在dllU声明了一U函CؓQ?

  __declspec(dllexport) int func(int a,int b);//注意Q这里没有stdcallQ用的是cdecl
  使用时代码ؓQ?

  typedef int (*WINAPI DLLFUNC)func(int a,int b);
  hLib = LoadLibrary(...);

  DLLFUNC func = (DLLFUNC)GetProcAddress(...)//q里修改了调用约?br>  result = func(1,2);//D错误

  ׃调用者没有理解WINAPI的含义错误的增加了这个修饎ͼ上述代码必然D堆栈被破坏,MFC在编译时插入的checkesp函数告诉你Q堆栈被破坏









 



沙漠里的 2009-05-15 17:30 发表评论
]]>
如何创徏自己的dllhttp://m.shnenglu.com/zjl-1026-2001/archive/2009/04/28/81304.html沙漠里的沙漠里的Tue, 28 Apr 2009 04:10:00 GMThttp://m.shnenglu.com/zjl-1026-2001/archive/2009/04/28/81304.htmlhttp://m.shnenglu.com/zjl-1026-2001/comments/81304.htmlhttp://m.shnenglu.com/zjl-1026-2001/archive/2009/04/28/81304.html#Feedback0http://m.shnenglu.com/zjl-1026-2001/comments/commentRss/81304.htmlhttp://m.shnenglu.com/zjl-1026-2001/services/trackbacks/81304.html前几天有个朋友问道这个问题,l果因ؓ以前从没搞过q个Q对vs2005也不熟?zhn)Q竟׃2个小时才搞定Q?img height=20 src="http://m.shnenglu.com/Emoticons/QQ/07.gif" width=20 border=0>?/pre>
特地拿来与大家分享,希望能给像我q样的菜鸟们一些帮助,O(∩_∩)O
【第一步】创qdll
1.打开vs2005Q选择菜单【File-New-Project】,在弹出对话框中选择[Visual C++]下的[Win32]-[Win32 Console Application]Q输入工E名后确认?/pre>
2.在弹出的对话框中选择[next]Q在Application Settiongs中选择Application type为DllQAdditional options选择Empty projectQ然后点Finish?/pre>
q时创Z一个空的可以生成dll文g的工E?/pre>
3.在工E中d一个头文g(q里为dll_test.h)Q在头文件中写入如下内容Q?/pre>
 1 #ifndef _DLL_TUTORIAL_H
 2 #define _DLL-TUTORIAL_H
 3 
 4 #include<iostream>
 5 
 6 #if defined DLL_EXPORT
 7   #define DECLDIR _declspec(dllexport)
 8 #else
 9   #define DECLDIR _declspec(dllimport)
10 #endif
11 
12 extern "C"
13 {
14   DECLDIR int Add(int a, int b);
15   DECLDIR void Function(void);
16 }
17
18 #endif
q里要说明的是:
在VC中有两个Ҏ(gu)来导出dll中定义的函数Q?/pre>
  (1) 使用__declspec,q是一个Microsoft定义的关键字?/pre>
  (2) 创徏一个模板定义文?Module-Definition FileQ即.DEF)?/pre>
  W一U方法稍E比W二U方法简单,在这里我们用的是第一U方法?/pre>
    __declspec(dllexport)函数的作用是导出函数W号到在你的Dll中的一个存储类里去?/pre>
当下面一行被定义时我定义DECLDIR宏来q行q个函数?/pre>
    #define DLL_EXPORT
在此情况下你导出函数Add(int a,int b)和Function().
4.创徏一个源文g(名字为dll_test.cpp)Q内容如下:
 
 1 #include <iostream>
 2 #define DLL_EXPORT
 3 #include "dll_test.h"
 4 
 5 extern "C"
 6 {
 7         // 定义了(DLL中的Q所有函?/span>
 8     DECLDIR int Add( int a, int b )
 9     {
10         return( a + b );
11     }
12     
13     DECLDIR void Function( void )
14     {
15         std::cout << "DLL Called!" << std::endl;
16     }
17 }
18 
【第二步】用创建好的DLL
现在已经创徏了DLLQ那么如何在一个应用程序中使用它呢Q?/pre>
当DLL被生成后Q它创徏了一?dll文g和一?libQ这两个都是使用dll旉要用到的?/pre>
在具体介l之前先看一下dll的链接方式?/pre>
(1)隐式q接
q里有两个方法来载入一个DLLQ一个方法是只链接到.lib文gQƈ?dll文g攑ֈ要用这个DLL的项目\径中?/pre>
因此Q创Z个新的空的Win32控制台项目ƈd一个源文g。将我们创徏好的DLL攑օ与新目相同的目录下。同时我们还必须链接到dll_test.lib文g?/pre>
可以在项目属性中讄Q也可以在源E序中用下面的语句来链接Q?
#pragma comment(lib, "dll_test.lib")
最后,我们q要在新的win32控制台项目中包含前面的dll_test.h头文件。可以把q个头文件放到新建win32控制台项目的目录中然后在E序中加入语句:
#include "dll_test.h"
新项目代码如下:
#include<iostream>
#include "DLLTutorial.h"
int main()
{
  Function();
  std::cout<< Add(32, 56)<< endl;
  return 0;
}
(2)昄链接
E微复杂一点的加蝲DLL的方法需要用到函数指针和一些Windows函数。但是,通过q种载入DLL的方法,不需要DLL?lib文g或头文gQ而只需要DLL卛_?/pre>
下面列出一些代码:
/****************************************************************/
#include <iostream>
#include <windows.h>
typedef int (*AddFunc)(int,int);
typedef void (*FunctionFunc)();
int main()
{
AddFunc _AddFunc;
   FunctionFunc _FunctionFunc;
   HINSTANCE hInstLibrary = LoadLibrary("DLL_Tutorial.dll");
   if (hInstLibrary == NULL)
{
FreeLibrary(hInstLibrary);
}
   _AddFunc = (AddFunc)GetProcAddress(hInstLibrary, "Add");
   _FunctionFunc = (FunctionFunc)GetProcAddress(hInstLibrary, "Function");
   if ((_AddFunc == NULL) || (_FunctionFunc == NULL))
{
FreeLibrary(hInstLibrary);
}
   std::cout << _AddFunc(23, 43) << std::endl;
   _FunctionFunc();
   std::cin.get();
   FreeLibrary(hInstLibrary);
   return(1);
}
/*******************************************************************/
首先可以看到Q这里包括进了windows.h头文Ӟ同时L了对dll_test.h头文件的包含。原因很单:因ؓwindows.h包含了一些Windows函数Q?/pre>
它也包含了一些将会用到的Windows特定变量。可以去掉DLL的头文gQ因为当使用q个Ҏ(gu)载入DLL时ƈ不需要其头文件?/pre>
下面你会看到Q以下面形式的一块古灵_怪的代码:
    typedef int (*AddFunc)(int,int);
typedef void (*FunctionFunc)();
    q是函数指针。因是一个关于DLL的自学指南,深入探究函数指针出了本指南的范_因此Q现在我们只把它们当作DLL包含的函数的别名?/pre>
    我喜Ƣ在N?#8220;Func”命名之?int,int)部分是这个函数的参数部分Q比如,Add函数要获得两个整敎ͼ因此Q你需要它?/pre>
Q译注:?int,int)部分Q作为函数指针的参数。Function函数没有参数Q因此你让它为空。main()部分中的前面两行是声明函数指针以使得你可
以认为它们等同于DLL内部的函数。我只是喜欢预先定义它们?/pre>
      一个HINSTANCE是一个Windows数据cdQ是一个实例的句柄Q在此情况下Q这个实例将是这个DLL。你可以通过使用函数LoadLibrary()获得DLL?/pre>
实例Q它获得一个名UC为参数?/pre>
     在调用LoadLibrary函数后,你必需查看一下函数返回是否成功。你可以通过查HINSTANCE是否{于NULLQ在Windows.h中定义ؓ0或Windows.h?/pre>
含的一个头文gQ来查看其是否成功。如果其{于NULLQ该句柄是无效的,q且你必需释放q个库。换句话_你必需释放DLL获得的内存?/pre>
      如果函数q回成功Q你的HINSTANCE包含了指向DLL的句柄。一旦你获得了指向DLL的句柄,你现在可以从DLL中重新获得函数?/pre>
     Zq样作,你必M用函数GetProcAddress()Q它?yu)DLL的句柄(你可以用HINSTANCEQ和函数的名UC为参数。你可以让函数指针获得由
GetProcAddress()q回的|同时你必需GetProcAddress()转换为那个函数定义的函数指针。D个例子,对于Add()函数Q你必需GetProcAddress()
转换为AddFuncQ这是它知道参数及q回值的原因。现在,最好先定函数指针是否{于NULL以及它们拥有DLL的函数?/pre>
     q只是一个简单的if语句Q如果其中一个等于NULLQ你必需如前所q释攑ֺ。一旦函数指针拥有DLL的函敎ͼ你现在就可以使用它们了,但是q里有一?/pre>
需要注意的地方Q你不能使用函数的实际名Uͼ你必需使用函数指针来调用它们。在那以后,所有你需要做的是释放库如此而已?/pre>
     现在你知道了DLL的一些基本知识。你知道如何创徏它们Q你也知道如何用两种不同的方法链接它们。这里仍然有更多的东襉K要我们学习,但我把它们留l你们自己探索了和更的作者来写了?


沙漠里的 2009-04-28 12:10 发表评论
]]> ɫۺϾþþþۺ99| þþþһvr| 91Ʒۿ91þþþþ| þþþƷҰ| ĻƷѾþ| Ʒξþþþ99վ| ҹҹþ| þ91Ʒ91| Ʒþþþþô| þþ91뾫ƷHD| Ů޾Ʒþþۺ| þ99޸ۿҳ| vvaþ| ޾ƷþþþĻ69 | ˾þþƷ| ˾þۺӰԺ| þ޾ƷĻ| ۺպþóAV| ޹Ʒþþþվ| ޹þþþƷ| þþƷһ| 99Ʒþ| þþƷһ | þþƷ| Ůþþùһ| ?VþþƷ| ۺϳ˾þôƬ91| wwwþþcom| 鶹˾þþƷ | seguiþùƷ| þþþþþۺձ | þþƷһ| ޾Ʒþþþþ| þɫۺ| þþþþþþþþþþþ| þ| þùŷպƷ| þù| ޾Ʒһþ| ݺɫþþۺ| þ޹ӰԺ|