| 個人檔案QIN部落格清單網路 | 說明 |
QIN |
||||
堆和栈的区别 (转贴)堆和栈的区别 (转贴)非本人作也!因非常经典,所以收归旗下,与众人阅之!原作者不祥! 堆和栈的区别
一、预备知识—程序的内存分配 一个由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.3申请大小的限制
2.5堆和栈中的存储内容 2.6存取效率的比较 char s1[] = "aaaaaaaaaaaaaaa";
windows进程中的内存结构
接触过编程的人都知道,高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢?程序又是如何使用这些变量的呢?下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明,默认都使用VC编译的release版。 首先,来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码: #include <stdio.h> int g1=0, g2=0, g3=0; int main() //打印出各个变量的内存地址 printf("0x%08x\n",&v1); //打印各本地变量的内存地址 编译后的执行结果是: 0x0012ff78 0x004068d0 0x004068dc 输 出的结果就是变量的内存地址。其中v1,v2,v3是本地变量,g1,g2,g3是全局变量,s1,s2,s3是静态变量。你可以看到这些变量在内存是连 续分布的,但是本地变量和全局变量分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不 同类型的内存区域中的结果。对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是“堆栈”。“栈 (stack)”和“堆(heap)”是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”,所以每个线程虽然 代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过“基地址”和“栈顶”地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数 据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。
#include <stdio.h> void __stdcall func(int param1,int param2,int param3) int main() 编译后的执行结果是: 0x0012ff78 0x0012ff68
;--------------func 函数的汇编代码------------------- :00401000 83EC0C sub esp, 0000000C //创建本地变量的内存空间 ……………………(省略若干代码) :00401075 83C43C add esp, 0000003C ;恢复堆栈,回收本地变量的内存空间 ;-------------------函数结束-------------------------
:00401080 6A03 push 00000003 //压入参数param3 聪明的读者看到这里,差不多就明白缓冲溢出的原理了。先来看下面的代码: #include <stdio.h> void __stdcall func() int main() 编 译后执行一下回怎么样?哈,“"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有更强的通用性。
#include <stdio.h> void func() void main() 程序执行结果为: 0x004107d0 可以发现用new关键字分配的内存即不在栈中,也不在静态数据区。VC编译器是通过windows下的“堆(heap)”来实现new关键字的内存动态分配。在讲“堆”之前,先来了解一下和“堆”有关的几个API函数: HeapAlloc 在堆中申请内存空间 当进程初始化时,系统会自动为进程创建一个默认堆,这个堆默认所占内存的大小为1M。堆对象由系统进行管理,它在内存中以链式结构存在。通过下面的代码可以通过堆动态申请内存空间: HANDLE hHeap=GetProcessHeap(); 其中hHeap是堆对象的句柄,buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢?它的值有什么意义吗?看看下面这段代码吧: #pragma comment(linker,"/entry:main") //定义程序的入口 _CRTIMP int (__cdecl *printf)(const char *, ...); //定义STL函数printf 执行结果为: 0x00130000 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() 这是用VC编译后的执行结果: 这是用Dev-C++编译后的执行结果: 这是用lcc编译后的执行结果: 三个编译器都做到了数据对齐,但是后两个编译器显然没VC“聪明”,让一个char占了4字节,浪费内存哦。
摘要: 讨论常见的堆性能问题以及如何防范它们。(共 9 页) 前言 什么是堆? 在程序中,使用堆来动态分配和释放对象。在下列情况下,调用堆操作: 事先不知道程序所需对象的数量和大小。
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 字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存来保留和提交页。 在图表的底部是“虚拟内存分配程序”,操作系统使用它来保留和提交页。所有分配程序使用虚拟内存进行数据的存取。 分配和释放块不就那么简单吗?为何花费这么长时间? 堆实现的注意事项 当应用程序或 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 月( 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”上(站点: 什么是常见的堆性能问题? 分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块,所以运行时分配程序代码会耗费周期寻找较大的空闲块,或从后端分配程序分配新块。
堆 破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情形包括释放已释放的堆块或使用已释放的堆块,以及块的越界重写等明显问 题。(破坏不在本文讨论范围之内。有关内存重写和泄漏等其他细节,请参见 Microsoft Visual C++(R) 调试文档 。)
在所有的服务器系统中(如 IIS、MSProxy、DatabaseStacks、网络服务器、 Exchange 和其他), 堆锁定实在是个大瓶颈。处理器数越多,竞争就越会恶化。 尽量减少堆的使用 如何减少使用堆操作?通过利用数据结构内的位置可减少堆操作的次数。请考虑下列实例: struct ObjectA { struct ObjectB { // 同时使用 objectA 和 objectB // // // struct ObjectX { 避免使用指针关联两个数据结构。如果使用指针关联两个数据结构,前面实例中的对象 A 和 B 将被分别分配和释放。这会增加额外开销—我们要避免这种做法。
正确使用 _amblksiz。C 运行时 (CRT) 有它的自定义前端分配程序,该分配程序从后端(Win32 堆)分配大小为 _amblksiz 的块。将 _amblksiz 设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用 CRT 的程序适用。 其他提高性能的技术 使用 Windows NT5 堆 改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构,防止多线程式的使用。但不幸的是,在高通信量的情况下,堆仍受困于全局锁,导致高竞争和低性能。Windows 2000 中,锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。
上述改进已 在 Windows 2000 beta 2 和 Windows NT 4.0 SP4 中使用。改进后,堆锁的竞争率显著降低。这使所 有 Win32 堆的直接用户受益。CRT 堆建立于 Win32 堆的顶部,但它使用自己的小块堆,因而不能从 Windows NT 改进中受 益。(Visual C++ 版本 6.0 也有改进的堆分配程序。) 使用分配高速缓存 典 型地,自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设 计成一套固定大小(如 32 字节、64 字节、128 字节等)。这一个很好的策略,但这种自定义堆分配程序丢失与分配和释放的对象相关的“语义信息 ”。 与自定义堆分配程序相反,“分配高速缓存”作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外,它们还能够保留 大量语义信息。每个分配高速缓存处理程序与一个目标二进制对象关联。它能够使用一套参数进行初始化,这些参数表示并发级别、对象大小和保持在空闲列表中的 元素的数量等。分配高速缓存处理程序对象维持自己的私有空闲实体池(不超过指定的阀值)并使用私有保护锁。合在一起,分配高速缓存和私有锁减少了与主系统 堆的通信量,因而提供了增加的并发、最大限度的重用和较高的可伸缩性。 需要使用清理程序来定期检查所有分配高速缓存处理程序的活动情况并回收未用的资源。如果发现没有活动,将释放分配对象的池,从而提高性能。 可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一,这种关系可以用来减少内存分配。 分配高速缓存也起到了调试助手的作用,帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名,甚至能够找到确切的失败的调用者。 MP 堆 本 程序包是好的步骤 —一种改进的 MP-友好的自定义堆分配程序。但是,它不提供语义信息和缺乏统计功能。通常将 MP 堆作为 SDK 库来使用。如果 使用这个 SDK 创建可重用组件,您将大大受益。但是,如果在每个 DLL 中建立这个 SDK 库,将增加工作设置。 重新思考算法和数据结构 减少需要的小对象的数量减少堆分配程序的负载。例如,我们在服务器的关键处理路径上使用五个不同的对象,每个对象单独分配和释放。一起高速缓存这些对象,把堆调用从五个减少到一个,显著减少了堆的负载,特别当每秒钟处理 1,000 个以上的请求时。 如果大量使用“Automation”结构,请考虑从主线代码中删除“Automation BSTR”,或至少避免重复的 BSTR 操作。(BSTR 连接导致过多的重分配和分配/释放操作。) 摘要 评价您的代码中堆的使用。
2009/5/29 软件设计 (三) 最近看了些东东,解决了些原先的一些思想上的困惑。看到好的地方,犹如醍醐灌顶。赶紧分享下,现部分总结如下: 1.功能需求影响架构,而架构必须适应功能需求。但功能需求并不能决定架构,因为如果仅为了满足功能需求而进行架构设计,那么设计结果几乎是毫无约束的,基于接口编程还是统统硬编码到实现都能实现功能需求,分不分层,以及如何分层似乎也无不同。 2.倒是质量属性需求对软件架构影响更大。 以上两点解决我对设计方面的一些困扰。我对为何要设计,设计被哪些因素影响,最终的设计架构为何有各种不同的,他们背后的设计思路是被什么影响的等等问题有了更清晰地认识。在有以上认识的基础上,我们对欣赏现有系统的架构,去尝试还原其设计的思想、策略,会有原则上的指导。 顺便对软件需求做一个介绍: 软件需求: 1.功能需求 2.非功能需求 2.1质量属性 2.1.1运行期质量属性 2.1.2开发期质量属性 2.2约束 以上知识主要出自温昱先生的软件架构设计。 PS:发现软件架构师有个特点,思路真的非常清晰。厉害啊。。。。。。 2009/5/21 行百里者半九十 我是百里行半 一切尚早 LIST 27 行百里者半九十 这句话 以前误读了 sigh.... 也许读作行百里者九十半 我会理解的好点哦 要学着做个自信淡定的老男人。。。虽然我的心理年龄已经不小了 sigh... 王冉有篇文章不错 像四十岁男人那样“沉”下来 http://blog.sina.com.cn/s/blog_47665bc10100d5ow.html 我的msn朋友们,大家有兴趣可以看看。 2009/4/10 当年明月VS徐霞客 我深以为然的想法(转载(长篇)明朝那些事儿-历史应该可以写得好看[1779])当年明月把明朝那些事讲完了。在最后的时候用徐霞客的故事说明了他写这段历史的原因和他的志向或者说是想法。
我表达的能力不太好 还是转载一下吧 明月写得比我好
(长篇)明朝那些事儿-历史应该可以写得好看 [1779]
从某个角度讲,这是上天对他的恩赐,因为这将是他的最后一次旅途,能走多远,就走多远吧。
他离开鸡足山,又继续前行,行进半年,翻越了昆仑山,又行进半年,进入藏区,游历几个月后,踏上归途。
回去没多久,就病了。
喜欢锻炼的人,身体应该比较好,天天锻炼的人(比如运动员),就不一定好,旅游也是如此。
估计是长年劳累,徐宏祖终究是病倒了,没能再次出行。崇祯十四年(1641 ),病重逝世,年五十四。
他所留下的笔记,据说总共有两百多万字,可惜没有保留下来,剩余的部分,大约几十万字,被后人编成《徐霞客游记》。
在这本书里,记载了祖国山川的详细情况,涉及地理、水利、地貌等情况,被誉为十七世纪最伟大的地理学著作,翻译成几十国语言,流传世界。
好的,总结应该出来了,这是一个伟大的地理学家的故事,他为了研究地理,四处游历,为地理学的发展做出了突出贡献,是中华民族的骄傲。
是这样吗?
不是的
其实讲述这人的故事,只想探讨一个问题,他为何要这样做。
没有资助,没有承认(至少生前没有),没有利益,没有前途,放弃一切,用一生的时间,只是为了游历?
究竟为了什么?
我很疑惑,很不解,于是我想起另一个故事。
新西兰登山家希拉里,在登上珠穆朗玛峰后,经常被记者问一个问题:
你为什么要爬?
他总不回答,于是记者总问,终于有一次,他答出了一个让所有人都无法再问的答案:
因为它(指珠峰),就在那里!
因为它就在那里。
其实这个世上很多事,本不需要理由,之所以需要理由,是因为很多人喜欢找抽,抽久了,就需要理由了。
正如徐霞客临终前,所说的那句话:
“汉代的张骞,唐代的玄奘,元代的耶律楚材,他们都曾游历天下,然而,他们都接受了皇帝的命令,受命前往四方。”
“我只是个平民,没有受命,只是穿着布衣,拿着拐杖,穿着草鞋,凭借自己,游历天下,故虽死,无憾。”
说完了。
我要讲的那样东西,就在这个故事里。
我相信,很多人会问,你讲了什么?
用如此之多的篇幅,讲述一个王朝的兴起和衰落,在终结的时候,却说了这样一个故事,你到底想说什么?
我重复一遍,我要讲的那样东西,就在这个故事里,已经讲完了。
所以后面的话,是讲给那些不明白的人,明白的人,就不用继续看。
此前,我讲过很多东西,很多兴衰起落、很多王侯将相、很多无奈更替,很多风云变幻,但这件东西,我个人认为,是最重要的。
因为我要告诉你,所谓千秋霸业,万古流芳,以及一切的一切,只是粪土。先变成粪,再变成土。
现在你不明白,将来你会明白,将来不明白,就再等将来,如果一辈子都不明白,也行。
而最后讲述的这件东西,它超越上述的一切,至少在我看来。
但这件东西,我想了很久,也无法用准确的语言,或是词句来表达,用最欠揍的话说,是只可意会,不可言传。
然而我终究是不欠揍的,在遍阅群书,却无从开口之后,我终于从一本不起眼,且无甚价值的读物上,找到了这句适合的话。
这是一本台历,一本放在我面前,不知过了多久,却从未翻过,早已过期的台历。
我知道,是上天把这本台历放在了我的桌前,它看着几年来我每天的努力,始终的坚持,它静静地,耐心地等待着终结。
它等待着,在即将结束的那一天,我将翻开这本陪伴我始终,却始终未曾翻开的台历,在上面,有着最后的答案。
我翻开了它,在这本台历上,写着一句连名人是谁都没说明白的名人名言。
是的,这就是我想说的,这就是我想通过徐霞客所表达的,足以藐视所有王侯将相,最完美的结束语:
成功只有一个——按照自己的方式,去度过人生。 (全文完) |
|
|||
|
|