??xml version="1.0" encoding="utf-8" standalone="yes"?>久久99热这里只有精品国产,97久久精品国产精品青草,国产精品美女久久久久久2018http://m.shnenglu.com/mysileng/category/20052.htmlzh-cnFri, 16 Jan 2015 15:44:01 GMTFri, 16 Jan 2015 15:44:01 GMT60C/C++ Volatile关键词深度剖?/title><link>http://m.shnenglu.com/mysileng/archive/2015/01/13/209505.html</link><dc:creator>鑫龙</dc:creator><author>鑫龙</author><pubDate>Tue, 13 Jan 2015 12:35:00 GMT</pubDate><guid>http://m.shnenglu.com/mysileng/archive/2015/01/13/209505.html</guid><wfw:comment>http://m.shnenglu.com/mysileng/comments/209505.html</wfw:comment><comments>http://m.shnenglu.com/mysileng/archive/2015/01/13/209505.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://m.shnenglu.com/mysileng/comments/commentRss/209505.html</wfw:commentRss><trackback:ping>http://m.shnenglu.com/mysileng/services/trackbacks/209505.html</trackback:ping><description><![CDATA[     摘要: 1    背景    12    VolatileQ易变的    12.1    结    23    Volatil...  <a href='http://m.shnenglu.com/mysileng/archive/2015/01/13/209505.html'>阅读全文</a><img src ="http://m.shnenglu.com/mysileng/aggbug/209505.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://m.shnenglu.com/mysileng/" target="_blank">鑫龙</a> 2015-01-13 20:35 <a href="http://m.shnenglu.com/mysileng/archive/2015/01/13/209505.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>函数调用U定Q_stdcallQ_cdeclQ_fastcall (?http://m.shnenglu.com/mysileng/archive/2012/12/27/196703.html鑫龙鑫龙Thu, 27 Dec 2012 03:41:00 GMThttp://m.shnenglu.com/mysileng/archive/2012/12/27/196703.htmlhttp://m.shnenglu.com/mysileng/comments/196703.htmlhttp://m.shnenglu.com/mysileng/archive/2012/12/27/196703.html#Feedback0http://m.shnenglu.com/mysileng/comments/commentRss/196703.htmlhttp://m.shnenglu.com/mysileng/services/trackbacks/196703.html(1) _stdcall调用
  _stdcall 是Standard Call的羃写,是C++的标准调用方式,也是PascalE序的缺省调用方式,参数采用从右到左的压栈方式,如果是调用类成员的话Q最后一个入栈的是this指针。这些堆栈中的参数由被调函数自n在返回前清空Q?/div>
使用的指令是 retn XQX表示参数占用的字节数QCPU在ret之后自动弹出X个字节的堆栈I间。称动清栈?/div>
  WIN32 Api都采用_stdcall调用方式Q这L宏定义说明了问题Q?/div>
  #define WINAPI _stdcall
  按C~译方式Q_stdcall调用U定在输出函数名前面加下划线Q后面加“@”W号和参数的字节敎ͼ形如_functionname@number?/div>
(2) _cdecl调用
  _cdecl是C Declaration[声明]的羃写,表示C/C++语言默认的函数调用方法,也是C/C++的缺省调用方式,所有参?span style="color: red;">从右到左依次入栈Q这些参数由调用者清?/span>Q称为手动清栈。_cedclU定的函数只能被C/C++调用Q每一个调用它?/div>
函数都包含清I堆栈的代码Q所以生的可执行文件大会比调用_stdcall函数的大。被调用函数无需要求调用者传递多参敎ͼ调用者传递过多或者过的参数Q甚臛_全不同的参数都不会生编译阶D늚错误?/div>
  ׃_cdecl调用方式的参数内存栈p用者维护,所以变长参数的函数能(也只能)使用q种调用U定?/div>
  ׃Visual C++默认采用_cdecl 调用方式Q所以VC中中调用DLLӞ用户应用_stdcall调用U定?/div>
  按C~译方式Q_cdecl调用U定仅在输出函数名前面加下划U,形如_functionname?/div>
(3) _fastcall调用
  _fastcall 是编译器指定的快速调用方式。由于大多数的函数参C数很,使用堆栈传递比较费时。因此_fastcall通常规定前两个Q或若干个)参数由寄存器传递,其余参数q是通过堆栈传递。不同编译器~译的程序规定的?/div>
存器不同。返回方式和_stdcall相当?/div>
  _fastcall调用较快Q它通过CPU内部寄存器传递参数?/div>
 
(4)PASCAL调用
PASCAL 是Pascal语言的函数调用方式,也可以在C/C++中用,参数压栈序与_cdecl和_stdcall相反?/div>
 
归纳如下:
调用U定            入栈参数清理         参数入栈序
-----------        --------------         ----------------
cdecl               调用者处?           ?>?/div>
stdcall             函数自己处理        ?>?/div>
fastcall            函数自己处理        依赖于编译器
pascal             函数自己处理        ?>?/div>


鑫龙 2012-12-27 11:41 发表评论
]]>左值和叛_|转)http://m.shnenglu.com/mysileng/archive/2012/12/16/196350.html鑫龙鑫龙Sun, 16 Dec 2012 07:08:00 GMThttp://m.shnenglu.com/mysileng/archive/2012/12/16/196350.htmlhttp://m.shnenglu.com/mysileng/comments/196350.htmlhttp://m.shnenglu.com/mysileng/archive/2012/12/16/196350.html#Feedback0http://m.shnenglu.com/mysileng/comments/commentRss/196350.htmlhttp://m.shnenglu.com/mysileng/services/trackbacks/196350.html

左值右?/span>

左|lvalueQ和叛_|rvalueQ是~程中两个非常基本的概念Q但是也非常Ҏ让h误解Q看了很多文章,自我感觉真正这个问题讲的很透彻的文章还没有看见Q所以自告奋勇来试一下。如果左值右值的概念不是非常清楚的话Q它们迟早会像拦路虎一栯出来Q让你烦心不Ԍ像玩电脑游戏的时候每隔一D|间L那么几个地雷考验你的耐性,如果一ơ把所有地h就好了。:Q?/span>

左|lvalueQ和叛_|rvalueQ最先来源于~译理论Q感谢南大小癑֐的programsQ。在C语言中表CZ于赋D符两侧的两个|左边的就叫左|双的就叫右倹{比如:

int ii = 5;//ii是左|5是右?/span>

int jj = ii;//jj是左|ii是右?/span>

上面表明Q左D定可以作为右g用,但反之则不然。左值和叛_的最早区别就在于能否改变。左值是可以变的Q右g能变。【注1?/span>

?/span>1Q这一点在C++中已l?/span>猪羊变色Q不再成立。拱猪游戏还是挺好玩的,我还真抓q好几次全红心,不过真的好险。:Q?/span>

在很多文章中提到Q在C++中,左值更多的指的是可以定位,x地址的|而右值没有地址。【注2?/span>

?/span>2Q这一点仍然不准确Q我在程序中生成一个时右?/span>std::vector()Q你能够说它没有地址吗?N它是没有肉体的鬼或q灵Q它是有地址的,而且它也是绝对的叛_{?/span>

在现?/span>C++中,现在左值和叛_基本上已经失去它们原本所h的意义,对于左D辑ּQ通过具体名字和引用(pointer or referenceQ来指定一个对象。非左值就是右倹{我来下一个定义:

左DC程序中必须有一个特定的名字引用到这个倹{?/span>

叛_DC程序中没有一个特定的名字引用到这个倹{?/span>

跟它们是否可以改变,是否在栈或堆Q?/span>stack or heapQ中有地址毫无关系?/span>

1Q左?/span>

在下面的代码中:

int ii = 5;

int const jj = ii;

int a[5];

a[0] = 100;

*(a+3) = 200;

int const& max( int const& a, int const& b ) //call by reference

{

      return a > b ? a : b;

}

int& fun(int& a) //call by reference

{

      a += 5;

   return a;

}

iiQ?/span>jjQ?/span>a[0]Q?/span>*(a+3)Q还有函?/span>max的返回值比?/span>max(ii, jj)Q【注3】函?/span>fun的返回?/span>fun(ii)都是左倹{,它们都是有特定的引用名字的倹{?/span>iiQ?/span>jjQ?/span>a[0]Q?/span>*(a+3)Q?/span>max(ii, jj)Q?/span>fun(ii)分别是它们的名字?/span>

?/span>3Q在q里有一个不太容易分清楚的盲炏V那是有h会问max(8, 9)到达是左D是右|C++标准规定帔R引用Q?/span>reference to constQ可以引用到叛_|所?/span>max(8, 9)g应该是右|不过不管它是左|q是叛_|我们都不能试囑֎改变它。ؓ了与前面的概念一_我认为它是左|不可改变的常量左倹{?/span>

左值有不能改变的,卌const所修饰的左|比如上面?/span>jjQ?/span>max(ii, jj)都是被常量(constQ魔咒所C的左倹{?/span>

没有?/span>constC的左值当然是可以改变的,比如下面的代码都是成立的Q?/span>

ii = 600;

a[0] = 700;

fun(ii) = 800; //OK!

我们的眼睛没有问题,fun(ii) = 800;完全正确Q因为它是可以改变的左倹{?/span>所以我们看STL的源码,׃理解std::vector中的重蝲operator[]q算W的q回gؓ什么要写成引用Q因?/span>operator[]必须q回左倹{?/span>

 

2Q右?/span>

没有特定名字的值是叛_{先看下面的代码Q?/span>

std::list();

std::string(“It is a rvalue!”);

int fun1() //call by value

{

      

}

int* fun2() //call by reference

{

      

}

其中std::list()Q?/span>std::string(“It is a rvalue!”)Q函?/span>fun1的返回?/span>fun1()Q函?/span>fun2的返回?/span>fun2()都是叛_|它们的值都没有特定的名字去引用。也许有Z奇怪,fun2()也是叛_|最前面?/span>max(a,b)不是左值吗Q?/span>

L清楚Q函?/span>fun2的返回值是pointerQ?/span>pointer也是call by valueQ而函?/span>max的返回值是referenceQ?/span>reference?/span>call by reference。所以说C++中引?/span>reference不仅仅是Z方便Q它也是一U必R【注4?/span>

?/span>4Q?/span>Scott Meyer写的?/span>More Effective C++》的条款1专门讲了pointer?/span>reference的区别,写的很好QL别的非常清楚?/span>

fun2()是右|?/span> *fun2()却是左|pl常看到?/span>*p一P所以看C++库代码的时候,会发现重?/span>operator*的函数返回值是reference?/span>

当然我还遗漏了一U右|那就是字面上的(literalQ|比如5Q?/span>8.23Q?/span>’a’{等理所当然的都是右倹{?/span>

叛_最初出现的时候,一个最大的特征是不可改变。但p我们的道h准一P时代不同了,标准也变化了Q以前的三纲五常早已l被扔到历史的垃圑֠里面了?/span>

C++中有可以改变的右|而且q个Ҏ还非常有用。那是用户自定义的c(classQ的构造函数生成的临时对象。比如:

std::vector(9)Q?/span>std::deque()Q?#8230;…都是可以改变的右倹{在Herb Sutter的?/span>More Exceptional C++》中的条?/span>7?/span>page51|q样几行代码Q?/span>

// Example 7-2(b): The right way to shrink-to-fit a vector.

vector<Customer> c( 10000 );

// ...now c.capacity() >= 10000...

// erase all but the first 10 elements

c.erase( c.begin()+10, c.end() );

// the following line does shrink c's

// internal buffer to fit (or close)

vector<Customer>( c ).swap( c );

// ...now c.capacity() == c.size(), or

// perhaps a little more than c.size()

认真看几遍,你会发现?/span>vector的大增大到一定程度,你又用不着q么多空间的时候,你会惛_法把它收~到最合适的大小Q但利用别的办法比如调用成员函数reserve()都无法办刎ͼq个时候就必须利用叛_可以改变这个性质了?/span>

vector<Customer>( c ).swap( c );q行代码是点睛之处?/span>

首先使用复制构造函数生成时右?/span>vector<Customer>( c )Q这个右值正好是合适大,然后?/span>c交换【注5】,c变成合适大了Q最后在整个表达式结束的时候,q个临时叛_析构归q内存空间。真是绅士一般的优雅Q?/span>

?/span>5Q这个时候这个时右值就发生了改变?/span>

如果q不理解Q可以看看书Q或者直接看库的源代码?/span>

至于Z么会q样Q我思考了一下,我想是这LQ我们看c(classQ的数据布置l构Q会发现它的每一个数据成员都是有名字的,我想~译器在~译的过E中Q都会生成一个外部不所知的对这个时对象右值的名字引用Q但需要改变这个时对象的时候,q个名字q上了。比如:

class Point

{

public: //UaZ方便Q我把数据成员公开Q现实中量不要q样?/span>

      int x, y ,z;

      ……//其他各种成员函数

};

我们现在可以改变右|用到了匿名的引用名字?/span>

Point().x = 6;//改变了右?/span>

Point().y = 6;//同意改变了右|不过注意Q这个右D上面的不是同一个?/span>

ȝ

左值和叛_的真正区别我想是q些了,左DC有特定的名字引用,而右值没有特定的名字引用。当然我仍然会有疏忽Q希望大家能够提醒我Q指正我的不?/span>

前两天看Herb Sutter从邮件中寄来的新文章Q我订阅他的新文章邮仉知Q,一是?/span>Tuple数据l构的,没有什么新意,以前好像看过Q还有一名字是Q(MostlyQ?/span>Private,地址?/span>http://www.cuj.com/documents/s=8273/cujcexp2107sutter/ 内容本nq不深,但看完文章,发现随处可见C++的L诡云Ԍ又会对什么叫袖里乑֝Q滴水藏多一份感性认识?/span>

在下一文章我想从不同于一般的角度Q从自己的经历谈谈在校毕业生?/span>IT行业怎样扑ַ作,我想会让所有读者都有一些思考,不仅仅是求职者。题目我已经惛_了,叫《扮虎吃猪》,不过现在我有一些别的事情要忙,所以可能会让大家等几天?/span>

转蝲h明来源,谢谢Q?/span>

吴桐写于2003.6.20

最q修?/span>2003.6.21

转自Q?/span> http://blog.csdn.net/csdnji/article/details/169200



鑫龙 2012-12-16 15:08 发表评论
]]>
拯构造函数和赋值操作函数的参数Z么是const?amp;?! http://m.shnenglu.com/mysileng/archive/2012/12/16/196349.html鑫龙鑫龙Sun, 16 Dec 2012 06:52:00 GMThttp://m.shnenglu.com/mysileng/archive/2012/12/16/196349.htmlhttp://m.shnenglu.com/mysileng/comments/196349.htmlhttp://m.shnenglu.com/mysileng/archive/2012/12/16/196349.html#Feedback0http://m.shnenglu.com/mysileng/comments/commentRss/196349.htmlhttp://m.shnenglu.com/mysileng/services/trackbacks/196349.html习惯了这样写Q但有时候会反问下自׃ؓ什么要const?amp;?
1.Z么要&?
{:&是必ȝ?br />如果它不是引用,在传递参数时Q会产生一个局部变量做参数Q而这个局部变量的构造又要调copy构造函C?....那就子子孙孙无穷匮了....
但如果是引用Q在传递参数时候,局部变量只需要绑定原变量Q而不需要再一ơ调用copy构造函数?br />
2.Z么要const?
{:const不是必须的?br />
构造函数是用引用方式传递复制对象,引用方式传递的是地址Q因此在构造函数内对该引用的修改会影响源对象。而你在用对象a1构造a2Ӟ自然不希望复制构造函C改变a1的内容,因此要防止复制构造函数内部修改该引用Q所以一般用const声明。加不加constq是需要看copy构造的行ؓQ比如std::auto_ptr的构造函敎ͼ没有const,因ؓ它要获得原有对象指针的拥有权?/pre>

鑫龙 2012-12-16 14:52 发表评论
]]>C++模板元编E?入门?(?http://m.shnenglu.com/mysileng/archive/2012/12/01/195869.html鑫龙鑫龙Sat, 01 Dec 2012 12:01:00 GMThttp://m.shnenglu.com/mysileng/archive/2012/12/01/195869.htmlhttp://m.shnenglu.com/mysileng/comments/195869.htmlhttp://m.shnenglu.com/mysileng/archive/2012/12/01/195869.html#Feedback0http://m.shnenglu.com/mysileng/comments/commentRss/195869.htmlhttp://m.shnenglu.com/mysileng/services/trackbacks/195869.html

C++模板元编E?入门?/a>

最q一直在看STL和BoostQ源码里边好多涉及到模板元编E技术,单了解一下,备忘QBoost Python中的涉及模板元的部分重点x一下)?/p>

范例引入

主要思想

利用模板特化机制实现~译期条仉择l构Q利用递归模板实现~译期@环结构,模板元程序则q译器在编译期解释执行?/p>

优劣及适用情况

通过计从q行期{U至~译期,在结果程序启动之前做可能多的工作,最l获得速度更快的程序。也是说模板元~程的优势在于:

  1.以编译耗时Zh来卓的q行期性能Q一般用于ؓ性能要求严格的数D换取更高的性能Q。通常来说Q一个有意义的程序的q行ơ数Q或服役旉QLq远过~译ơ数Q或~译旉Q?/p>

  2.提供~译期类型计,通常q才是模板元~程大放异彩的地斏V?/p>

模板元编E技术ƈ非都是优点:

  1.代码可读性差Q以cL板的方式描述法也许有点抽象?/p>

  2.调试困难Q元E序执行于编译期Q没有用于单步跟t元E序执行的调试器Q用于设|断炏V察看数据等Q。程序员可做的只能是{待~译q程p|Q然后h工破译编译器倾泻到屏q上的错误信息?/p>

  3.~译旉长,通常带有模板元程序的E序生成的代码尺寸要比普通程序的大,

  4.可移植性较差,对于模板元编E用的高模板Ҏ,不同的编译器的支持度不同?/p>

ȝQ?/p>

模板元编E技术不适用普通程序员的日常应用,它常怼做ؓcd开发的提供技术支持,为常规模板代码的内核的关键算法实现更好的性能或者编译期cd计算。模板元E序几乎L应该与常规代码结合用被装在一个程序库的内部。对于库的用h_它应该是透明的?/p>

工程应用实例

1. Blitz++Q由于模板元~程最先是因ؓ数D而被发现的,因此早期的研I工作主要集中于数D方面,Blitz++库利用模板将q行期计{U至~译期的库,主要提供了对向量、矩늭q行处理的线性代数计?/p>

2.LokiQ将模板元编E在cd计算斚w的威力应用于设计模式领域Q利用元~程Q以及其他一些重要的设计技术)实现了一些常见的设计模式之泛型版本。Loki库中的Abstract Factory泛型模式卛_助于这U机制实现在不损q型安全性的前提下降低对cd的静态依赖性?/p>

3.BoostQ元~程库目前主要包含MPL、Type Traits和Static Assert{库?Static Assert和Type Traits用作MPL的基。Boost Type Traits库包含一pdtraitsc,用于萃取C++cd特征。另外还包含了一些{换traitsQ例如移除一个类型的const修饰W等Q。Boost Static Assert库用于编译期断言Q如果评估的表达式编译时计算l果为trueQ则代码可以通过~译Q否则编译报错?/p>

技术细?/strong>

模板元编E用静态C++语言成分Q编E风格类g函数式编E,在模板元~程中,主要操作整型Q包括布类型、字W类型、整数类型)帔R和类型,不可以用变量、赋D句和q代l构{。被操纵的实体也UCؓ元数据(MetadataQ,所有元数据均可作ؓ模板参数?/p>

׃在模板元~程中不可以使用变量Q我们只能用typedef名字和整型常量。它们分别采用一个类型和整数D行初始化Q之后不能再赋予新的cd或数倹{如果需要新的类型或数|必须引入新的typedef名字或常量?/p>

其它范例

 

 

ReferencesQ?/strong>

http://club.topsage.com/thread-421469-1-1.html

http://wenku.baidu.com/view/c769720df78a6529647d539d.html

Blitz++Q?nbsp;http://www.oonumerics.org/blitz .
Loki Q?a target="_blank" style="box-sizing: border-box; color: #3468a4;">http://sourceforge.net/projects/loki-lib

BoostQ?a style="box-sizing: border-box; color: #3468a4;">http://www.boost.org/



鑫龙 2012-12-01 20:01 发表评论
]]>Traits 技?--- 模板元编E?(? http://m.shnenglu.com/mysileng/archive/2012/12/01/195868.html鑫龙鑫龙Sat, 01 Dec 2012 11:57:00 GMThttp://m.shnenglu.com/mysileng/archive/2012/12/01/195868.htmlhttp://m.shnenglu.com/mysileng/comments/195868.htmlhttp://m.shnenglu.com/mysileng/archive/2012/12/01/195868.html#Feedback0http://m.shnenglu.com/mysileng/comments/commentRss/195868.htmlhttp://m.shnenglu.com/mysileng/services/trackbacks/195868.html      什么是traitsQؓ什么h们把它认为是C++ Generic Programming的重要技术?

      z地_traits如此重要Q是因ؓ此项技术允许系l在~译时根据类型作一些决断,好像在q行时根据值来做出x一栗更q一步,此技术遵?#8220;另增一个间接层”的谚语,解决了不Y件工E问题,traits使您能根据其产生的背?context) 来做出抉择。这hl的代码变得清晰易读,Ҏl护。如果你正确q用了traits技术,你就能在不付ZQ何性能和安全代L同时得到q些好处Q或者能够契合其他解x案上的需求?br />
      先D个浅显易懂的例子来说明traits的用法:
//首先假如有以下一个泛型的q代器类Q其中类型参?nbsp;T P代器所指向的类型:
template <typename T>
class myIterator
{

};
       那么当用myIteratorӞ怎样才能知道它所指向元素的类型呢Q一U解x案是个类加入一个内嵌类型:
template <typename T>
class myIterator
{
typedef T value_type;

};
      当用myIteratorӞ可以通过myIterator::value_type来获得相应的myIterator所指向的类型。下面D例用:
template <typename T>
typename myIterator<T>::value_type func(myIterator<T> i)
{

}
      q里定义了一个函数funcQ返回值类型ؓ参数i所指的cdQ也是模板参数TQ那么ؓ什么不直接使用模板参数TQ而要l着圈去使用那个value_type呢?所以我们返回来Q当修改func函数Ӟ它能够适应所有类型的q代器,不是更好吗?如下所C:
template <typename I> //q里的I可以是Q意类型的q代?/span>
typename I::value_type func(I i)
{

}
      现在QQ意定义了value_type内嵌cd的P代器都可以做为func的参CQƈ且func的返回值的cd与相应q代器所指的元素的类型一致。至此一切问题似乎都已解冻Iq且gq没有用Q何特D的技术?br />      然而当考虑C下情冉|Q新的问题便昄出来了:原生指针也完全可以做P代器来用,然而显然没有办法ؓ原生指针d一个value_type的内嵌类型,如此一来func()函数׃能适用原生指针了,q不能不说是一大缺憾。那么有什么办法可以解册个问题呢Q此时不想C用Traits萃取cd信息。可以不直接使用myIterator的value_typeQ而是通过TraitscL把这个信息提取出来:(不同的类型,可以有不同的提取方式)
template <typename T>
class Traits
{
typedef typename T::value_type value_type;
};
      q样以后可以通过Traits<myIterator>::value_type来提取出myIterator中的value_typeQ于是func函数改写成:
template <typename I> //q里的I可以是Q意类型的q代?/span>
typename Traits<I>::value_type Foo(I i)
{

}
      然而,即ɘq样Q那个原生指针的问题仍然没有解决Q因为Traitc还是没办法获得原生指针的相关信息。于是不妨将Traits偏特?partial specialization)Q?通过特化、重载特化等手段产出不同的提取方?
template <typename T>
class Traits<T*> //注意 q里针对原生指针q行了偏特化
{
typedef typename T value_type;
};
     通过上面q个Traits的偏特化版本Q一个T*cd的指针所指向的元素的cd为T。如此一来,我们func函数完全可以适用于原生指针了。比如:
int * p;
.
int i = func(p);
     Traits会自动推导出p所指元素的cd为intQ从而func正确q回?/div>
-----------------------------------------------------------------------------------------------------------------------------------------------------------
     现在再看一个更加一般的例子——smart pointers?指针)
     假设你正在设计一个SmartPtr模板c,对于一个smart pointer 来说Q它的最大的用处是可以自动管理内存问题,同时在其他方面又像一个常规指针。但是有些C++的Smart pointer实现技术却非常令h难以理解。这一D酷的事实带来了一个重要实늻验:你最好尽一切可能一x逸,写出一个出色的、具有工业强度的 smart pointer来满你所有的需求。此外,你通常不能修改一个类来适应你的smart pointerQ所以你的SmartPtr一定要_灉|?/div>
     有不类层次使用引用计数(reference counting)以及相应的函数管理对象的生存期。然而,q没有reference counting的标准实现方法,每一个C++库的供应商在实现的语法和/或语义上都有所不同。例如,在你的应用程序中有这样两个interfacesQ?br />W一U智能指?-大部分的cdCRefCounted接口:
class RefCounted
{
public:
virtual void IncRef() = 0;
virtual bool DecRef() = 0;
// if you DecRef() to zero references, the object is destroyed
// automatically and DecRef() returns true
virtual ~RefCounted() {}
};
W二U智能指?-W三Ҏ供的WidgetcM用不同的接口Q?/div>
class Widget
{
public:
void AddReference();
int RemoveReference(); 
// returns the remaining number of references; it's the client's
// responsibility to destroy the object

};
      不过你ƈ不想l护两个smart pointerc,你想让两U类׃n一个SmartPtr。一个基于traits的解x案把两种不同的接口用语法和语义上l一的接口包装v来,建立针对普通类的通用模板Q而针对Widget建立一个特D化版本Q如下:
template <class T>
class RefCountingTraits
{
static void Refer(T* p)
{
p->IncRef(); // assume RefCounted interface
}
static void Unrefer(T* p)
{
p->DecRef(); //assume RefCounted interface
}
};
template<>
class RefCountingTraits<Widget>
{
static void Refer(Widget* p)
{
p->AddReference(); //use Widget interface
}
static void Unrefer(Widget* p)
{
//use Widget interface
If (p->RemoveReference() == 0)
delete p;
}
};
      在SmartPtr里,我们像这样用RefCountingTraits:
template <class T>
class SmartPtr
{
private:
typedef RefCountingTraits<T> RCTraits;
T* pointee_;
public:

~SmartPtr()
{
RCTraits::Unrefer(pointee_);
}
};
      当然在上面的例子里,你可能会争论说你可以直接Ҏ化WidgetcȝSmartPtr的构造与析构函数。你可以使用把模板特D化技术用?SmartPtr本nQ而不是用在traits上头Q这栯可以消除额外的类。尽对q个问题来说q种x没错Q但q是׃些你需要注意的~陷Q?/div>
  1. q么q缺乏可扩展性。如果给SmartPtr再增加一个模板参敎ͼ你不能特D化q样一个SmartPtr<T. U>Q其中模板参数T是WidgetQ而U可以为其他Q何类型?/li>
  2. 最l代码不那么清晰。Trait有一个名字,而且把相关的东西很好的组lv来,因此使用traits的代码更加容易理解。相比之下,用直接特D化SmartPtr成员函数的代码,看上L招黑客的喜欢?/li>
      用承机制的解决ҎQ就本w完无瑕,也至存在上q的~陷。解册样一个变体问题,使用l承实在是太W重了。此外,通常用以取代l承Ҏ的另一U经典机?#8212;—containmentQ用在这里也昑־画蛇添Q繁琐不堪。相反,traitsҎq净利落Q简明有效,物合其用Q恰到好处?/div>
      Traits的一个重要的应用?#8220;interface glue”Q接口胶合剂Q,通用的、可适应性极强的适配子。如果不同的cd于一个给定的概念有着不同的实玎ͼtraits可以把这些实现再l织l一成一个公q接口。对于一个给定类型提供多UTRAITSQ现在,我们假设所有的人都很喜Ƣ你的SmartPtr模板c,直到有一天,在你的多U程应用E序里开始现了神U的bug。你发现|魁R是WidgetQ它的引用计数函数ƈ不是U程安全的。现在你不得不亲自实现Widget:: AddReference和Widget::RemoveReferenceQ最合理的位|应该是在RefCountingTraits中,打上个补丁吧Q?/div>
// Example 7: Patching Widget's traits for thread safety
template <>
class RefCountingTraits<Widget>
{
static void Refer(Widget* p)
{
Sentry s(lock_); // serialize access
p->AddReference();
}
static void Unrefer(Widget* p)
{
Sentry s(lock_); // serialize access
if (p->RemoveReference() == 0)
delete p;
}
private:
static Lock lock_;
};
       不幸的是Q虽然你重新~译、测试之后正运行,但是E序慢得像蜗牛。仔l分析之后发玎ͼ你刚才的所作所为往E序里塞了一个糟p的瓉。实际上只有数几个Widget是需要能够被好几个线E访问的Q余下的l大多数Widget都是只被一个线E访问的。你要做的是告诉~译器按你的需求分别用多U程traits和单U程traitsq两个不同版本。你的代码主要用单U程traits?/div>
       如何告诉~译器用哪个traitsQ一U方法是把traits作ؓ另一个模板参ClSmartPtr。缺省情况下传递老式的traits模板Q而用特定的类型实例化特定的模ѝ?/div>
template <class T, class RCTraits = RefCountingTraits<T> >
class SmartPtr
{

};
      你对单线E版的RefCountingTraits<Widget>不做改动Q而把多线E版攑֜一个单独的cMQ?/div>
class MtRefCountingTraits
{
static void Refer(Widget* p)
{
Sentry s(lock_); // serialize access
p->AddReference();
}
static void Unrefer(Widget* p)
{
Sentry s(lock_); // serialize access
if (p->RemoveReference() == 0)
delete p;
}
private:
static Lock lock_;
};
       现在你可SmartPtr<Widget>用于单线E目的,SmartPtr<Widget,MtRefCountingTraits>用于多线E目的?/div>
       最后,以SGI STL中的__type_traitsl束本篇讨论Q在SGI 实现版的STL中,Z获取高效率,提供了__type_traitsQ用来提取类的信息,比如cL否拥有trival的构造、析构、拷贝、赋值操作,然后跟据具体的信息,可提供最有效率的操作。以下摘录cygwin的gcc3.3源码Q有改动Q在<type_traits.h>中?/div>
struct __true_type {};
struct __false_type {};
template <class _Tp>
struct __type_traits
{
typedef __true_type    this_dummy_member_must_be_first;
typedef __false_type    has_trivial_default_constructor;
typedef __false_type    has_trivial_copy_constructor;
typedef __false_type    has_trivial_assignment_operator;
typedef __false_type    has_trivial_destructor;
typedef __false_type    is_POD_type;
};
        对于普通类来讲Qؓ了安全v见,都认为它们拥有non-trival的构造、析构、拷贝、赋值函敎ͼPOD是指plain old data。接下来对C++的原生类型(boolQintQ?double之类Q定义了昑ּ的特化实玎ͼ以doubleZQ?/div>
template<> 
struct __type_traits<long double> {
typedef __true_type    has_trivial_default_constructor;
typedef __true_type    has_trivial_copy_constructor;
typedef __true_type    has_trivial_assignment_operator;
typedef __true_type    has_trivial_destructor;
typedef __true_type    is_POD_type;
};
q有Q对所有的原生指针来讲Q它们的构造、析构等操作也是trival的,因此有:
template <class _Tp>
struct __type_traits<_Tp*> {
typedef __true_type    has_trivial_default_constructor;
typedef __true_type    has_trivial_copy_constructor;
typedef __true_type    has_trivial_assignment_operator;
typedef __true_type    has_trivial_destructor;
typedef __true_type    is_POD_type;
};
      ?lt;stl_algobase.h>中copy的部分代码来说明对__type_traits的应用?/div>
template<typename _Tp>
inline _Tp* __copy_trivial(const _Tp* __first, const _Tp* __last, _Tp* __result)
{
memmove(__result, __first, sizeof(_Tp) * (__last - __first));
return __result + (__last - __first);
}
template<typename _Tp>
inline _Tp* __copy_aux (_Tp* __first, _Tp* __last, _Tp* __result, __true_type)
return __copy_trivial(__first, __last, __result); } 
template<typename _Tp>
inline _Tp* __copy_aux (_Tp* __first, _Tp* __last, _Tp* __result, __false_type)
{ 另外处理Q}
template<typename _InputIter, typename _OutputIter> inline 
_OutputIter copy (_InputIter __first, _InputIter __last, _OutputIter __result)
{
typedef typename iterator_traits<_InputIter>::value_type _ValueType;
typedef typename __type_traits<_ValueType>::has_trivial_assignment_operator _Trivial;
return __copy_aux(__first, __last, __result, _Trivial());
}
      Copy 函数利用__type_traits判断当前的value_type是否有trival的赋值操作,如果是,则生类__true_type的实例,~译 旉择__copy_trivial函数q行memmoveQ效率最高。如果是non-trival的赋值操作,则另作处理,效率自然低些。__true_type和__false_type之所以是c,因为C++的函数重载是Ҏcd信息来的Q不能依据参数值来判别。用SGI STLӞ可以qcd义__type_traits昑ּ特化版本Q以求达到高效率?/div>
本文来自CSDN博客Q{载请标明出处Qhttp://blog.csdn.net/budTang/archive/2008/05/06/2397013.aspx


鑫龙 2012-12-01 19:57 发表评论
]]>C++cd转换ȝ(?http://m.shnenglu.com/mysileng/archive/2012/11/25/195650.html鑫龙鑫龙Sun, 25 Nov 2012 09:33:00 GMThttp://m.shnenglu.com/mysileng/archive/2012/11/25/195650.htmlhttp://m.shnenglu.com/mysileng/comments/195650.htmlhttp://m.shnenglu.com/mysileng/archive/2012/11/25/195650.html#Feedback0http://m.shnenglu.com/mysileng/comments/commentRss/195650.htmlhttp://m.shnenglu.com/mysileng/services/trackbacks/195650.htmlC风格的强制类型{?Type Cast)很简单,不管什么类型的转换l统是:
TYPE b = (TYPE)a?br />C++风格的类型{换提供了4U类型{换操作符来应对不同场合的应用?/p>

const_castQ字面上理解是去const属性?br />static_castQ命名上理解是静态类型{换。如int转换成char?br />dynamic_castQ命名上理解是动态类型{换。如子类和父cM间的多态类型{换?br />reinterpret_castQ仅仅重新解释类型,但没有进行二q制的{换?br />4U类型{换的格式Q如QTYPE B = static_cast(TYPE)(a)?/p>

const_cast
Lcd的const或volatile属性?/p>

1 struct SA {
2 int i;
3 };
4 const SA ra;
5 //ra.i = 10; //直接修改constcdQ编译错?/span>
6 SA &rb = const_cast<SA&>(ra);
7 rb.i = 10;

static_cast

cM于C风格的强制{换。无条g转换Q静态类型{换。用于:
1. 基类和子cM间{换:其中子类指针转换成父cL针是安全的;但父cL针{换成子类指针是不安全的?基类和子cM间的动态类型{换徏议用dynamic_cast)
2. 基本数据cd转换。enum, struct, int, char, float{。static_cast不能q行无关cdQ如非基cd子类Q指针之间的转换?br />3. 把空指针转换成目标类型的I指针?br />4. 把Q何类型的表达式{换成voidcd?br />5. static_cast不能Lcd的const、volitale属?用const_cast)?/p>

1 int n = 6;
2 double d = static_cast<double>(n); // 基本cd转换
3 int *pn = &n;
4 double *d = static_cast<double *>(&n) //无关cd指针转换Q编译错?/span>
5 void *p = static_cast<void *>(pn); //Lcd转换成voidcd

dynamic_cast
有条件{换,动态类型{换,q行时类型安全检?转换p|q回NULL)Q?br />1. 安全的基cd子类之间转换?br />2. 必须要有虚函数?br />3. 相同基类不同子类之间的交叉{换。但l果是NULL?/p>

 1 class BaseClass {
2 public:
3 int m_iNum;
4 virtual void foo(){}; //基类必须有虚函数。保持多台特性才能用dynamic_cast
5 };
6
7 class DerivedClass: public BaseClass {
8 public:
9 char *m_szName[100];
10 void bar(){};
11 };
12
13 BaseClass* pb = new DerivedClass();
14 DerivedClass *pd1 = static_cast<DerivedClass *>(pb); //子类->父类Q静态类型{换,正确但不推荐
15 DerivedClass *pd2 = dynamic_cast<DerivedClass *>(pb); //子类->父类Q动态类型{换,正确
16
17 BaseClass* pb2 = new BaseClass();
18 DerivedClass *pd21 = static_cast<DerivedClass *>(pb2); //父类->子类Q静态类型{换,危险Q访问子cm_szName成员界
19 DerivedClass *pd22 = dynamic_cast<DerivedClass *>(pb2); //父类->子类Q动态类型{换,安全的。结果是NULL

reinterpret_cast
仅仅重新解释cdQ但没有q行二进制的转换Q?br />1. 转换的类型必L一个指针、引用、算术类型、函数指针或者成员指针?br />2. 在比特位U别上进行{换。它可以把一个指针{换成一个整敎ͼ也可以把一个整数{换成一个指针(先把一个指针{换成一个整敎ͼ在把该整数{换成原类型的指针Q还可以得到原先的指针|。但不能非32bit的实例{成指针?br />3. 最普通的用途就是在函数指针cd之间q行转换?br />4. 很难保证UL性?/p>

1 int doSomething(){return 0;};
2 typedef void(*FuncPtr)(); //FuncPtr is 一个指向函数的指针Q该函数没有参数Q返回值类型ؓ void
3 FuncPtr funcPtrArray[10]; //10个FuncPtrs指针的数l?让我们假设你希望Q因为某些莫名其妙的原因Q把一个指向下面函数的指针存入funcPtrArray数组Q?/span>
4
5 funcPtrArray[0] = &doSomething;// ~译错误Q类型不匚wQreinterpret_cast可以让编译器以你的方法去看待它们QfuncPtrArray
6 funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); //不同函数指针cd之间q行转换

ȝ
去const属性用const_cast?br />基本cd转换用static_cast?br />多态类之间的类型{换用daynamic_cast?br />不同cd的指针类型{换用reinterpret_cast?/p>

鑫龙 2012-11-25 17:33 发表评论
]]>
c++ cout 格式化输??http://m.shnenglu.com/mysileng/archive/2012/11/19/195358.html鑫龙鑫龙Mon, 19 Nov 2012 06:42:00 GMThttp://m.shnenglu.com/mysileng/archive/2012/11/19/195358.htmlhttp://m.shnenglu.com/mysileng/comments/195358.htmlhttp://m.shnenglu.com/mysileng/archive/2012/11/19/195358.html#Feedback0http://m.shnenglu.com/mysileng/comments/commentRss/195358.htmlhttp://m.shnenglu.com/mysileng/services/trackbacks/195358.html

cout 格式化输?/a>


?cout ?flag 保存到变?/strong>, 以便修改后的恢复


?bool g literals 输出

cout <<"numeric : " <<true <<" or " <<false <<endl ; // 1 or 0 cout <<"literals : " <<boolalpha <<true <<" or " <<false <<endl ; // true or false cout <<"literals : " <<boolalpha <<0 <<endl ; // 0 原因: 0 在cout中不{h?false

一旦我们?boolalpha 改?cout ?bool 值的输出格式. 此后?cout 都会?bool 输出?literals.


?bool g numeric 输出

cout <<"numeric : " <<noboolalpha <<true <<" or " <<false <<endl ;// 1 or 0

从此以后, cout ?bool 值的输出恢?numeric 格式


指定 Integral Values ?Base

const int ival = 17 ; // 'ival' is constant, so value never change cout <<"oct : " <<oct <<ival <<endl ; // 21 : 8 q制 cout <<"dec : " <<dec <<ival <<endl ; // 17 : 10 q制 cout <<"hex : " <<hex <<ival <<endl ; // 11 : 16 q制 cout <<"hex : " <<hex <<17.01 <<endl ; // 17.01 : 不受影响
?boolalpha 一? oct, dec, hex 也是 persistent. 一旦改? 媄响后l的输出格式.


昄表明 Integer Values ?Base

cout <<showbase ; // Show base when printing integral values cout <<"oct : " <<oct <<ival <<endl ; // 21 : 8 q制 cout <<"dec : " <<dec <<ival <<endl ; // 017 : 10 q制 cout <<"hex : " <<hex <<ival <<endl ; // 0x11 : 16 q制 cout <<"hex : " <<hex <<17.01 <<endl ; // 17.01 : 不受影响 cout <<noshowbase ; // Reset state of the stream

若想改变16q制字母的大?/strong>, 可以l合 uppercase/nouppercase

cout <<showbase <<uppercase ; cout <<"hex : " <<hex <<15 <<endl ; // 0XF 大写形式 cout <<nouppercase ; cout <<"hex : " <<hex <<15 <<endl ; // 0xf 写形式

showbase ?noshowbase 的作用周期也?persistent


对于 float/double ? 有三U格式化控制

一: 输出_ֺ precision : by default is 6 pricision
   控制了至多一׃输出多少个数?  
   当要输出的数字多余指定的值时, 发?四舍五入(rounded);  
   当要输出的数字少于指定的值时, 则实际输出的数字个数少于指定?
// cout.pricision(4) ; // {h?cout <<setprecision(4) ; cout <<setprecision(4) <<12.345678 <<endl ; // 12.35 rounded! cout <<setprecision(10) <<12.345678 <<endl ; // 12.345678 其实内部发生?rounded, 而结果正好进? 与原值相?/span> cout <<cout.precision() <<endl ; // 输出当前_ֺ
 ? 表现形式 notation : 'very large and very small values are printed using scientific notation. other values use fixed decimal.'
   notation 控制了输出的形式 : U学计数?scientific) ?定点数(fixed)
float f = 101 / 6.0 ; cout <<fixed <<f <<endl ; // 16.83334 : 数点后??/span> cout <<scientific <<f <<endl ; // 1.683333e+001 : 数点后??/span>
  恢复到初始状?br />
cout.unsetf(ostream::floatfield) ; // Retrieve to default handling for notation cout <<f <<endl ; // 16.8333 : 所有数字共6?/span>
 ? 输出十进制Q?/strong> 'By default, when the fractional part of a floating-point value is 0, the decimal point is not displayed. The showpoint manipulator forces the decimal point ot be printed.'
cout <<10.0 <<endl ; // 10 cout <<showpoint <<10.0 <<endl ; // 10.0000 cout <<noshowpoint <<endl ; // Revert to default handling of decimal  


输出填充 
Padding the Output

   setw to specify the minimum space for the next numeric or string value.
cout <<setw(10) <<12.3 <<endl ; // ______12.3 cout <<setw(10) <<12 <<3 <<endl ; // ________123 cout <<setw(3) <<12.345 <<endl ; // If the total output is more than 3, it can be extended
 
   left to left-justify the output.
cout <<left ; // left-justify cout <<setw(5) <<12 <<setw(5) <<34 <<endl ; // 12___34___
 
   right to right-justify the output. Output is right-justified bu default.
cout <<right ; // By default cout <<setw(5) <<12 <<setw(5) <<34 <<endl ; // 12___34___
 
   internal controls placement of the sign on negative value. internal left-justifies the sign and right-justifies the value, padding any intervening space with blanks.(if setfill not set) 
cout <<internal ; // By default cout <<setw(5) <<-12 <<endl ; // 12___34___
 
   setfill lets us specify an alternative character to use when padding the output. By default, the value is a space.
cout <<setfill('*') ; // By default cout <<setw(5) <<12 <<endl ; // 12___34___
 

Header Files

   Manipulators Defined in <iomanip>
setfill(char ch) Fill whitespace with 'ch' setprecision(int n) Set floating-point precision to 'n' setw(int w) Read or write value to 'w' characters setbase(int b) Output integers in base 'b'(only 'b' is 8/10/16 could the function work)


鑫龙 2012-11-19 14:42 发表评论
]]>
c++内存泄露机制(?http://m.shnenglu.com/mysileng/archive/2012/11/06/194769.html鑫龙鑫龙Tue, 06 Nov 2012 13:04:00 GMThttp://m.shnenglu.com/mysileng/archive/2012/11/06/194769.htmlhttp://m.shnenglu.com/mysileng/comments/194769.htmlhttp://m.shnenglu.com/mysileng/archive/2012/11/06/194769.html#Feedback0http://m.shnenglu.com/mysileng/comments/commentRss/194769.htmlhttp://m.shnenglu.com/mysileng/services/trackbacks/194769.html对于一个c/c++E序员来_内存泄漏是一个常见的也是令h头疼的问题。已l有许多技术被研究出来以应对这个问题,比如 Smart PointerQGarbage Collection{。Smart Pointer技术比较成熟,STL中已l包含支持Smart Pointer的classQ但是它的用似乎ƈ不广泛,而且它也不能解决所有的问题QGarbage Collection技术在Java中已l比较成熟,但是在c/c++领域的发展ƈ不顺畅,虽然很早有人思考在C++中也加入GC的支持。现实世界就是这LQ作Z个c/c++E序员,内存泄漏是你心中永远的痛。不q好在现在有许多工具能够帮助我们验证内存泄漏的存在,扑և发生问题的代码?/span>

  内存泄漏的定?/strong> 

   一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指E序从堆中分配的Q大Q意的Q内存块的大可以在E序q行期决定)Q用完后必LC释攄?存。应用程序一般用mallocQreallocQnew{函C堆中分配C块内存,使用完后Q程序必负责相应的调用free或delete释放?内存块,否则Q这块内存就不能被再ơ用,我们pq块内存泄漏了。以下这D小E序演示了堆内存发生泄漏的情形:

void MyFunction(int nSize)
{
 char* p= new char[nSize];
 if( !GetStringFrom( p, nSize ) ){
  MessageBox(“Error”);
  return;
 }
 …//using the string pointed by p;
 delete p;
}

  例一

  当函数GetStringFrom()q回零的时候,指针p指向的内存就不会被释放。这是一U常见的发生内存泄漏的情形。程序在入口处分配内存,在出口处释放内存Q但是c函数可以在Q何地斚w出,所以一旦有某个出口处没有释攑ֺ该释攄内存Q就会发生内存泄漏?/span>

   q义的说Q内存泄漏不仅仅包含堆内存的泄漏Q还包含pȝ资源的泄?resource leak)Q比如核心态HANDLEQGDI ObjectQSOCKETQ?Interface{,从根本上说这些由操作pȝ分配的对象也消耗内存,如果q些对象发生泄漏最l也会导致内存的泄漏。而且Q某些对象消耗的是核心态内 存,q些对象严重泄漏时会D整个操作pȝ不稳定。所以相比之下,pȝ资源的泄漏比堆内存的泄漏更ؓ严重?/span>

  GDI Object的泄漏是一U常见的资源泄漏Q?/span>

void CMyView::OnPaint( CDC* pDC )
{
 CBitmap bmp;
 CBitmap* pOldBmp;
 bmp.LoadBitmap(IDB_MYBMP);
 pOldBmp = pDC->SelectObject( &bmp );
 …
 if( Something() ){
  return;
 }
 pDC->SelectObject( pOldBmp );
 return;
}

  例二

   当函数Something()q回非零的时候,E序在退出前没有把pOldBmp选回pDC中,q会DpOldBmp指向的HBITMAP对象发生?漏。这个程序如果长旉的运行,可能会导致整个系l花屏。这U问题在Win9x下比较容易暴露出来,因ؓWin9x的GDI堆比Win2k或NT的要很 多?/span>

  内存泄漏的发生方式:

  以发生的方式来分c,内存泄漏可以分ؓ4c:

  1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行刎ͼ每次被执行的时候都会导致一块内存泄漏。比如例二,如果Something()函数一直返回TrueQ那么pOldBmp指向的HBITMAP对象L发生泄漏?/span>

   2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作q程下才会发生。比如例二,如果Something()函数只有在特定环境下才返?TrueQ那么pOldBmp指向的HBITMAP对象q不L发生泄漏。常发性和偶发性是相对的。对于特定的环境Q偶发性的也许变成了常发性的。所?试环境和测试方法对内存泄漏至关重要?/span>

  3. 一ơ性内存泄漏。发生内存泄漏的代码只会被执行一ơ,或者由于算法上的缺PDM有一块仅且一块内存发生泄漏。比如,在类的构造函C分配内存Q在?构函C却没有释放该内存Q但是因个类是一个SingletonQ所以内存泄漏只会发生一ơ。另一个例子:

char* g_lpszFileName = NULL;

void SetFileName( const char* lpcszFileName )
{
 if( g_lpszFileName ){
  free( g_lpszFileName );
 }
 g_lpszFileName = strdup( lpcszFileName );
}

  例三

  如果E序在结束的时候没有释放g_lpszFileName指向的字W串Q那么,即多次调用SetFileName()QM有一块内存,而且仅有一块内存发生泄漏?/span>

   4. 隐式内存泄漏。程序在q行q程中不停的分配内存Q但是直到结束的时候才释放内存。严格的说这里ƈ没有发生内存泄漏Q因为最l程序释放了所有申L内存。但 是对于一个服务器E序Q需要运行几天,几周甚至几个月,不及旉攑ֆ存也可能D最l耗尽pȝ的所有内存。所以,我们U这cd存泄漏ؓ隐式内存泄漏。D一 个例子: 

class Connection
{
 public:
  Connection( SOCKET s);
  ~Connection();
  …
 private:
  SOCKET _socket;
  …
};

class ConnectionManager
{
 public:
  ConnectionManager(){}
  ~ConnectionManager(){
   list::iterator it;
   for( it = _connlist.begin(); it != _connlist.end(); ++it ){
    delete Q?itQ?
   }
   _connlist.clear();
  }
  void OnClientConnected( SOCKET s ){
   Connection* p = new Connection(s);
   _connlist.push_back(p);
  }
  void OnClientDisconnected( Connection* pconn ){
   _connlist.remove( pconn );
   delete pconn;
  }
 private:
  list _connlist;
};

  例四

   假设在Client从Server端断开后,Serverq没有呼叫OnClientDisconnected()函数Q那么代表那ơ连接的 Connection对象׃会被及时的删除(在ServerE序退出的时候,所有Connection对象会在ConnectionManager的析 构函数里被删除)。当不断的有q接建立、断开旉式内存泄漏就发生了?/span>

  从用户用程序的角度来看Q内存泄漏本w不会生什么危宻I?Z般的用户Q根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆U,q会最l消耗尽pȝ所有的内存。从q个角度来说Q一ơ性内存泄漏ƈ没有什么危 宻I因ؓ它不会堆U,而隐式内存泄漏危x则非常大,因ؓ较之于常发性和偶发性内存泄漏它更难被检到?nbsp;
内存泄?/strong>

  内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住q两个函敎ͼ我们p跟踪每一 块内存的生命周期Q比如,每当成功的分配一块内存后Q就把它的指针加入一个全局的list中;每当释放一块内存,再把它的指针从list中删除。这P?E序l束的时候,list中剩余的指针是指向那些没有被释攄内存。这里只是简单的描述了检内存泄漏的基本原理Q详l的法可以参见Steve Maguire?lt;<Writing Solid Code>>?/span>

  如果要检堆内存的泄漏,那么需要截获住 malloc/realloc/free和new/delete可以了Q其实new/delete最l也是用malloc/free的,所以只要截获前 面一l即可)。对于其他的泄漏Q可以采用类似的ҎQ截获住相应的分配和释放函数。比如,要检BSTR的泄漏,需要截?SysAllocString/SysFreeStringQ要HMENU的泄漏,需要截获CreateMenu/ DestroyMenu。(有的资源的分配函数有多个Q释攑և数只有一个,比如QSysAllocStringLen也可以用来分配BSTRQ这时就需?截获多个分配函数Q?/span>

  在Windowsq_下,内存泄漏的工具常用的一般有三种QMS C-Runtime Library内徏的检功能;外挂式的工P诸如QPurifyQBoundsChecker{;利用Windows NT自带的Performance Monitor。这三种工具各有优缺点,MS C-Runtime Library虽然功能上较之外挂式的工兯弱,但是它是免费的;Performance Monitor虽然无法标示出发生问题的代码Q但是它能检出隐式的内存泄漏的存在Q这是其他两cdh能ؓ力的地方?/span>

  以下我们详细讨论q三U检工P

  VC下内存泄漏的方?/span>

  用MFC开发的应用E序Q在DEBUG版模式下~译后,都会自动加入内存泄漏的检代码。在E序l束后,如果发生了内存泄漏,在DebugH口中会昄出所有发生泄漏的内存块的信息Q以下两行显CZ一块被泄漏的内存块的信息:

E:/TestMemLeak/TestDlg.cpp(70) : {59} normal block at 0x00881710, 200 bytes long.

Data: <abcdefghijklmnop> 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70

   W一行显C内存块由TestDlg.cpp文gQ第70行代码分配,地址?x00881710Q大ؓ200字节Q{59}是指调用内存分配函数?Request OrderQ关于它的详l信息可以参见MSDN中_CrtSetBreakAlloc()的帮助。第二行昄该内存块?6个字节的内容Q尖括号内是?ASCII方式昄Q接着的是?6q制方式昄?/span>

  一般大安误以些内存泄漏的功能是由MFC提供的,其实不然。MFC只是 装和利用了MS C-Runtime Library的Debug Function。非MFCE序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检功能。MS C-Runtime Library在实现malloc/freeQstrdup{函数时已经内徏了内存泄漏的功能?/span>

  注意观察一下由MFC Application Wizard生成的项目,在每一个cpp文g的头部都有这样一D宏定义Q?/span>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

  有了q样的定义,在编译DEBUG版时Q出现在q个cpp文g中的所有new都被替换成DEBUG_NEW了。那么DEBUG_NEW是什么呢QDEBUG_NEW也是一个宏Q以下摘自afx.hQ?632?/span>

#define DEBUG_NEW new(THIS_FILE, __LINE__)

  所以如果有q样一行代码:

char* p = new char[200];

  l过宏替换就变成了:

char* p = new( THIS_FILE, __LINE__)char[200];

  ҎC++的标准,对于以上的new的用方法,~译器会Lq样定义的operator newQ?/span>

void* operator new(size_t, LPCSTR, int)

  我们在afxmem.cpp 63行找C一个这Loperator new 的实?/span>

void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine)
{
 return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine);
}

void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine)
{
 …
 pResult = _malloc_dbg(nSize, nType, lpszFileName, nLine);
 if (pResult != NULL)
  return pResult;
 …
}

   W二个operator new函数比较长,Z单期_我只摘录了部分。很昄最后的内存分配q是通过_malloc_dbg函数实现的,q个函数属于MS C-Runtime Library 的Debug Function。这个函C但要求传入内存的大小Q另外还有文件名和行号两个参数。文件名和行号就是用来记录此ơ分配是由哪一D代码造成的。如果这块内 存在E序l束之前没有被释放,那么q些信息׃输出到DebugH口里?/span>

  q里Z提一下THIS_FILEQ__FILE?__LINE__。__FILE__和__LINE__都是~译器定义的宏。当到__FILE__Ӟ~译器会把__FILE__替换成一个字W串Q这 个字W串是当前在编译的文g的\径名。当到__LINE__Ӟ~译器会把__LINE__替换成一个数字,q个数字是当前q行代码的行受在 DEBUG_NEW的定义中没有直接使用__FILE__Q而是用了THIS_FILEQ其目的是ؓ了减目标文件的大小。假讑֜某个cpp文g中有 100处用了newQ如果直接用__FILE__Q那~译器会产生100个常量字W串Q这100个字W串都是?/SPAN>cpp文g的\?名,昄十分冗余。如果用THIS_FILEQ编译器只会产生一个常量字W串Q那100处new的调用用的都是指向帔R字符串的指针?/span>

   再次观察一下由MFC Application Wizard生成的项目,我们会发现在cpp文g中只对new做了映射Q如果你在程序中直接使用malloc函数分配内存Q调用malloc的文件名和行 h不会被记录下来的。如果这块内存发生了泄漏QMS C-Runtime Library仍然能检到Q但是当输出q块内存块的信息Q不会包含分配它的的文g名和行号?/span>

  要在非MFCE序中打开内存泄漏的检功能非常容易,你只要在E序的入口处加入以下几行代码Q?/span>

int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );

tmpFlag |= _CRTDBG_LEAK_CHECK_DF;

_CrtSetDbgFlag( tmpFlag );

  q样Q在E序l束的时候,也就是winmainQmain或dllmain函数q回之后Q如果还有内存块没有释放Q它们的信息会被打印到DebugH口里?/span>

  如果你试着创徏了一个非MFC应用E序Q而且在程序的入口处加入了以上代码Qƈ且故意在E序中不释放某些内存块,你会在DebugH口里看C下的信息Q?/span>

{47} normal block at 0x00C91C90, 200 bytes long.

Data: < > 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

  内存泄漏的确到了,但是和上面MFCE序的例子相比,~少了文件名和行受对于一个比较大的程序,没有q些信息Q解决问题将变得十分困难?/span>

  Z能够知道泄漏的内存块是在哪里分配的,你需要实现类似MFC的映功能,把newQmaolloc{函数映到_malloc_dbg函数上。这里我不再赘述Q你可以参考MFC的源代码?/span>

   ׃Debug Function实现在MS C-RuntimeLibrary中,所以它只能到堆内存的泄漏Q而且只限于mallocQrealloc或strdup{分配的内存Q而那些系l资 源,比如HANDLEQGDI ObjectQ或是不通过C-Runtime Library分配的内存,比如VARIANTQBSTR的泄漏,它是无法到的,q是q种法的一个重大的局限性。另外,Z能记录内存块是在哪里 分配的,源代码必ȝ应的配合Q这在调试一些老的E序非常ȝQ毕竟修Ҏ代码不是一件省心的事,q是q种法的另一个局限性?/span>

  ?于开发一个大型的E序QMS C-Runtime Library提供的检功能是q远不够的。接下来我们q看外挂式的检工兗我用的比较多的是BoundsCheckerQ一则因为它的功能比较全 面,更重要的是它的稳定性。这cd具如果不E_Q反而会忙里Mؕ。到底是鼎鼎大名的NuMegaQ我用下来基本上没有什么大问题?/span>
 使用BoundsChecker内存泄漏:

  BoundsChecker采用一U被UCؓ Code Injection的技术,来截获对分配内存和释攑ֆ存的函数的调用。简单地_当你的程序开始运行时QBoundsChecker的DLL被自动蝲入进 E的地址I间Q这可以通过system-level的Hook实现Q,然后它会修改q程中对内存分配和释攄函数调用Q让q些调用首先转入它的代码Q然?再执行原来的代码。BoundsChecker在做q些动作的时Q无M改被调试E序的源代码或工E配|文Ӟq得用它非常的简ѝ直接?/span>

  q里我们以malloc函数ZQ截获其他的函数Ҏ与此cM?/span>

  需要被截获的函数可能在DLL中,也可能在E序的代码里。比如,如果静态连lC-Runtime LibraryQ那么malloc函数的代码会被连l到E序里。ؓ了截获住对这cd数的调用QBoundsChecker会动态修改这些函数的指o?/span>

  以下两段汇编代码Q一D|有BoundsChecker介入Q另一D则有BoundsChecker的介入:

126: _CRTIMP void * __cdecl malloc (
127: size_t nSize
128: )
129: {

00403C10 push ebp
00403C11 mov ebp,esp
130: return _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);
00403C13 push 0
00403C15 push 0
00403C17 push 1
00403C19 mov eax,[__newmode (0042376c)]
00403C1E push eax
00403C1F mov ecx,dword ptr [nSize]
00403C22 push ecx
00403C23 call _nh_malloc_dbg (00403c80)
00403C28 add esp,14h
131: }

  以下q一D代码有BoundsChecker介入Q?/span>

126: _CRTIMP void * __cdecl malloc (
127: size_t nSize
128: )
129: {

00403C10 jmp 01F41EC8
00403C15 push 0
00403C17 push 1
00403C19 mov eax,[__newmode (0042376c)]
00403C1E push eax
00403C1F mov ecx,dword ptr [nSize]
00403C22 push ecx
00403C23 call _nh_malloc_dbg (00403c80)
00403C28 add esp,14h
131: }

   当BoundsChecker介入后,函数malloc的前三条汇编指o被替换成一条jmp指oQ原来的三条指o被搬到地址01F41EC8处了。当E?序进入malloc后先jmp?1F41EC8Q执行原来的三条指oQ然后就是BoundsChecker的天下了。大致上它会先记录函数的q回地址 Q函数的q回地址在stack上,所以很Ҏ修改Q,然后把返回地址指向属于BoundsChecker的代码,接着跛_malloc函数原来的指令,?是?0403c15的地斏V当malloc函数l束的时候,׃q回地址被修改,它会q回到BoundsChecker的代码中Q此?BoundsChecker会记录由malloc分配的内存的指针Q然后再跌{到到原来的返回地址厅R?/span>

  如果内存分配/释放函数在DLL中,BoundsChecker则采用另一U方法来截获对这些函数的调用。BoundsChecker通过修改E序的DLL Import Table让table中的函数地址指向自己的地址Q以辑ֈ截获的目的?/span>

   截获住这些分配和释放函数QBoundsCheckerp记录被分配的内存或资源的生命周期。接下来的问题是如何与源代码相关Q也是说当 BoundsChecker到内存泄漏Q它如何报告q块内存块是哪段代码分配的。答案是调试信息QDebug InformationQ。当我们~译一个Debug版的E序Ӟ~译器会把源代码和二q制代码之间的对应关p记录下来,攑ֈ一个单独的文g?(.pdb)或者直接连l进目标E序Q通过直接d调试信息p得到分配某块内存的源代码在哪个文Ӟ哪一行上。用Code Injection和Debug InformationQBoundsChecker不但能记录呼叫分配函数的源代码的位置Q而且q能记录分配时的Call StackQ以及Call Stack上的函数的源代码位置。这在用像MFCq样的类库时非常有用Q以下我用一个例子来说明Q?/span>


void ShowXItemMenu()
{
 …
 CMenu menu;

 menu.CreatePopupMenu();
 //add menu items.
 menu.TrackPropupMenu();
 …
}

void ShowYItemMenu( )
{
 …
 CMenu menu;
 menu.CreatePopupMenu();
 //add menu items.
 menu.TrackPropupMenu();
 menu.Detach();//this will cause HMENU leak
 …
}

BOOL CMenu::CreatePopupMenu()
{
 …
 hMenu = CreatePopupMenu();
 …
}

   当调用ShowYItemMenu()Ӟ我们故意造成HMENU的泄漏。但是,对于BoundsChecker来说被泄漏的HMENU是在class CMenu::CreatePopupMenu()中分配的。假讄你的E序有许多地方用了CMenu的CreatePopupMenu()函数Q如 CMenu::CreatePopupMenu()造成的,你依然无法确认问题的根结到底在哪里,在ShowXItemMenu()中还是在 ShowYItemMenu()中,或者还有其它的地方也用了CreatePopupMenu()Q有了Call Stack的信息,问题容易了。BoundsChecker会如下报告泄漏的HMENU的信息:

Function
File
Line

CMenu::CreatePopupMenu
E:/8168/vc98/mfc/mfc/include/afxwin1.inl
1009

ShowYItemMenu
E:/testmemleak/mytest.cpp
100

  q里省略了其他的函数调用

  如此Q我们很Ҏ扑ֈ发生问题的函数是ShowYItemMenu()。当使用MFC之类的类库编E时Q大部分的API调用都被装在类库的class里,有了Call Stack信息Q我们就可以非常Ҏ的追t到真正发生泄漏的代码?/span>

  记录Call Stack信息会ɽE序的运行变得非常慢Q因此默认情况下BoundsChecker不会记录Call Stack信息。可以按照以下的步骤打开记录Call Stack信息的选项开养I

  1. 打开菜单QBoundsChecker|Setting… 

  2. 在Error Detection中Q在Error Detection Scheme的List中选择Custom

  3. 在Category的Combox中选择 Pointer and leak error check

  4. 钩上Report Call Stack复选框

  5. 点击Ok

  ZCode InjectionQBoundsCheckerq提供了API Parameter的校验功能,memory over run{功能。这些功能对于程序的开发都非常有益。由于这些内容不属于本文的主题,所以不在此详述了?/span>

  管BoundsChecker的功能如此强大,但是面对隐式内存泄漏仍然昑־苍白无力。所以接下来我们看看如何用Performance Monitor内存泄漏?/span>

  使用Performance Monitor内存泄?/span>
  NT的内核在设计q程中已l加入了pȝ监视功能Q比?/span>CPU的用率Q内存的使用情况QI/O操作的频J度{都作ؓ一个个CounterQ应用程序可以通过dq些Counter了解整个pȝ的或者某个进E的q行状况。Performance Monitor是q样一个应用程序?/span>

   Z内存泄漏,我们一般可以监视Process对象的Handle CountQVirutal Bytes 和Working Set三个Counter。Handle Count记录了进E当前打开的HANDLE的个敎ͼ监视q个Counter有助于我们发现程序是否有Handle泄漏QVirtual Bytes记录了该q程当前在虚地址I间上用的虚拟内存的大,NT的内存分配采用了两步走的ҎQ首先,在虚地址I间上保留一D늩_q时操作pȝq?没有分配物理内存Q只是保留了一D地址。然后,再提交这D늩_q时操作pȝ才会分配物理内存。所以,Virtual Bytes一般d于程序的Working Set。监视Virutal Bytes可以帮助我们发现一些系l底层的问题; Working Set记录了操作系lؓq程已提交的内存的总量Q这个值和E序甌的内存总量存在密切的关p,如果E序存在内存的泄漏这个g持箋增加Q但?Virtual Bytes却是跌式增加的?/span>
  监视q些Counter可以让我们了解进E用内存的情况Q如果发生了泄漏Q即使是?式内存泄漏,q些Counter的g会持l增加。但是,我们知道有问题却不知道哪里有问题Q所以一般用Performance Monitor来验证是否有内存泄漏Q而用BoundsChecker来找到和解决?/span>
  当Performance Monitor昄有内存泄漏,而BoundsChecker却无法检到Q这时有两种可能Q第一U,发生了偶发性内存泄漏。这时你要确保?Performance Monitor和用BoundsCheckerӞE序的运行环境和操作Ҏ是一致的。第二种Q发生了隐式的内存泄漏。这时你要重新审查程序的设计Q然 后仔l研IPerformance Monitor记录的Counter的值的变化图,分析其中的变化和E序q行逻辑的关p,扑ֈ一些可能的原因。这是一个痛苦的q程Q充满了假设、猜惟뀁验 证、失败,但这也是一个积累经验的l好Z?/span>
  ȝ
  内存泄漏是个大而复杂的问题Q即使是Java?Netq样?Gabarge Collection机制的环境,也存在着泄漏的可能,比如隐式内存泄漏。由于篇q和能力的限Ӟ本文只能对这个主题做一个粗的研究。其他的问题Q比?多模块下的泄漏检,如何在程序运行时对内存用情况进行分析等{,都是可以深入研究的题目。如果您有什么想法,或发C某些错误Q欢q和我交?/span> 

鑫龙 2012-11-06 21:04 发表评论
]]>
ŷ޾ƷþþavӰ| þþƷŷ| þۺϹɫ88þþƷۺ| ŷƷ99þ| ޾Ʒþþ| þøŮ߳MBA| Ʒ99þþƷ| ˳վþ99ȹ| ˾þۺϳ| 91޹˾þþƷ| þþƷav鶹ѿ| Ʒ99þþƷ| ݺɫɫݺݺۺϾþ| ձѾþþþþþվ| þɫۺϼ| Ʒݾþþþø99| þҹ³˿Ƭϼ| þþƷ| ƷƵþ| 99ƷþþƷһ| ŷþۺϾɫۺ| XxŷʸƷþþþþ| ɫۺϾþþþר| ԸߺþþþþþþAAAAA| ˾þþƷ鶹һ| 97þۺɫۺɫhd| 2021þþƷѹۿ| ˼˼þúúȾƷ| þһ99| þĻ| þˬˬˬav| þ99Ʒþþþþò| һĻþ| 97þþþ| ձþ| þþþavӰ | ԾþþӰԺ| ɫþþþۺ| ھƷ˾þþӰԺ| þþƷһ| ɫۺϾþĻ|