個人檔案QIN部落格清單網路 工具 說明
2009/6/23

怎样写一个拼写检查器

http://blog.youxu.info/spell-correct.html

堆和栈的区别 (转贴)

堆和栈的区别 (转贴)

非本人作也!因非常经典,所以收归旗下,与众人阅之!原作者不祥!

堆和栈的区别
一、预备知识—程序的内存分配
一个由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下,栈的大小是2M(也有的说是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的函数调用规则是不一样的,前者由被调函 数调整堆栈,后者由调用者调整堆栈。两者通过“__stdcall”和“__cdecl”前缀区分。先看下面这段代码: 

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

编译后的执行结果是: 

0x0012ff78 
0x0012ff7c 
0x0012ff80 

0x0012ff68 
0x0012ff6c 
0x0012ff70 


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


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

;--------------func 函数的汇编代码------------------- 

:00401000 83EC0C sub esp, 0000000C //创建本地变量的内存空间 
: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 

……………………(省略若干代码) 

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

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


;--------------主程序调用func函数的代码-------------- 

:00401080 6A03 push 00000003 //压入参数param3 
:00401082 6A02 push 00000002 //压入参数param2 
:00401084 6A01 push 00000001 //压入参数param1 
:00401086 E875FFFFFF call 00401000 //调用func函数 
;如果是“__cdecl”的话,将在这里恢复堆栈,“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; 

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


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


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

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

程序执行结果为: 

0x004107d0 
0x0012ff04 
0x004068c0 

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

HeapAlloc 在堆中申请内存空间 
HeapCreate 创建一个新的堆对象 
HeapDestroy 销毁一个堆对象 
HeapFree 释放申请的内存 
HeapWalk 枚举堆对象的所有内存块 
GetProcessHeap 取得进程的默认堆对象 
GetProcessHeaps 取得进程所有的堆对象 
LocalAlloc 
GlobalAlloc 

当进程初始化时,系统会自动为进程创建一个默认堆,这个堆默认所占内存的大小为1M。堆对象由系统进行管理,它在内存中以链式结构存在。通过下面的代码可以通过堆动态申请内存空间: 

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

其中hHeap是堆对象的句柄,buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢?它的值有什么意义吗?看看下面这段代码吧: 

#pragma comment(linker,"/entry:main") //定义程序的入口 
#include <windows.h> 

_CRTIMP int (__cdecl *printf)(const char *, ...); //定义STL函数printf 
/*--------------------------------------------------------------------------- 
写到这里,我们顺便来复习一下前面所讲的知识: 
(*注)printf函数是C语言的标准函数库中函数,VC的标准函数库由msvcrt.dll模块实现。 
由 函数定义可见,printf的参数个数是可变的,函数内部无法预先知道调用者压入的参数个数,函数只能通过分析第一个参数字符串的格式来获得压入参数的信 息,由于这里参数的个数是动态的,所以必须由调用者来平衡堆栈,这里便使用了__cdecl调用规则。BTW,Windows系统的API函数基本上是 __stdcall调用形式,只有一个API例外,那就是wsprintf,它使用__cdecl调用规则,同printf函数一样,这是由于它的参数个 数是可变的缘故。 
---------------------------------------------------------------------------*/ 
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); 

执行结果为: 

0x00130000 
0x00133100 
0x00133118 

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

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

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

这是用VC编译后的执行结果: 
0x0012ff7c 
0x0012ff7b 
0x0012ff80 
变量在内存中的顺序:b(1字节)-a(4字节)-c(4字节)。 

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

这是用lcc编译后的执行结果: 
0x0012ff6c 
0x0012ff6b 
0x0012ff64 
变量在内存中的顺序:同上。 

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


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


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



摘要: 讨论常见的堆性能问题以及如何防范它们。(共 9 页)

前言
您 是否是动态分配的 C/C++ 对象忠实且幸运的用户?您是否在模块间的往返通信中频繁地使用了“自动化”?您的程序是否因堆分配而运行起来很慢?不仅仅 您遇到这样的问题。几乎所有项目迟早都会遇到堆问题。大家都想说,“我的代码真正好,只是堆太慢”。那只是部分正确。更深入理解堆及其用法、以及会发生什 么问题,是很有用的。

什么是堆?
(如果您已经知道什么是堆,可以跳到“什么是常见的堆性能问题?”部分)

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

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


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

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

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

COM 的 IMalloc 分配程序(或 CoTaskMemAlloc / CoTaskMemFree):函数使用每个进程的默认堆。自动化程序使用“组件对象模型 (COM)”的分配程序,而申请的程序使用每个进程堆。

C/C++ 运 行时 (CRT) 分配程序:提供了 malloc() 和 free() 以及 new 和 delete 操作符。 如 Microsoft Visual Basic 和 Java 等语言也提供了新的操作符并使用垃圾收集来代替堆。CRT 创建自己的私有堆,驻留 在 Win32 堆的顶部。

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

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

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

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

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

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

在所有虚拟内存系统中,堆驻留在操作系统的“虚拟内存管理器”的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下,这些堆是操作系统堆中的层,而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆,而使用虚拟内存函数更利于堆的分配和块的使用。

典 型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用,堆尝试从前端列表找到一个自由块。如果失败,堆被迫从后端 (保留和提交虚拟内存)分配一个大块来满足请求。通用的实现有每块分配的开销,这将耗费执行周期,也减少了可使用的存储空间。

Knowledge Base 文 章 Q10758,“用 calloc() 和 malloc() 管理内存” (搜索文章编号), 包含了有关这些主题的更多背景知识。另外,有关堆实 现和设计的详细讨论也可在下列著作中找 到:“Dynamic Storage Allocation: A Survey and Critical Review”,作 者 Paul R. Wilson、Mark S. Johnstone、 Michael Neely 和 David Boles;“International 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]) 保存大于 1,024 字节的块。空闲列表容纳了用双向链表链接在一起的对象。默认情况下,“进程堆”执行收集 操作。(收集是将相邻空闲块合并成一个大块的操作。)收集耗费了额外的周期,但减少了堆块的内部碎片。

单一全局锁保护堆,防止多线程式的使用。(请参见“Server Performance and Scalability Killers”中的第一个注意事项, George Reilly 所著,在 “MSDN Online Web Workshop”上(站点:http://msdn.microsoft.com/workshop/server/iis/tencom.asp(英文)。)单一全局锁本质上是用来保护堆数据结构,防止跨多线程的随机存取。若堆操作太频繁,单一全局锁会对性能有不利的影响。

什么是常见的堆性能问题?
以下是您使用堆时会遇到的最常见问题: 

分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块,所以运行时分配程序代码会耗费周期寻找较大的空闲块,或从后端分配程序分配新块。


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


堆 竞争造成的速度减慢。当两个或多个线程同时访问数据,而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争。竞争总是导致麻烦;这也是目前多处理 器系统遇到的最大问题。当大量使用内存块的应用程序或 DLL 以多线程方式运行(或运行于多处理器系统上)时将导致速度减慢。单一锁定的使用—常用的解 决方案—意味着使用堆的所有操作是序列化的。当等待锁定时序列化会引起线程切换上下文。可以想象交叉路口闪烁的红灯处走走停停导致的速度减慢。 
竞争通常会导致线程和进程的上下文切换。上下文切换的开销是很大的,但开销更大的是数据从处理器高速缓存中丢失,以及后来线程复活时的数据重建。

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


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

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

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

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

struct ObjectA {
   // objectA 的数据 
}

struct ObjectB {
   // objectB 的数据 
}

// 同时使用 objectA 和 objectB

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

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

//
// 集合 – 在另一对象内使用 objectA 和 objectB
//

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

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


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


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


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


在块中分配对象(块化)。块化是以组的方式一次分配多个对象的方法。如果对列表的项连续跟踪, 例如对一个 {名称,值} 对的列表,有两种选择:选择一是为每一个“名称-值”对分配一个节点;选择二是分配一个能容纳(如五个)“名称-值”对的结 构。例如,一般情况下,如果存储四对,就可减少节点的数量,如果需要额外的空间数量,则使用附加的链表指针。 
块化是友好的处理器高速缓存,特别是对于 L1-高速缓存,因为它提供了增加的位置 —不用说对于块分配,很多数据块会在同一个虚拟页中。

正确使用 _amblksiz。C 运行时 (CRT) 有它的自定义前端分配程序,该分配程序从后端(Win32 堆)分配大小为 _amblksiz 的块。将 _amblksiz 设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用 CRT 的程序适用。
使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同。但总能在性能和可升缩性方面有所收获。另一方面,代码会有点特殊,但如果经过深思熟虑,代码还是很容易管理的。

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

使用 Windows NT5 堆 
由于几个同事的努力和辛勤工作,1998 年初 Microsoft Windows(R) 2000 中有了几个重大改进:

改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构,防止多线程式的使用。但不幸的是,在高通信量的情况下,堆仍受困于全局锁,导致高竞争和低性能。Windows 2000 中,锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。


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


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

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

使用分配高速缓存 
分配高速缓存允许高速缓存分配的块,以便将来重用。这能够减少对进程堆(或全局堆)的分配/释放调用的次数,也允许最大限度的重用曾经分配的块。另外,分配高速缓存允许收集统计信息,以便较好地理解对象在较高层次上的使用。

典 型地,自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设 计成一套固定大小(如 32 字节、64 字节、128 字节等)。这一个很好的策略,但这种自定义堆分配程序丢失与分配和释放的对象相关的“语义信息 ”。 

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

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

可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一,这种关系可以用来减少内存分配。

分配高速缓存也起到了调试助手的作用,帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名,甚至能够找到确切的失败的调用者。

MP 堆 
MP 堆 是对多处理器友好的分布式分配的程序包,在 Win32 SDK(Windows NT 4.0 和更新版本)中可以得到。最初由 JVert 实现,此 处堆抽象建立在 Win32 堆程序包的顶部。MP 堆创建多个 Win32 堆,并试图将分配调用分布到不同堆,以减少在所有单一锁上的竞争。

本 程序包是好的步骤 —一种改进的 MP-友好的自定义堆分配程序。但是,它不提供语义信息和缺乏统计功能。通常将 MP 堆作为 SDK 库来使用。如果 使用这个 SDK 创建可重用组件,您将大大受益。但是,如果在每个 DLL 中建立这个 SDK 库,将增加工作设置。

重新思考算法和数据结构 
要 在多处理器机器上伸缩,则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问,“我能用不同的数据结构完成此工作吗?”例 如,如果在应用程序初始化时加载了只读项的列表,这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎 片,从而增强性能。

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

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

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

评价您的代码中堆的使用。


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


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


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


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


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



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

2009/5/29

软件设计 (三)

最近看了些东东,解决了些原先的一些思想上的困惑。看到好的地方,犹如醍醐灌顶。赶紧分享下,现部分总结如下:
1.功能需求影响架构,而架构必须适应功能需求。但功能需求并不能决定架构,因为如果仅为了满足功能需求而进行架构设计,那么设计结果几乎是毫无约束的,基于接口编程还是统统硬编码到实现都能实现功能需求,分不分层,以及如何分层似乎也无不同。
2.倒是质量属性需求对软件架构影响更大。
以上两点解决我对设计方面的一些困扰。我对为何要设计,设计被哪些因素影响,最终的设计架构为何有各种不同的,他们背后的设计思路是被什么影响的等等问题有了更清晰地认识。在有以上认识的基础上,我们对欣赏现有系统的架构,去尝试还原其设计的思想、策略,会有原则上的指导。

顺便对软件需求做一个介绍:
软件需求:
1.功能需求
2.非功能需求
   2.1质量属性
         2.1.1运行期质量属性
         2.1.2开发期质量属性
    2.2约束

以上知识主要出自温昱先生的软件架构设计。
PS:发现软件架构师有个特点,思路真的非常清晰。厉害啊。。。。。。热烈的笑脸当然啦,驾驭全局的能力也是很爽很awesome啊。

2009/5/21

行百里者半九十

我是百里行半 一切尚早 LIST 27
行百里者半九十 这句话 以前误读了 sigh....
也许读作行百里者九十半 我会理解的好点哦
要学着做个自信淡定的老男人。。。虽然我的心理年龄已经不小了 sigh...
王冉有篇文章不错 像四十岁男人那样“沉”下来
http://blog.sina.com.cn/s/blog_47665bc10100d5ow.html
我的msn朋友们,大家有兴趣可以看看

2009/4/10

当年明月VS徐霞客 我深以为然的想法(转载(长篇)明朝那些事儿-历史应该可以写得好看[1779])

当年明月把明朝那些事讲完了。在最后的时候用徐霞客的故事说明了他写这段历史的原因和他的志向或者说是想法。

 

我表达的能力不太好 还是转载一下吧 明月写得比我好

 

 

(长篇)明朝那些事儿-历史应该可以写得好看 [1779]

 

从某个角度讲,这是上天对他的恩赐,因为这将是他的最后一次旅途,能走多远,就走多远吧。

 

他离开鸡足山,又继续前行,行进半年,翻越了昆仑山,又行进半年,进入藏区,游历几个月后,踏上归途。

 

回去没多久,就病了。

 

喜欢锻炼的人,身体应该比较好,天天锻炼的人(比如运动员),就不一定好,旅游也是如此。

 

估计是长年劳累,徐宏祖终究是病倒了,没能再次出行。崇祯十四年(1641 ),病重逝世,年五十四。

 

他所留下的笔记,据说总共有两百多万字,可惜没有保留下来,剩余的部分,大约几十万字,被后人编成《徐霞客游记》。

 

在这本书里,记载了祖国山川的详细情况,涉及地理、水利、地貌等情况,被誉为十七世纪最伟大的地理学著作,翻译成几十国语言,流传世界。

 

好的,总结应该出来了,这是一个伟大的地理学家的故事,他为了研究地理,四处游历,为地理学的发展做出了突出贡献,是中华民族的骄傲。

 

是这样吗?

 

不是的

 

其实讲述这人的故事,只想探讨一个问题,他为何要这样做。

 

没有资助,没有承认(至少生前没有),没有利益,没有前途,放弃一切,用一生的时间,只是为了游历?

 

究竟为了什么?

 

我很疑惑,很不解,于是我想起另一个故事。

 

新西兰登山家希拉里,在登上珠穆朗玛峰后,经常被记者问一个问题:

 

你为什么要爬?

 

他总不回答,于是记者总问,终于有一次,他答出了一个让所有人都无法再问的答案:

 

因为它(指珠峰),就在那里!

 

因为它就在那里。

 

其实这个世上很多事,本不需要理由,之所以需要理由,是因为很多人喜欢找抽,抽久了,就需要理由了。

 

正如徐霞客临终前,所说的那句话:

 

“汉代的张骞,唐代的玄奘,元代的耶律楚材,他们都曾游历天下,然而,他们都接受了皇帝的命令,受命前往四方。”

 

“我只是个平民,没有受命,只是穿着布衣,拿着拐杖,穿着草鞋,凭借自己,游历天下,故虽死,无憾。”

 

说完了。

 

我要讲的那样东西,就在这个故事里。

 

我相信,很多人会问,你讲了什么?

 

用如此之多的篇幅,讲述一个王朝的兴起和衰落,在终结的时候,却说了这样一个故事,你到底想说什么?

 

我重复一遍,我要讲的那样东西,就在这个故事里,已经讲完了。

 

所以后面的话,是讲给那些不明白的人,明白的人,就不用继续看。

 

此前,我讲过很多东西,很多兴衰起落、很多王侯将相、很多无奈更替,很多风云变幻,但这件东西,我个人认为,是最重要的。

 

因为我要告诉你,所谓千秋霸业,万古流芳,以及一切的一切,只是粪土。先变成粪,再变成土。

 

现在你不明白,将来你会明白,将来不明白,就再等将来,如果一辈子都不明白,也行。

 

而最后讲述的这件东西,它超越上述的一切,至少在我看来。

 

但这件东西,我想了很久,也无法用准确的语言,或是词句来表达,用最欠揍的话说,是只可意会,不可言传。

 

然而我终究是不欠揍的,在遍阅群书,却无从开口之后,我终于从一本不起眼,且无甚价值的读物上,找到了这句适合的话。

 

这是一本台历,一本放在我面前,不知过了多久,却从未翻过,早已过期的台历。

 

我知道,是上天把这本台历放在了我的桌前,它看着几年来我每天的努力,始终的坚持,它静静地,耐心地等待着终结。

 

它等待着,在即将结束的那一天,我将翻开这本陪伴我始终,却始终未曾翻开的台历,在上面,有着最后的答案。

 

我翻开了它,在这本台历上,写着一句连名人是谁都没说明白的名人名言。

 

是的,这就是我想说的,这就是我想通过徐霞客所表达的,足以藐视所有王侯将相,最完美的结束语:

 

成功只有一个——按照自己的方式,去度过人生。

   (全文完)

2009/4/6

Less is More

Less is more.
Keep your mind open.

2009/2/22

无题

300 无题 无需评论 怕忘却的记录而已
2009/2/14

思念抑或是纪念

与其写今天 不如纪念昨日 人生终于进入新的阶段了  哎 这周有点忙 来不及思念 倒是学着同时处理多件事情。。。
2009/1/30

前世今生

昨天是大年初四 终于见到一位阔别已久的故人 总算让我了却桩心事 故事是画上句号的时候了 昨夜听歌好久。。。
未曾想过会来江苏求学
未曾想过诗句落霞与孤鹜齐飞会让我有新的感觉
如果人生的第一个二十四年是前世的话
我想的我的今生终于开始了 牛年从今天开始 对我来说 正式开始了
2008/12/28

2008之镜

已近年末,回首2008年,无论是国家对我自己,都是意义重大的一年。
国家可以说是高潮迭起,乱事频生。其实是看到太多漏洞,太多值得思考的地方。以事为镜,直指人心。
对我来说,08同样意义非凡,下血本的800元钱的新眼镜(买贵了)还是有效果的,还是要注意下形象。功夫熊猫很好很强大。理论永远取代不了实践,给我机会,我来操盘。很多东西,你不在那个位置,你是看不到,想不到,悟不到的。不服不行。昨天做了个08总结:以铜为镜,可以正衣冠,以人为镜可以知得失。以史围巾,可以知兴替。其实镜子主要是拿来化妆的,呵呵。
2008/11/11

软件设计 (二) 最小接口 与 人本接口(【转】 Martin Fowler's Bliki 中文版)

原文:MinimalInterface    设计            Bliki 索引

所谓最小接口,其设计风格与人本接口形成鲜明对照,它背后的主旨是设计一套API不仅能满足用户完成所有操作的需求,还要把这种能力积聚到一个最精简的方法集合上。(两者的区别请参考人本接口里的例子。)

人本接口里 的例子“Ruby-Array VS Java-List”来说,既然List已经有了取索引位置处元素的方法,又能获得链表的长度,那就没必要再加上first和last方法了—— first和last的效果用已有接口就能实现,因此它们属于便利方法(convenience methods),不过,推崇最小接口的设计者对便利方法也并非一概避免,只是一个便利方法要想跻身为最小接口的一员需要先跳过一个很高的标杆。

我们已经讨论过人本接口了,下面说一下最小接口的基本原则。

学习接口怎么用是要花时间的,如果一个class接口很庞大,用户可能对它的第一印象就不是很好,想用好它没准就很不容易了。如果能保持接口被限制在一个窄小而聚焦的方法集合上,那么用户要了解这是个什么class、它有哪些功能就相对容易了。

聚焦”对class设计者也很重要。设计class时的一个常见问题就是让一个class做太多事情。把注意力集中在最基本的操作上能帮助class避免赘肉缠身,让它专心做一件事并把这件事情做好。

如 果选择人本方案,一个方法只要可能被某人用到,你就会把它添上,这样不断添加方法,最后多得没完没了,何处是个头儿呢?因此,为了避免方法爆炸,必需得有 个指导原则。如果把“有用的就是需要的”作为设计人本接口的指导原则,确定什么是“有用的”本来就见仁见智,很难说清。而最小接口的原则就简单多了——如 果一个操作用已经有的方法能完成,那么就无需再新添一个。

(需要注意,找出哪些方法是有用的——这个问题对编写公开发布的类库的人比对编写应用代码的人意义更重大。写应用代码时,因为是限制在一个封闭的系统范围内,你很清楚一个接口有哪些用法。)

如 果你用的是静态类型的纯接口(比如Java和C#里的interface关键字),那么保持方法总数较小的另一个原因是为了减轻接口实现的负担。如果不得 不实现的方法数量庞大,那可真是不小的工作量。(可以用抽象类(abstract class)以混入(mixin)的方式来减轻这种负担。)

在用一个最小接口class时,如果需要更多功能,你可以借助其他class。例如在Java中你要想翻转一个List或给它排序(这些操作都是Ruby Array的常规方法),可以借助Collections这个工具类。

如果你是在编写一个库,一旦发布,就很难再从中去掉什么东西了。因此,最好是先天不足,后天逐渐丰富,这样要强于先天营养过剩长成胖子,之后想减肥也减不下来




原文:HumaneInterface    设计        2005年12月5日            Bliki 索引

(更新频繁,见文尾增添的链接。)

在Ruby用户群中混迹了一段时日,我经常见到“人本接口”这个术语。“人本接口”体现了Ruby大虾们设计class接口时的态度,此外,我觉得它还与另一个学派(最小接口)在设计API的思路上形成了有趣的对照。

人本接口的本质思想是找出人们想要的操作并设计出接口,之后所有常见的操作都可以轻松搞定了。

与最小接口形成鲜明对比的是,人本接口容易变得非常肥大,其设计者对此也着实不怎么介意。这里的“肥大”不是说在实现的代码量上人本接口的class就一定比最小接口的大。两者的基本功能也往往非常相似。

拿Java和Ruby的链表组件做个对比,是观察人本接口与最小接口差异的一个好办法。Java提供了一个interface(java.util.List),它声明了25个实例方法,而Ruby的Array class(不是数组,而是个链表)有78个方法。数量的差异多少暗示出风格的不同(尽管还有许多其他原因可以解释这种差异)。两种组件提供的基本服务是相同的,不过Ruby的Array还包括了不少附加功能,但都是些小功能,用Java的最小接口也能实现。

举个小例子来展示一下它们的区别吧:得到一个链表末尾的元素。用Java你会这么写:

aList.get(aList.size -1)

用Ruby则为:

anArray.last

其实更让人奇怪的是,Ruby Array还有一个first方法,所以你不必写anArray[0],只需anArray.first。

还有一些复杂功能,比如Ruby Array的flatten方法,它能把嵌套的链表变成单独的一条:

irb> [1,2,[3,4,[5,6],7],8].flatten ⇒ [1, 2, 3, 4, 5, 6, 7, 8]

这 里的要点是:不论简单如last还是复杂若flatten,所有这些功能都可以留给用户自己编写,而无需增加链表class的代码量。最小主义者倾向于聚 焦一组必要方法的最小集合,来支持所有这些操作,人本主义者则尽量添加人们需要的方法。通常这些额外的方法被称作“便利方法(convenience methods)”——这个术语在最小主义者眼里并非“不可或缺品”之意。

这就引出一个问题:以什么标准来判断一个方法该不该添到一个人本接口里呢?要是把所有人可能用得到的所有方法全都添上,最后会造出一个极其复杂的class。人本接口设计者的处理原则是努力甄别哪些方法是一个class最常用的,再加以设计以方便大家使用。

这 条原则不仅能帮你决定哪些方法要添加,还影响着怎样给它们取名字。在RubyConf上, Tanaka Akira指出常用方法优先取短名字的好处。那些方法用得多,因此我们很熟悉,短名字记起来也容易,另一个好处是名字短打字少读起来方便。例如class DateTime的parse方法,负责解析常用的日期格式,还有一个更灵活的方法叫strptime,能解析所有日期格式,但用得就没那么多了。

这条关于命名的原则与最小接口方案并不冲突。一个例证就是Java的List interface一登场就把老旧的Vector的elementAt方法改成了get。

方 法别名是Ruby的人本接口哲学引发的另一个有趣现象。要想得到一个链表的长度,用length还是用size?有的库用length有的库用 size,而Ruby的Array两个都有,它们互为别名,调用哪个都是同一份代码。Ruby大虾们的观点是:与其让用户记住哪个库里有哪个名字不如两个 都提供。

关于人本接口和最小接口的设计风格孰优孰劣的讨论太多了,长篇累牍惹人烦,所以在这儿我尽量总结一下人本接口支持者们的主要观点(反方观点见最小接口):

一 个object的功能强弱主要不是取决于它有什么数据,而在于它有哪些操作。如果总是想着保持接口尽量窄小,哪怕是一些很常用的功能,也迫使用户们不得不 一遍一遍地重复编码。比如上面提到的flatten操作,一些人会用递归实现,虽然不算难,但既然这个操作不是那么不常用,何必劳驾用户们呢?

即便是简单情况,比如取链表末尾元素,若没有last方法,还得让用户学会一个惯用法才能取到。一个简单方法就直接读取了,何必给用户加一层间接呢?优秀软件处处先为用户着想,为用户提供方便,人本接口就遵循用户为先原则。

人本接口做的越多,用户就越不需要亲自动手。尤其是用户们可借助这些API来简化日常任务——代码写起来、读起来都方便。

正反双方都不乏闪亮论点。就我个人而言,我倾向于人本接口一方,尽管我觉得它的难度确实更高。

后续讨论

经我这篇小文一煽动,引发了一些有趣而且有意义的讨论。等到了一定程度,我会在下面的链接旁加些叙述以方便大家阅读,但在那之前,我就先只把它们列出来吧。这场辩论主要是由Elliotte Harold对人本方式简短却鞭辟入里的批评以及James Robertson的回应(别忘了看Robertson帖子后面的评论)引发的。随后论战大潮就涌来了:| Cees de Groot | Antonio Vieiro | David Hoefler | James Higgs | Peter Williams | Cedric Beust | John D. Mitchell | Stuart Roebuck | Elliotte Harold (2) | Jon Tirsen | Hitesh Jasani | Blaine Buxton | Ramnivas Laddad | Anders Noras | James Robertson (2) | Kieth Ray | James Robertson (3) | Elliotte Harold (3) | Charles Miller | Rob Lally | Bernard Notarianni | David Crow | Jim Weirich | Jim Weirich (2) | Ian Bicking | Brian Foote | Justin Gehtland | Tom Moertel | Antonio Vieiro (2) | Kris Wehner | The Server Side | Ravi Mohan | Danny Lagrouw | Piers Cawley | Peter Williams | Florian Frank | Chris Siebenmann

除 了上边的还有很多,我没搜罗齐全,列出这些的目的是希望让辩论多些有意思的东西,并避免恶言相向。Ruby Array VS Java List的例子过分吸引了大家的眼球,让人们忽视了其背后的道理,产生这种倾向是自然而然的。这场讨论已经发散出一些不错的方向,有机会的话我会试着说说 其中一两个话题。

读一下Joey deVilla的文章也是个不错的选择,它包括了上边大部分链接的文摘。
2008/11/9

软件设计 (一)

软件体系结构
什么是软件体系结构?  他有时候也被成为“高层次设计”。他是一种顶级定义,是一种对系统的全面概览,其中刻意避免了过多的细节。他是宏观层面的,而不是微观的。

软件体系视图
作用:
1.确定关键的软件模块
2.确定个组件之间是如何通信的
3.有助于鉴别和确定系统中所有重要接口的特性,阐明各个子系统的正确度角色和职责。
这些信息是我们能够把系统当作一个整体来考虑,而不必了解其中的每个部分都是如何工作的。体系结构为后续开发提供了一个框架。它说明了如何在团队各成员之间分配工作tz:为团队开发提供了可能),并使你可以权衡不同的实现策略。
体系结构不仅描绘了系统是如何构成的,而且也说明了系统以后应该如何扩展。在一个大型团队中,如果有一个清晰统一的视图,说明应该如何调整软件、每个模块中应该放入哪些功能以及各模块如何互相连接,那么就可以更加优雅地开发程序(tz:需要让每个程序员都尽量理解整个系统的架构吗,还是只用让他知道说负责的模块的功能和要求就可以了,是否他对整个系统有一定的理解,能够更好把说负责的模块完成好呢?)。

同桌的你 睡在我上铺的兄弟

觉得老狼的歌蛮有感觉的 恩 不错哦 呵呵

同桌的你-老狼
词曲:高晓松
欢迎您
明天你是否会想起
昨天你写的日记
明天你是否还惦记
曾经最爱哭的你
老师们都已想不起
猜不出问题的你
我也是偶然翻相片
才想起同桌的你
谁娶了多愁善感的你
谁看了你的日记
谁把你的长发盘起
谁给你做的嫁衣
music...
你从前总是很小心
问我借半块橡皮
你也曾无意中说起
喜欢跟我在一起
那时候天总是很蓝
日子总过得太慢
你总说毕业遥遥无期
转眼就各奔东西
谁遇到多愁善感的你
谁安慰爱哭的你
谁看了我给你写的信
谁把它丢在风里
music...
从前的日子都远去
我也将有我的妻
我也会给她看相片
给她讲同桌的你
谁娶了多愁善感的你
谁安慰爱哭的你
谁把你的长发盘起
谁给你做的嫁衣


睡在我上铺的兄弟
无声无息的你
你曾经问我的那些问题
如今再没人问起
分给我烟抽的兄弟
分给我快乐的往昔
你总是猜不对我手里的硬币
摇摇头说这太神秘
你来的信写的越来越客气
关于爱情你只字不提
你说你现在有很多的朋友却再也不为那些事忧愁
睡在我寂寞的回忆
那些日子里你总说起的女孩
是否送了你她的发带
你说每当你回头看夕阳红
每当你又听到晚钟
从前的点点滴滴会涌起
在你来不及难过的心里
你问我几时能一起回去
看看我们的宿舍我们的过去
你刻在墙上的字依然清晰
从那时候起就没有人能擦去
2008/11/8

六加一的产业链

我以芭比娃娃为例,我们广东东莞所生产的芭比娃娃卖到美国是9.9美元一个,接近10美元,请问10美元减掉1美元的9美元是如何创造出来的?那就是美国 企业的灵魂,它透过6大块所创造出来,包括产品设计、原料采购、仓储运输、定单处理、批发经营、终端零售,创造出了9美元的产值,6大块加上中国一块的制 造,叫做六加一的整个流程就是产业链,我们这么多年的经济成长,我们取得了整条产业链的“一”,而欧美各国掌控了整条产业链的“六”,这个价值是怎么回事 儿?我们的制造业者在破坏环境、浪费资源、剥削劳工的基础上,我们每创造出一百万美元的产值,我们同时替美国创造出九百万美元的产值,我们辛辛苦苦创造出 一亿美国的产值,我们同时替美国创造出9亿美元的产值。因此,中国越制造,美国越富裕,美国席卷了90%的价值。

 

那么这一种生产模式叫做国际分工。而中国被分到了最差的一项,破坏环境、浪费资源、剥削劳工。我们现在常常以1.8万亿感到扬扬得意,你晓不晓得我们怎样 创造出这样的成绩?我们掌控着10%的价值,我们创造出1.8万亿美元的外汇,也就是1.8万亿除以10%等于18万亿,也就是我们出口制造业替全世界创 造出18万亿的产值,我们只分到了1.8,其他的都是欧美的,你知道18万亿是什么概念呢?那就是在座的各位来宾和全中国的工人80年所加起来的工资的总 和,这就是18万亿被国际分工席卷。

 

所以我常讲,今天西方帝国主义对中国的掠夺和19世纪免费掠夺非洲差不多,当时非洲是0%,现在中国拿10%,在我看来没有什么差别,至少非洲没有环境污 染等的问题,我们的10%伴随着污染环境,浪费的资源以及被剥削的劳工,由于你是处在产业链的最底端。因此你特别抗压,因为真正掌控定价权的是整个产业链 的六,一是不掌控定价权,所以你能不能想象,欧美各国尤其是以美国为首的欧美国家,不但席卷的90%的利益,而且掌控着我们的定价权,我们不掌控。这就是 为什么,创造了汇率,成本、劳动合同法以及宏观调控之后我们的企业必须全力承担这些成本,因为我们不掌控定价权,我们无法提升售价,各位都懂了吗?这就是 为什么我说的投资营销环境急速恶化,因为你不掌控定价权。

2008/11/6

专注和极致

发现其实做很多事情都是一样的,有那么复杂那么难吗?只要做到专注,做到极致,有多少不能做的啊。
这两天有所感悟,今天值得写文纪念。用google的两句话来总结吧:
1.It's best to do one thing really, really well.
2.Great just isn't good enough.
2008/10/25

大明与乞丐 -- 观《明》话剧有感

今天是《明》剧在上海的最后一场演出,又是周末,人多演员也特别卖力。
明讲述的大明皇帝在三个王子间挑选继承人的故事。老大,老二,老三。。。
给我印象或者说是影响比较深的是老三的一段月夜下的独白(其实有“美女”在旁边无语地听着。。。):
我有光荣的理想,我要打造我的伟大的国家,在一千年以后,我要让我的国民过上幸福的生活,闲得没事干的生活,不用上班,不用加班。。。(本人记忆过滤整理版,如有雷同,纯属正常)
还有一段比较精彩的是:
造反!
对,造反!
三王子面对大王子的紧逼(他已经干掉了二王子。。。),一个和尚给他出的主意!丫的,和尚叫姚广孝,呵呵。以燕王大哥为原型也太明显了吧。
反还是不反,这是一个问题。
反 的结局只有两种,成王败寇,一笔大买卖。三王子在焦虑在彷徨在挣扎,你分明看见他在颤抖,其实是听见(你丫,没事老拿扇子敲木桌干啥玩意哪?大哥,咱紧张 啊。。。)。这种场景你在朱重八决定造反起义前看到过,这种场景你在朱棣决定造反勤王前看到过,这种场景你在无数牛人决定。。。前看到过。。。其实,牛人 也是人,决定他是牛还是熊的,也许就在于:英雄明知前途艰难,老子tmd腿再抖也要颤颤巍巍地迈出去!而一般人一般就缩回去了,何必呢,太难了。在明 知。。。的情况下做出的决定,你才知道他的决心有多大,志向有多远。
三王子快成功了,他一遍又一遍地呼喊,我的女人呢?我的国家呢?
结局没什么太多悬念,老大老二都挂了。。。我个人觉得,治理国家,打造心中梦想的国度,才是伟大征程的开始。历经种种,终于可以。。。了,演出开始了:。。。
田导蛮平易近人的,俺还过去和大师握了握手。。。还没说上两句呢。。。田导被人拉去照相了。。。

回 来的地铁上,看到一位老人在地铁上一节节车厢地乞讨。我发现大家给钱的人蛮多的,估计心态和我差不多。因为人家是老人了,还坐在车厢地板上拉二胡,你也可 以看作是卖艺的嘛,虽然只是一节车厢拉个一两句,给到钱就下一节。。。虽然上海乞丐蛮多的,也听说过很多白天讨完就上馆子,据说比白领月收入多多了。可是 每次看到是老人,记得上次是在天桥上,一个老头,天还下着雨,都坚持在岗位上,还是有些心酸和无奈。。。别和我说他们收入不低,一个六七十的老头,要是老 有所养,谁tm吃饱了撑,晚上都十点多,还屁颠屁颠地奋战在地铁上给你拉小曲玩,好玩哪?!!!我贱啊?!!!
老吾老以及人之老,幼吾幼以及人之 幼。。。我一直都记得初中时候的一个场景:我跑去洋人街坐在街边和老外练口语,一个老大妈在背后的垃圾桶里辛勤地捡东西,然后过来和我们要钱。。。我不知 道和老外说什么,我和他们说。We are hope,老外纠正说是hopeful...我坚信以后一定会如何如何。。。靠谁呢,政府呢,她肩膀小,扛不住的。靠我?哼,年少轻狂!靠谁!我们!我辈 仍需努力!!!凡事我但尽心,成功不必在我。。。很多事,不是单单靠我们一个人能做好,做成的。需要我们这一代,甚至下一代。。。哎,讲着讲着就整成天行 健了,停!一个国家最有活力的最充满希望的是年轻人,如果年轻人都不想不做,那就真的没戏了。呵呵
最后要说的是,事情是做出来的哦,hoho
2008/10/12

丰富的午餐

呵呵 今天尝到小冷子女友的厨艺,不错不错。好丰盛,好多肉啊...有肉就是爽啊。o(∩_∩)o...哈哈
发现自己连斗地主都不会,第一次对自己的智商产生怀疑,hoho
恩 蛮好玩的热烈的笑脸
2008/10/1

Robert C. Martin

Robert C. Martin

President and Chief Executive Officer

 

Robert C. Martin has been a software professional since 1970. In the last 35 years, he has worked in various capacities on literally hundreds of software projects. He has authored "landmark" books on Agile Programming, Extreme Programming, UML, Object-Oriented Programming, and C++ Programming. He has published dozens of articles in various trade journals. Today, He is one of the software industry's leading authorities on Agile software development and is a regular speaker at international conferences and trade shows. He is a former editor of the C++ Report and currently writes a monthly Craftsman column for Software Development magazine.

Selected Publications

Pattern Languages of Program Design 3
Edited by Robert C. Martin, Frank Buschmann, Dirk Riehle,
Addison Wesley, 1997, ISBN 0201310112

 

Designing Object Oriented C++ Applications using the Booch Method
Robert C. Martin,
Prentice Hall, 1995, ISBN 0132038374

 

More C++ Gems
Edited by Robert C. Martin,
Cambridge Press, 1999, ISBN 0521786185

 

Agile Software Development: Principles, Patterns, and Practices
Robert C. Martin,
Prentice Hall, 2002

 

Extreme Programming in Practice
James Newkirk and Robert C. Martin,
Addison Wesley, 2001, ISBN 0201709376

  UML for Java Programmers
Robert C. Martin,
Prentice Hall (2003)


2008/9/24

吾道一以贯之

吾道一以贯之。。。