﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-dazhi-随笔分类-C++</title><link>http://www.cppblog.com/dazhi/category/11608.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 31 Aug 2009 05:58:18 GMT</lastBuildDate><pubDate>Mon, 31 Aug 2009 05:58:18 GMT</pubDate><ttl>60</ttl><item><title>C/C++ 内存空间分配</title><link>http://www.cppblog.com/dazhi/archive/2009/08/30/94817.html</link><dc:creator>大志</dc:creator><author>大志</author><pubDate>Sun, 30 Aug 2009 10:38:00 GMT</pubDate><guid>http://www.cppblog.com/dazhi/archive/2009/08/30/94817.html</guid><wfw:comment>http://www.cppblog.com/dazhi/comments/94817.html</wfw:comment><comments>http://www.cppblog.com/dazhi/archive/2009/08/30/94817.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/dazhi/comments/commentRss/94817.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/dazhi/services/trackbacks/94817.html</trackback:ping><description><![CDATA[<p>一. 在c中分为这几个存储区<br>1.栈 - 由编译器自动分配释放<br>2.堆 - 一般由程序员分配释放，若程序员不释放，程序结束时可能由OS回收<br>3.全局区（静态区），全局变量和静态变量的存储是放在一块的，初始化的全局变量和静态变量在一块区域，未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束释放<br>4.另外还有一个专门放常量的地方。- 程序结束释放<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>在函数体中定义的变量通常是在栈上，用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量，加了static修饰符后不管在哪里都存放在全局区（静态区）,在所有函数体外定义的static变量表示在该文件中有效，不能extern到别的文件用，在函数体内定义的static表示只在该函数体内有效。另外，函数中的"adgfdf"这样的字符串存放在常量区。比如：</p>
<p>int a = 0; //全局初始化区<br>char *p1; //全局未初始化区<br>void main()<br>{<br>&nbsp;&nbsp;&nbsp; int b; //栈<br>&nbsp;&nbsp;&nbsp; char s[] = "abc"; //栈<br>&nbsp;&nbsp;&nbsp; char *p2; //栈<br>&nbsp;&nbsp;&nbsp; char *p3 = "123456"; //123456{post.content}在常量区，p3在栈上<br>&nbsp;&nbsp;&nbsp; static int c = 0; //全局（静态）初始化区<br>&nbsp;&nbsp;&nbsp; p1 = (char *)malloc(10); //分配得来得10字节的区域在堆区<br>&nbsp;&nbsp;&nbsp; p2 = (char *)malloc(20); //分配得来得20字节的区域在堆区<br>&nbsp;&nbsp;&nbsp; strcpy(p1, "123456");<br>&nbsp;&nbsp;&nbsp; //123456{post.content}放在常量区，编译器可能会将它与p3所指向的"123456"优化成一块<br>}</p>
<p><br>二.在C++中，内存分成5个区，他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区<br>1.栈，就是那些由编译器在需要的时候分配，在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。<br>2.堆，就是那些由new分配的内存块，他们的释放编译器不去管，由我们的应用程序去控制，一般一个new就要对应一个delete。如果程序员没有释放掉，那么在程序结束后，操作系统会自动回收。<br>3.自由存储区，就是那些由malloc等分配的内存块，他和堆是十分相似的，不过它是用free来结束自己的生命的。<br>4.全局/静态存储区，全局变量和静态变量被分配到同一块内存中，在以前的C语言中，全局变量又分为初始化的和未初始化的，在C++里面没有这个区分了，他们共同占用同一块内存区。<br>5.常量存储区，这是一块比较特殊的存储区，他们里面存放的是常量，不允许修改（当然，你要通过非正当手段也可以修改）</p>
<p>三. 谈谈堆与栈的关系与区别<br>具体地说，现代计算机(串行执行机制)，都直接在代码底层支持栈的数据结构。这体现在，有专门的寄存器指向栈所在的地址，有专门的机器指令完成数据入栈出栈的操作。这种机制的特点是效率高，支持的数据有限，一般是整数，指针，浮点数等系统直接支持的数据类型，并不直接支持其他的数据结构。因为栈的这种特点，对栈的使用在程序中是非常频繁的。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把返回地址推入栈，然后跳转至子程序地址的操作，而子程序中的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。C/C++中的自动变量是直接利用栈的例子，这也就是为什么当函数返回时，该函数的自动变量自动失效的原因。 </p>
<p>和栈不同，堆的数据结构并不是由系统(无论是机器系统还是操作系统)支持的，而是由函数库提供的。基本的malloc/realloc/free 函数维护了一套内部的堆数据结构。当程序使用这些函数去获得新的内存空间时，这套函数首先试图从内部堆中寻找可用的内存空间，如果没有可以使用的内存空间，则试图利用系统调用来动态增加程序数据段的内存大小，新分配得到的空间首先被组织进内部堆中去，然后再以适当的形式返回给调用者。当程序释放分配的内存空间时，这片内存空间被返回内部堆结构中，可能会被适当的处理(比如和其他空闲空间合并成更大的空闲空间)，以更适合下一次内存分配申请。这套复杂的分配机制实际上相当于一个内存分配的缓冲池(Cache)，使用这套机制有如下若干原因：<br>1. 系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配)；这样的话对于大量的小内存分类来说会造成浪费。<br>2. 系统调用申请内存可能是代价昂贵的。系统调用可能涉及用户态和核心态的转换。<br>3. 没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。</p>
<p>堆和栈的对比<br>从以上知识可知，栈是系统提供的功能，特点是快速高效，缺点是有限制，数据不灵活；而栈是函数库提供的功能，特点是灵活方便，数据适应面广泛，但是效率有一定降低。栈是系统数据结构，对于进程/线程是唯一的；堆是函数库内部数据结构，不一定唯一。不同堆分配的内存无法互相操作。栈空间分静态分配和动态分配两种。静态分配是编译器完成的，比如自动变量(auto)的分配。动态分配由alloca函数完成。栈的动态分配无需释放(是自动的)，也就没有释放函数。为可移植的程序起见，栈的动态分配操作是不被鼓励的！堆空间的分配总是动态的，虽然程序结束时所有的数据空间都会被释放回系统，但是精确的申请内存/ 释放内存匹配是良好程序的基本要素。</p>
<p>&nbsp;&nbsp;&nbsp; 1.碎片问题：对于堆来讲，频繁的new/delete势必会造成内存空间的不连续，从而造成大量的碎片，使程序效率降低。对于栈来讲，则不会存在这个问题，因为栈是先进后出的队列，他们是如此的一一对应，以至于永远都不可能有一个内存块从栈中间弹出，在他弹出之前，在他上面的后进的栈内容已经被弹出，详细的可以&gt;参考数据结构，这里我们就不再一一讨论了。<br>&nbsp;&nbsp;&nbsp; 2.生长方向：对于堆来讲，生长方向是向上的，也就是向着内存地址增加的方向；对于栈来讲，它的生长方向是向下的，是向着内存地址减小的方向增长。<br>&nbsp;&nbsp;&nbsp; 3.分配方式：堆都是动态分配的，没有静态分配的堆。栈有2种分配方式：静态分配和动态分配。静态分配是编译器完成的，比如局部变量的分配。动态分配由alloca函数进行分配，但是栈的动态分配和堆是不同的，他的动态分配是由编译器进行释放，无需我们手工实现。<br>&nbsp;&nbsp;&nbsp; 4.分配效率：栈是机器系统提供的数据结构，计算机会在底层对栈提供支持：分配专门的寄存器存放栈的地址，压栈出栈都有专门的指令执行，这就决定了栈的效率比较高。堆则是C/C++函数库提供的，它的机制是很复杂的，例如为了分配一块内存，库函数会按照一定的算法（具体的算法可以参考数据结构/操作系统）在堆内存中搜索可用的足够大小的空间，如果没有足够大小的空间（可能是由于内存碎片太多），就有可能调用系统功能去增加程序数据段的内存空间，这样就有机会分到足够大小的内存，然后进行返回。显然，堆的效率比栈要低得多。</p>
<p>&nbsp;&nbsp;&nbsp; 明确区分堆与栈:<br>&nbsp;&nbsp;&nbsp; 在bbs上，堆与栈的区分问题，似乎是一个永恒的话题，由此可见，初学者对此往往是混淆不清的，所以我决定拿他第一个开刀。<br>&nbsp;&nbsp;&nbsp; 首先，我们举一个例子：</p>
<p>void f()<br>{ <br>&nbsp;&nbsp;&nbsp; int* p=new int[5];<br>}</p>
<p>这条短短的一句话就包含了堆与栈，看到new，我们首先就应该想到，我们分配了一块堆内存，那么指针p呢？他分配的是一块栈内存，所以这句话的意思就是：在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小，然后调用operator new分配内存，然后返回这块内存的首地址，放入栈中，他在VC6下的汇编代码如下：<br>&nbsp;&nbsp;&nbsp; 00401028&nbsp;&nbsp;&nbsp; push&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 14h<br>&nbsp;&nbsp;&nbsp; 0040102A&nbsp;&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; operator new (00401060)<br>&nbsp;&nbsp;&nbsp; 0040102F&nbsp;&nbsp;&nbsp; add&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; esp,4<br>&nbsp;&nbsp;&nbsp; 00401032&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [ebp-8],eax<br>&nbsp;&nbsp;&nbsp; 00401035&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; eax,dword ptr [ebp-8]<br>&nbsp;&nbsp;&nbsp; 00401038&nbsp;&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dword ptr [ebp-4],eax<br>&nbsp;&nbsp;&nbsp; 这里，我们为了简单并没有释放内存，那么该怎么去释放呢？是delete p么？澳，错了，应该是delete []p，这是为了告诉编译器：我删除的是一个数组，VC6就会根据相应的Cookie信息去进行释放内存的工作。<br>&nbsp;&nbsp;&nbsp; 好了，我们回到我们的主题：堆和栈究竟有什么区别？<br>&nbsp;&nbsp;&nbsp; 主要的区别由以下几点：<br>&nbsp;&nbsp;&nbsp; 1、管理方式不同；<br>&nbsp;&nbsp;&nbsp; 2、空间大小不同；<br>&nbsp;&nbsp;&nbsp; 3、能否产生碎片不同；<br>&nbsp;&nbsp;&nbsp; 4、生长方向不同；<br>&nbsp;&nbsp;&nbsp; 5、分配方式不同；<br>&nbsp;&nbsp;&nbsp; 6、分配效率不同；<br>&nbsp;&nbsp;&nbsp; 管理方式：对于栈来讲，是由编译器自动管理，无需我们手工控制；对于堆来说，释放工作由程序员控制，容易产生memory leak。<br>&nbsp;&nbsp;&nbsp; 空间大小：一般来讲在32位系统下，堆内存可以达到4G的空间，从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲，一般都是有一定的空间大小的，例如，在VC6下面，默认的栈空间大小是1M（好像是，记不清楚了）。当然，我们可以修改：<br>&nbsp;&nbsp;&nbsp; 打开工程，依次操作菜单如下：Project-&gt;Setting-&gt;Link，在Category 中选中Output，然后在Reserve中设定堆栈的最大值和commit。<br>注意：reserve最小值为4Byte；commit是保留在虚拟内存的页文件里面，它设置的较大会使栈开辟较大的值，可能增加内存的开销和启动时间。<br>&nbsp;&nbsp;&nbsp; 堆和栈相比，由于大量new/delete的使用，容易造成大量的内存碎片；由于没有专门的系统支持，效率很低；由于可能引发用户态和核心态的切换，内存的申请，代价变得更加昂贵。所以栈在程序中是应用最广泛的，就算是函数的调用也利用栈去完成，函数调用过程中的参数，返回地址，EBP和局部变量都采用栈的方式存放。所以，我们推荐大家尽量用栈，而不是用堆。</p>
<p>另外对存取效率的比较:<br>代码:</p>
<p>char s1[] = "aaaaaaaaaaaaaaa";<br>char *s2 = "bbbbbbbbbbbbbbbbb";<br>aaaaaaaaaaa是在运行时刻赋值的；<br>而bbbbbbbbbbb是在编译时就确定的；<br>但是，在以后的存取中，在栈上的数组比指针所指向的字符串(例如堆)快。<br>比如：</p>
<p>void main()<br>{<br>&nbsp;&nbsp;&nbsp; char a = 1;<br>&nbsp;&nbsp;&nbsp; char c[] = "1234567890";<br>&nbsp;&nbsp;&nbsp; char *p ="1234567890";<br>&nbsp;&nbsp;&nbsp; a = c[1];<br>&nbsp;&nbsp;&nbsp; a = p[1];<br>&nbsp;&nbsp;&nbsp; return;<br>}<br>对应的汇编代码<br>10: a = c[1];<br>00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]<br>0040106A 88 4D FC mov byte ptr [ebp-4],cl<br>11: a = p[1];<br>0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]<br>00401070 8A 42 01 mov al,byte ptr [edx+1]<br>00401073 88 45 FC mov byte ptr [ebp-4],al<br>第一种在读取时直接就把字符串中的元素读到寄存器cl中，而第二种则要先把指针值读到edx中，在根据edx读取字符，显然慢了.<br>&nbsp;&nbsp;&nbsp; 无论是堆还是栈，都要防止越界现象的发生（除非你是故意使其越界），因为越界的结果要么是程序崩溃，要么是摧毁程序的堆、栈结构，产生以想不到的结果,就算是在你的程序运行过程中，没有发生上面的问题，你还是要小心，说不定什么时候就崩掉,编写稳定安全的代码才是最重要的。</p>
<p>static用来控制变量的存储方式和可见性<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 函数内部定义的变量，在程序执行到它的定义处时，编译器为它在栈上分配空间，函数在栈上分配的空间在此函数执行结束时会释放掉，这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时，如何实现？ 最容易想到的方法是定义一个全局的变量，但定义为一个全局变量有许多缺点，最明显的缺点是破坏了此变量的访问范围（使得在此函数中定义的变量，不仅仅受此 函数控制）。 </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部，对外不可见。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static的内部机制：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用，所以静态数据成员不能在任何函数内分配空间和初始化。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这样，它的空间分配有三个可能的地方，一是作为类的外部接口的头文件，那里有类声明；二是类定义的内部实现，那里有类的成员函数定义；三是应用程序的main（）函数前的全局数据声明和定义处。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 静态数据成员要实际地分配空间，故不能在类的声明中定义（只能声明数据成员）。类声明只声明一个类的&#8220;尺寸和规格&#8221;，并不进行实际的内存分配，所以在类声 明中写成定义是错误的。它也不能在头文件中类声明的外部定义，因为那会造成在多个使用该类的源文件中，对其重复定义。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static被引入以告知编译器，将变量存储在程序的静态存储区而非栈上空间，静态数据成员按定义出现的先后顺序依次初始化，注意静态成员嵌套时，要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static的优势：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以节省内存，因为它是所有对象所公有的，因此，对多个对象来说，静态数据成员只存储一处，供所有对象共用。静态数据成员的值对每个对象都是一样，但它的 值是可以更新的。只要对静态数据成员的值更新一次，保证所有对象存取更新后的相同的值，这样可以提高时间效率。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 引用静态数据成员时，采用如下格式：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;类名&gt;::&lt;静态成员名&gt;<br>&nbsp;&nbsp;&nbsp; 如果静态数据成员的访问权限允许的话(即public的成员)，可在程序中，按上述格式<br>来引用静态数据成员。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PS:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1)类的静态成员函数是属于整个类而非类的对象，所以它没有this指针，这就导致<br>了它仅能访问类的静态数据和静态成员函数。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2)不能将静态成员函数定义为虚函数。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3)由于静态成员声明于类中，操作于其外，所以对其取地址操作，就多少有些特殊<br>，变量地址是指向其数据类型的指针 ，函数地址类型是一个&#8220;nonmember函数指针&#8221;。 </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (4)由于静态成员函数没有this指针，所以就差不多等同于nonmember函数，结果就<br>产生了一个意想不到的好处：成为一个callback函数，使得我们得以将C++和C-based X W<br>indow系统结合，同时也成功的应用于线程函数身上。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (5)static并没有增加程序的时空开销，相反她还缩短了子类对父类静态成员的访问<br>时间，节省了子类的内存空间。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (6)静态数据成员在&lt;定义或说明&gt;时前面加关键字static。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (7)静态数据成员是静态存储的，所以必须对它进行初始化。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (8)静态成员初始化与一般数据成员初始化不同:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 初始化在类体外进行，而前面不加static，以免与一般静态变量或对象相混淆；<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 初始化时不加该成员的访问权限控制符private，public等；<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 初始化时使用作用域运算符来标明它所属类；<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 所以我们得出静态数据成员初始化的格式：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;数据类型&gt;&lt;类名&gt;::&lt;静态数据成员名&gt;=&lt;值&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (9)为了防止父类的影响，可以在子类定义一个与父类相同的静态变量，以屏蔽父类的影响。这里有一点需要注意：我们说静态成员为父类和子类共享，但我们有 重复定义了静态成员，这会不会引起错误呢？不会，我们的编译器采用了一种绝妙的手法：name-mangling 用以生成唯一的标志。</p>
<p>补充：new&nbsp; delete［］,基本类型的对象没有析构函数（例如 int , char ），所以回收基本类型组成的数组空间 delete&nbsp; delete[] 都是应该可以如： int p = new int[10], delete p 和delete[]p 都可 。但是对于类对象数组（如string strArr = new string[10]），只能 delete[]。对 new 的单个对象，只能 delete 不能 delete[] 回收空间 。</p>
<img src ="http://www.cppblog.com/dazhi/aggbug/94817.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/dazhi/" target="_blank">大志</a> 2009-08-30 18:38 <a href="http://www.cppblog.com/dazhi/archive/2009/08/30/94817.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC++常用设置</title><link>http://www.cppblog.com/dazhi/archive/2009/08/29/94764.html</link><dc:creator>大志</dc:creator><author>大志</author><pubDate>Sat, 29 Aug 2009 07:32:00 GMT</pubDate><guid>http://www.cppblog.com/dazhi/archive/2009/08/29/94764.html</guid><wfw:comment>http://www.cppblog.com/dazhi/comments/94764.html</wfw:comment><comments>http://www.cppblog.com/dazhi/archive/2009/08/29/94764.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/dazhi/comments/commentRss/94764.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/dazhi/services/trackbacks/94764.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;
<div class=date>&nbsp;</div>
<table style="TABLE-LAYOUT: fixed; WIDTH: 100%">
    <tbody>
        <tr>
            <td>
            <div class=cnt id=blog_text>
            <p>编译参数的设置。主要通过IDE的菜单项Project-&gt;Settings-&gt;C/C++页来完成。我们可以看到这一页的最下面Project Options中的内容，一般如下：</p>
            <p>　　/nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_AFXDLL" /D "_M</p>
            <p>　　BCS" /Fp"Debug/WritingDlgTest.pch" /Yu"stdafx.h" /Fo"Debug/" /Fd"Debug/" /FD /GZ /c</p>
            <p>　　各个参数代表的意义，可以参考Msdn。比如/nologo表示编译时不在输出窗口显示这些设置(我们可以把这个参数去掉来看看效果)等等。一般我们不会直接修改这些设置，而是通过这一页最上面的Category中的各项来完成。</p>
            <p>　　1) General：一些总体设置。Warning level用来控制警告信息，其中Level 1是最严重的级别;Warnings as errors将警告信息当作错误处理;Optimizations是代码优化，可以在Category的Optimizations项中进行更细的设置;Generate browse info用以生成.sbr文件，记录类、变量等符号信息，可以在Category的Listing Files项中进行更多的设置。Debug info，生成调试信息：None，不产生任何调试信息(编译比较快);Line Numbers Only，仅生成全局的和外部符号的调试信息到.OBJ文件或.EXE文件，减小目标文件的尺寸;C 7.0- Compatible，记录调试器用到的所有符号信息到.OBJ文件和.EXE文件;Program Database，创建.PDB文件记录所有调试信息;Program Database for "Edit &amp; Continue"，创建.PDB文件记录所有调试信息，并且支持调试时编辑。</p>
            <p>　　2) C++ Language：pointer_to_member representation用来设置类定义/引用的先后关系，一般为Best-Case Always表示在引用类之前该类肯定已经定义了;Enable Exception Handling，进行同步的异常处理;Enable Run-Time Type Information迫使编译器增加代码在运行时进行对象类型检查;Disable Construction Displacements，设置类构造/析构函数调用虚函数问题。</p>
            <p>　　3) Code Generation：Processor表示代码指令优化，可以为80386、80486、Pentium、Pentium Pro，或者Blend表示混合以上各种优化。Use run-time library用以指定程序运行时使用的运行时库(单线程或多线程，Debug版本或Release版本)，有一个原则就是，一个进程不要同时使用几个版本的运行时库。Single-Threaded，静态连接LIBC.LIB库;Debug Single-Threaded，静态连接LIBCD.LIB库;Multithreaded，静态连接LIBCMT.LIB库;Debug Multithreaded，静态连接LIBCMTD.LIB库;Multithreaded DLL，动态连接MSVCRT.DLL库;Debug Multithreaded DLL，动态连接MSVCRTD.DLL库。连接了单线程库就不支持多线程调用，连接了多线程库就要求创建多线程的应用程序。</p>
            <p>　　Calling convention可以用来设定调用约定，有三种：__cdecl、__fastcall和__stdcall。各种调用约定的主要区别在于，函数调用时，函数的参数是从左到右压入堆栈还是从右到左压入堆栈;在函数返回时，由函数的调用者来清理压入堆栈的参数还是由函数本身来清理;以及在编译时对函数名进行的命名修饰(可以通过Listing Files看到各种命名修饰方式)。Struct member alignment用以指定数据结构中的成员变量在内存中是按几字节对齐的，根据计算机数据总线的位数，不同的对齐方式存取数据的速度不一样。这个参数对数据包网络传输等应用尤为重要，不是存取速度问题，而是数据位的精确定义问题，一般在程序中使用#pragma pack来指定。</p>
            <p>　　4) Customize：Disable Language Extensions，表示不使用微软为标准C做的语言扩展;Eliminate Duplicate Strings，主要用于字符串优化(将字符串放到缓充池里以节省空间)，使用这个参数，使得</p>
            <p>　　char *sBuffer = "This is a character buffer";</p>
            <p>　　char *tBuffer = "This is a character buffer";</p>
            <p>　　sBuffer和tBuffer指向的是同一块内存空间;Enable Function-Level Linking ，告诉编译器将各个函数按打包格式编译;Enables minimal rebuild，通过保存关联信息到.IDB文件，使编译器只对最新类定义改动过的源文件进行重编译，提高编译速度;Enable Incremental Compilation，同样通过.IDB文件保存的信息，只重编译最新改动过的函数;Suppress Startup Banner and Information Messages，用以控制参数是否在output窗口输出。</p>
            <p>　　5) Listing Files：Generate browse info的功能上面已经提到过。这里可以进行更多的设置。Exclude Local Variables from Browse Info表示是否将局部变量的信息放到.SBR文件中。Listing file type可以设置生成的列表信息文件的内容：Assembly-Only Listing仅生成汇编代码文件(.ASM扩展名);Assembly With Machine Code生成机器代码和汇编代码文件(.COD扩展名);Assembly With Source Code生成源代码和汇编代码文件(.ASM扩展名);Assembly, Machine Code,and Source生成机器码、源代码和汇编代码文件(.COD扩展名)。Listing file name为生成的信息文件的路径，一般为Debug或Release目录下，生成的文件名自动取源文件的文件名。<br>6) Optimizations：代码优化设置。可以选择Maximize Speed生成最快速的代码，或Minimize Size生成最小尺寸的程序，或者Customize定制优化。定制的内容包括：</p>
            <p>　　Assume No Aliasing，不使用别名(提高速度);</p>
            <p>　　Assume Aliasing Across Function Calls，仅函数内部不使用别名;</p>
            <p>　　Global Optimizations，全局优化，比如经常用到的变量使用寄存器保存，或者循环内的计算优化，如</p>
            <p>　　i = -100;</p>
            <p>　　while( i &lt; 0 ){ i += x + y;}</p>
            <p>　　会被优化为</p>
            <p>　　i = -100;</p>
            <p>　　t = x + y;</p>
            <p>　　while( i &lt; 0 ){i += t;}</p>
            <p>　　Generate Intrinsic Functions，使用内部函数替换一些函数调用(提高速度);</p>
            <p>　　Improve Float Consistency，浮点运算方面的优化;</p>
            <p>　　Favor Small Code，程序(exe或dll)尺寸优化优先于代码速度优化;</p>
            <p>　　Favor Fast Code，程序(exe或dll)代码速度优化优先于尺寸优化;</p>
            <p>　　Frame-Pointer Omission，不使用帧指针，以提高函数调用速度;</p>
            <p>　　Full Optimization，组合了几种参数，以生成最快的程序代码。</p>
            <p>　　Inline function expansion，内联函数扩展的三种优化(使用内联可以节省函数调用的开销，加快程序速度)：Disable不使用内联;Only __inline，仅函数定义前有inline或__inline标记使用内联;Any Suitable，除了inline或__inline标记的函数外，编译器&#8220;觉得&#8221;应该使用内联的函数，都使用内联。</p>
            <p>　　7) Precompiled Headers：预编译头文件的设置。使用预编译可以提高重复编译的速度。IDE一般将一些公共的、不大变动的头文件(比如afxwin.h等)集中放到 stdafx.h中，这一部分代码就不必每次都重新编译(除非是Rebuild All)。</p>
            <p>　　8) Preprocessor：预编译处理。可以定义/解除定义一些常量。Additional include directories，可以指定额外的包含目录，一般是相对于本项目的目录，如..\Include。</p>
            <p>　　连接参数的设置。主要通过IDE的菜单项Project-&gt;Settings-&gt;Link页来完成。我们可以看到这一页的最下面Project Options中的内容，一般如下：</p>
            <p>　　/nologo /subsystem:windows /incremental:yes /pdb:"Debug/WritingDlgTest.pdb" /debug /machi</p>
            <p>　　ne:I386 /out:"Debug/WritingDlgTest.exe" /pdbtype:sept</p>
            <p>　　下面我们分别来看一下Category中的各项设置。</p>
            <p>　　1) General：一些总体设置。可以设置生成的文件路径、文件名;连接的库文件;Generate debug info，生成Debug信息到.PDB文件(具体格式可以在Category-&gt;Debug中设置);Ignore All Default Libraries，放弃所有默认的库连接;Link Incrementally，通过生成. ILK文件实现递增式连接以提高后续连接速度，但一般这种方式下生成的文件(EXE或DLL)较大;Generate Mapfile，生成.MAP文件记录模块相关信息;Enable Profiling，这个参数通常与Generate Mapfile参数同时使用，而且如果产生Debug信息的话，不能用.PDB文件，而且必须用Microsoft Format。</p>
            <p>　　2) Customize：这里可以进行使用程序数据库文件的设置。Force File Output ，强制产生输出文件(EXE或DLL);Print Progress Messages，可以将连接过程中的进度信息输出到Output窗口。</p>
            <p>　　3) Debug：设置是否生成调试信息，以及调试信息的格式。格式可以有Microsoft Format、COFF Format(Common Object File Format)和Both Formats三种选择;Separate Types，表示将Debug格式信息以独立的.PDB文件存放，还是直接放在各个源文件的.PDB文件中。选中的话，表示采用后者的方式，这种方式调试启动比较快。</p>
            <p>　　4) Input：这里可以指定要连接的库文件，放弃连接的库文件。还可以增加额外的库文件目录，一般是相对于本项目的目录，如..\Lib。Force Symbol References，可以指定连接特定符号定义的库。</p>
            <p>　　5) Output：Base Address可以改变程序默认的基地址(EXE文件默认为0x400000，DLL默认为x10000000)，操作系统装载一个程序时总是试着先从这个基地址开始。Entry-Point Symbol可以指定程序的入口地址，一般为一个函数名(且必须采用__stdcall调用约定)。一般Win32的程序，EXE的入口为 WinMain，DLL的入口为DllEntryPoint;最好让连接器自动设置程序的入口点。默认情况下，通过一个C的运行时库函数来实现：控制台程序采用mainCRTStartup (或wmainCRTStartup)去调用程序的main (或wmain)函数;Windows程序采用WinMainCRTStartup (或 wWinMainCRTStartup)调用程序的WinMain (或 wWinMain，必须采用__stdcall调用约定);DLL采用_DllMainCRTStartup调用DllMain函数(必须采用 __stdcall调用约定)。Stack allocations，用以设置程序使用的堆栈大小(请使用十进制)，默认为1兆字节。Version Information告诉连接器在EXE或DLL文件的开始部分放上版本号。</p>
            <p>值得注意的是，上面各个参数是大小写敏感的;在参数后加上&#8220;-&#8221;表示该参数无效;各个参数值选项</p>
            <p>　　有&#8220;*&#8221;的表示为该参数的默认值;可以使用页右上角的&#8220;Reset&#8221;按钮来恢复该页的所有默认设置。</p>
            <p>　　其它一些参数设置</p>
            <p>　　1) Project-&gt;Settings-&gt;General，可以设置连接MFC库的方式(静态或动态)。如果是动态连</p>
            <p>　　接，在你的软件发布时不要忘了带上MFC的DLL。</p>
            <p>　　2) Project-&gt;Settings-&gt;Debug，可以设置调试时运行的可执行文件，以及命令行参数等。</p>
            <p>　　3) Project-&gt;Settings-&gt;Custom Build，可以设置编译/连接成功后自动执行一些操作。比较有</p>
            <p>　　用的是，写COM时希望IDE对编译通过的COM文件自动注册，可以如下设置：</p>
            <p>　　Description: Register COM</p>
            <p>　　Commands: regsvr32 /s /c $(TargetPath)</p>
            <p>　　echo regsvr32 exe.time &gt; $(TargetDir)\$(TargetName).trg</p>
            <p>　　Outputs: $(TargetDir)\$(TargetName).trg</p>
            <p>　　4) Tools-&gt;Options-&gt;Directories，设置系统的Include、Library路径。</p>
            <p>　　一些小窍门(针对Visual C++)</p>
            <p>　　1) 有时候，你可能在编译的时候，计算机突然非法关机了(可能某人不小心碰了电源或你的内存不稳定等原因)。当你重启机器后打开刚才的项目，重新进行编译，发现IDE会崩掉。你或许以为你的编译器坏了，其实不然(你试试编译其它项目，还是好的!)，你只要将项目的.ncb、.opt、.aps、.clw文件以及Debug、Release目录下的所有文件都删掉，然后重新编译就行了。</p>
            <p>　　2) 如果你想与别人共享你的源代码项目，但是把整个项目做拷贝又太大。你完全可以删掉以下文件：.dsw、.ncb、.opt、.aps、.clw、. plg文件以及Debug、Release目录下的所有文件。</p>
            <p>　　3) 当你的Workspace中包含多个Project的时候，你可能不能直观地、一眼看出来哪个是当前项目。可以如下设置：Tools-&gt;Options-&gt;Format，然后在Category中选择Workspace window，改变其默认的字体(比如设成Fixedsys)就行了。</p>
            <p>　　4) 如何给已有的Project改名字?将该Project关掉。然后以文本格式打开.dsp文件，替换原来的Project名字即可。</p>
            <p>　　5) VC6对类成员的智能提示功能很有用，但有时候会失灵。你可以先关掉项目，将.clw和.ncb删掉，然后重新打开项目，点击菜单项 View-&gt;ClassWizard，在弹出的对话框中按一下&#8220;Add All&#8221;按钮;重新Rebuild All。应该可以解决问题。</p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/dazhi/aggbug/94764.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/dazhi/" target="_blank">大志</a> 2009-08-29 15:32 <a href="http://www.cppblog.com/dazhi/archive/2009/08/29/94764.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>