??xml version="1.0" encoding="utf-8" standalone="yes"?> 64位指的是cpu通用寄存器的数据宽度?4位的?/p> 1 static_cast 用法Qstatic_cast < type-id > ( expression ) 该运符把expression转换为type-idcdQ但没有q行时类型检查来保证转换的安全性。它主要有如下几U用法: ①用于类层次l构中基cd子类之间指针或引用的转换?/p>
q行上行转换Q把子类的指针或引用转换成基c表C)是安全的Q?/p>
q行下行转换Q把基类指针或引用{换成子类表示Q时Q由于没有动态类型检查,所以是不安全的?/p>
②用于基本数据类型之间的转换Q如把int转换成charQ把int转换成enum。这U{换的安全性也要开发h员来保证?/p>
③把I指针{换成目标cd的空指针?/p>
④把Mcd的表辑ּ转换成voidcd?/p>
注意Qstatic_cast不能转换掉expression的const、volitale、或者__unaligned属性?/p>
2 dynamic_cast 用法Qdynamic_cast < type-id > ( expression ) 该运符把expression转换成type-idcd的对象。Type-id必须是类的指针、类的引用或者void *Q?/p>
如果type-id是类指针cdQ那么expression也必L一个指针,如果type-id是一个引用,那么expression也必L一个引用?/p>
dynamic_cast主要用于cdơ间的上行{换和下行转换Q还可以用于cM间的交叉转换?/p>
在类层次间进行上行{换时Qdynamic_cast和static_cast的效果是一LQ?/p>
在进行下行{换时Qdynamic_casthcd查的功能Q比static_cast更安全?/p>
class B{ public: int m_iNum; virtual void foo(); }; class D:public B{ public: char *m_szName[100]; }; void func(B *pb){ D *pd1 = static_cast<D *>(pb); D *pd2 = dynamic_cast<D *>(pb); } 在上面的代码D中Q如果pb指向一个Dcd的对象,pd1和pd2是一LQƈ且对q两个指针执行Dcd的Q何操作都是安全的Q?/p>
但是Q如果pb指向的是一个Bcd的对象,那么pd1是一个指向该对象的指针,对它q行Dcd的操作将是不安全的(如访问m_szNameQ, 而pd2是一个空指针?/p>
另外要注意:B要有虚函敎ͼ否则会编译出错;static_cast则没有这个限制?/p>
q是׃q行时类型检查需要运行时cd信息Q而这个信息存储在cȝ虚函数表Q?/p>
关于虚函数表的概念,详细可见<Inside c++ object model>Q中Q只有定义了虚函数的cL有虚函数表, 没有定义虚函数的cL没有虚函数表的?/p>
另外Qdynamic_castq支持交叉{换(cross castQ。如下代码所C?/p>
class A{ public: int m_iNum; virtual void f(){} }; class B:public A{ }; class D:public A{ }; void foo(){ B *pb = new B; pb->m_iNum = 100; D *pd1 = static_cast<D *>(pb); //compile error D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL delete pb; } 在函数foo中,使用static_castq行转换是不被允许的Q将在编译时出错Q而?dynamic_cast的{换则是允许的Q结果是I指针?/p>
3 reinterpret_cast 用法Qreinterpret_cast<type-id> (expression) type-id必须是一个指针、引用、算术类型、函数指针或者成员指针?/p>
它可以把一个指针{换成一个整敎ͼ也可以把一个整数{换成一个指针(先把一个指针{换成一个整敎ͼ 在把该整数{换成原类型的指针Q还可以得到原先的指针|?/p>
该运符的用法比较多?/p>
(static_cast .? reinterpret_cast比较Q见下面 ) 4 const_cast 用法Qconst_cast<type_id> (expression) 该运符用来修改cd的const或volatile属性。除了const 或volatile修饰之外Q?type_id和expression的类型是一L?/p>
帔R指针被{化成非常量指针,q且仍然指向原来的对象; 帔R引用被{换成非常量引用,q且仍然指向原来的对象;帔R对象被{换成非常量对象?/p>
Voiatile和constc试。D如下一例: class B{ public: int m_iNum; } void foo(){ const B b1; b1.m_iNum = 100; //comile error B b2 = const_cast<B>(b1); b2. m_iNum = 200; //fine } 上面的代码编译时会报错,因ؓb1是一个常量对象,不能对它q行改变Q?/p>
使用const_cast把它转换成一个常量对象,可以对它的数据成员L改变。注意:b1和b2是两个不同的对象?/p>
== =========================================== == dynamic_cast .vs. static_cast == =========================================== class B { ... }; class D : public B { ... }; void f(B* pb) { D* pd1 = dynamic_cast<D*>(pb); D* pd2 = static_cast<D*>(pb); } If pb really points to an object of type D, then pd1 and pd2 will get the same value. They will also get the same value if pb == 0. If pb points to an object of type B and not to the complete D class, then dynamic_cast will know enough to return zero. However, static_cast relies on the programmer’s assertion that pb points to an object of type D and simply returns a pointer to that supposed D object. 即dynamic_cast可用于承体pM的向下{型,卛_基类指针转换为派生类指针Q比static_cast更严格更安全。dynamic_cast在执行效率上比static_cast要差一些,但static_cast在更宽上范围内可以完成映,q种不加限制的映伴随着不安全性。static_cast覆盖的变换类型除cdơ的静态导航以外,q包括无映射变换、窄化变?q种变换会导致对象切?丢失信息)、用VOID*的强制变换、隐式类型变换等... == =========================================== == static_cast .vs. reinterpret_cast == ================================================ reinterpret_cast是ؓ了映到一个完全不同类型的意思,q个关键词在我们需要把cd映射回原有类型时用到它。我们映到的类型仅仅是Z故弄玄虚和其他目的,q是所有映中最危险的?q句话是C++~程思想中的原话) static_cast ?reinterpret_cast 操作W修改了操作数类型。它们不是互逆的Q?static_cast 在编译时使用cd信息执行转换Q在转换执行必要的检?诸如指针界计算, cd?. 其操作数相对是安全的。另一斚wQreinterpret_cast 仅仅是重新解释了l出的对象的比特模型而没有进行二q制转换Q?例子如下Q?/p>
int n=9; double d=static_cast < double > (n); 上面的例子中, 我们一个变量从 int 转换?double?q些cd的二q制表达式是不同的?要将整数 9 转换?双精度整?9Qstatic_cast 需要正地为双_ֺ整数 d 补比特位。其l果?9.0。而reinterpret_cast 的行为却不同: int n=9; double d=reinterpret_cast<double & > (n); q次, l果有所不同. 在进行计以? d 包含无用? q是因ؓ reinterpret_cast 仅仅是复?n 的比特位?d, 没有q行必要的分? 因此, 你需要}慎?reinterpret_cast.
]]>
]]>
而对于非int行,目前为止Q所有的cd分配的字节数都是兼容的,即不同^台对于同一个类型分配相同的字节敎ͼQ?br />
Q在代码中尽量避免用intcdQ根据不同的需要可以用short,long,unsigned int {代ѝ?br />
下面是各个类型一览表【{?br />数据cd名称 字节?/strong> 别名 取D?/font> int * signed,signed int ?a target="_blank">操作pȝ军_Q即与操作系l的Q字长"有关 unsigned int * unsigned 由操作系l决定,即与操作pȝ的"字长Q有?/font> __int8 1 char,signed char ?28 ?127 __int16 2 short,short int,signed short int ?2,768 ?32,767 __int32 4 signed,signed int ?,147,483,648 ?2,147,483,647 __int64 8 ?/font> ?,223,372,036,854,775,808 ?9,223,372,036,854,775,807 bool 1 ?/font> false ?true char 1 signed char ?28 ?127 unsigned char 1 ?/font> 0 ?255 short 2 short int,signed short int ?2,768 ?32,767 unsigned short 2 unsigned short int 0 ?65,535 long 4 long int,signed long int ?,147,483,648 ?2,147,483,647 long long 8 none (but equivalent to __int64) ?,223,372,036,854,775,808 ?9,223,372,036,854,775,807 unsigned long 4 unsigned long int 0 ?4,294,967,295 enum * ?/font> 由操作系l决定,即与操作pȝ的"字长Q有?/font> float 4 ?/font> 3.4E +/- 38 (7 digits) double 8 ?/font> 1.7E +/- 308 (15 digits) long double 8 ?/font> 1.7E +/- 308 (15 digits) wchar_t 2 __wchar_t 0 ?65,535 cd标识W?/td> cd说明 长度
Q字节)范围 备注 char 字符?/td> 1 -128 ~ 127 -27 ~ (27 -1) unsigned char 无符字符?/td> 1 0 ~ 255 0 ~ (28 -1) short int 短整?/td> 2 -32768 ~ 32767 2-15 ~ (215 - 1) unsigned short int 无符短整?/td> 2 0 ~ 65535 0 ~ (216 - 1) int 整型 4 -2147483648 ~ 2147483647 -231 ~ (231 - 1) unsigned int 无符整型 4 0 ~ 4294967295 0 ~ (232-1) float 实型Q单_ֺQ?/td> 4 1.18*10-38 ~ 3.40*1038 7位有效位 double 实型Q双_ֺQ?/td> 8 2.23*10-308 ~ 1.79*10308 15位有效位 long double 实型Q长双精度) 10 3.37*10-4932 ~ 1.18*104932 19位有效位
]]>
青年和女朋友一起从老家到大q打工,热恋中,x友告诉他没有房子别想l婚。青q没有太多的U蓄Ҏ׃不v房子Q在苦劝一个月无果后该青年不想zMQ欲卧轨自杀。铁路民警发现准备寻短见的青q后Q追?00多米其“抓住”,l过开导终于青年攑ּ了寻短见的念头?br />
ȝŞ此新闻,W者心情无比沉重,因ؓq样卛_发生的悲剧绝不是个案。笔者因业务关系认识一个做设计的年MhQ月?000的他在一家知名模具公怓Q设计部经理。在我们交往q程中,他给W者的印象是勤奋、敬业、专业,为此Q笔者非常看重他。有一天,我们相约谈设计方案时Q笔者非常诧异地发现原本满头黑发的他Q居然在短短半月没见的情况下变得头发q、脸型清瘦、眼H深陗?br />
Ҏ谈完Q笔者请其共q晚,酒过三E后,伙子说Z被婚戉K得寝食隑֮Q精已q崩溃。因q样的事牉|太多人,W者也不便多说Q只能劝其一定要保重自己的n体,至于感情、婚姻都可以和对方好好沟通的。餐后,W者让司机送小伙子回住处,在其d后,W者心里久久难以^静?br />
在中国这样处于经高速发展、社会相对稳定、民众生zd富裕的C会Q却有很多年青h被婚房而困扎ͼ有的Z{w买房花光全家的积蓄、榨q父母的养老钱、借完朋友的生z费Q再向银行D债得以成功当了房_有的因实在无能ؓ力买不v房而与相恋的女友分手;有的Z买房_崩溃Q有的ؓ了买房走上犯|道路;有的Z买房自杀…?br />
在笔者看来,中国青年人因Ch而背负沉重的_负担Q折出中国C会诸多悲哀Q?br />
悲哀一Q畸高房h逼青qhC起婚房的最重要原因Q而整个社会却鲜有充分认识。青qhl婚是一辈子的大事,在其l婚时能够拥有属于自己及伴G的爱巢是几乎l大多数青年人的梦想。而无情的现实却是Q畸高房价o青年人的梦想不断破灭。尽部分青qh通过各种途径、利用各U方法实C拥有产权婚房的梦惻I可其婚后却因此背负了沉重的债务负担?br />
可大多数青年人却因买不v婚房而备受煎熬,一q幕悲剧正在或即上演。对于青qhC起婚房,很多Z能理性看待。有些地产利益代a青年Z不v房是自己努力的不够;有些青年Ch是其家h不能伸出援手Q有些h说青qh应该买不vѝ这些h在表q这L观点Ӟ大多扑և诸多理由。可他们却刻意E化或忽略一个事实,那就是导致青q买不v房的最重要原因——畸高房P
对于青年C起婚房,整个C会~Z充分认识Q民众表现出L、妥协、顺从。这从整个社会对于高房h的控诉与抉|极其无力、民众ؓ了子女结婚們ְ全家U蓄为其买房、尽房价已然畸高民众依然ؓ其买单等可以得到明证?br />
悲哀二:荒唐的婚恋观、爱情观依然在深深媄响当今社会。很多h认ؓQ结婚就要拥有权房Q只有这P婚姻基础才显得牢固。而在W者看来,两个Z所以有l婚的想法(或打)Q是双方感情发展C要通过l徏家庭共同生活的地步,是爱情的升华Q是共同理想的结Ӟ是对好生活的共同追求,也是对社会的责Q。所有这一切,与双Ҏ否拥有权房没有M关系?br />
有h_男h没有产权房的话,很难说服女友与自己走向婚姻的殿堂。这一观点也是很荒谬的。我们要清楚自己要找的是共同生活的伴侣还是爱慕虚荣的q客Q如果你的女友因没有产权房而不和你l婚Q只能说明一个问题:你们的感情还处在较浅的层ơ,是非常脆qQ还没有辑ֈ能步入婚L堂的地步Q这P需要进一步发展,或者,你们双方Ҏ׃适合?br />
当然Q也有h会说Q我爱女友胜q爱自己Q她的要求我必须满Qʎ汤蹈火亦在所不惜Q何况买房子呢?诚然Q对感情h执着的精是值得赞赏的,可如果不从自q实际能力考虑盲目地通过购房来取悦女友是极不可取的。试惻IZ满奛_q样的要求,你将付出多大的代P有可能榨q父母的养老钱、背上沉重的债务包袱、个Z用受到很大媄响、结婚后生活质量大打折扣…?br />
上述观点都是荒唐的婚姻观、爱情观在作怪。对于爱的理解,可以说是见仁见智。我一贯的观点是,一加一{于爱,即两个h心心相印Q互相理解、互相支持、互相奉献,甘苦于共、相敬相随、偕老一生。爱是无U的、纯z的Q是不受金钱、物质、空间等非情感因素媄响的。在爱情的天q上Q承载的是关怀、奉献、崇敬与对美好生zȝq求与向往。而诸如,金钱、权房、力所不能及的物质需求不应成为爱情天q上的砝码?br />
悲哀三:民众理性房屋消Ҏ育严重缺失,政府及相关部门却没能为此做出_的努力。近q来Q由于房C市场的持l火爆,很多本不具备买房实力的h在当时的市场状况下投入了买房者行列(在我们n边,q样的例子有很多Q,q种行ؓ在一定程度上助推了房L上涨。诚Ӟ在当时看来,q种行ؓ无可厚非Q也有一部䆾人因此获得了意想不到的收莗可却有更大一部分人因自己的购房行上了沉重的包袱,甚至有的人成为房奴。这是非理性房屋消费观D的恶果?br />
对于房屋消费Q民众大多是凭自q判断Q而政府及相关教育部门却没能做出理性的引导。无论是在教育机构,各类媒体q是文化作品中,我们都很看到关于民众理性房屋消费的引导和宣传。而近q来W者与牛刀、时寒冰{理性地产研Ih士所发出的民众理性房屋消费的呼吁也因~少媒体力推而没在|络h中?br />
悲哀四:C商用来诱导青qh买房的所谓“刚需理论”依然大行其道,寚wqh依然产生_的媄响。房C刚需是地产利益群体h为炮制出来旨在蒙蔽ƈ用来民众的伪命题。所谓刚性需求,是相对于Ҏ需求而言的,卛_L的需求。在生活中,我们必须喝水、吃饭、呼吸空气,q些均ؓ刚性需求,因ؓ如果一个h满不了q些需求的话,难以存zR而除了上qCU需求外Q我们很难找有什么是必须要满的需求?br />
房地产利益群体在q行房销售时Qh为杜撰ƈL向民众灌输一个概念,卛_自住、改善、结婚而买房行为属于刚性需求。事实果真如此吗Q显然不是!在房价处畔R状态的今天Q普通民众D全家乃至整个家族之力Q甚至不惜透支未来十几、二十年的收入买房完全是一U异常奢侈的消费行ؓ。这L行ؓq当于普通民众家庭餐吃q味Q就相当于普通民众大丑ր贷IK玛尼西服、背LV包、开宝马车。很昄Q这L购买行ؓ完全没有必要Q更何来刚性一_再者说Q结婚就一定得买房吗?U房生活׃能步入婚L堂了Q相信理性的人定然不认同l婚一定要买房的观炏V?
]]>
]]>
]]>
在讲q这个Makefile之前Q还是让我们先来_略地看一看Makefile的规则?br />
target ... : prerequisites ...
command
...
...
target也就是一个目标文Ӟ可以是Object FileQ也可以是执行文件?br />
prerequisites是Q要生成那个target所需要的文g或是目标?br />
command也就是make需要执行的命o。(L的Shell命oQ?br />
q是一个文件的依赖关系Q也是_targetq一个或多个的目标文件依赖于prerequisites中的文gQ其生成规则定义?command中。说白一点就是说Qprerequisites中如果有
一个以上的文g比target文g要新的话Qcommand所定义的命令就会被执行。这是Makefile的规则。也是Makefile中最核心的内宏V?br />
二、一个示?br />
正如前面所说的Q如果一个工E有3个头文gQ和8个C文gQ我们ؓ了完成前面所q的那三个规则,我们的Makefile应该是下面的q个样子的?br />
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
gcc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
gcc -c main.c
kbd.o : kbd.c defs.h command.h
gcc -c kbd.c
command.o : command.c defs.h command.h
gcc -c command.c
display.o : display.c defs.h buffer.h
gcc -c display.c
insert.o : insert.c defs.h buffer.h
gcc -c insert.c
search.o : search.c defs.h buffer.h
gcc -c search.c
files.o : files.c defs.h buffer.h command.h
gcc -c files.c
utils.o : utils.c defs.h
gcc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
反斜杠(\Q是换行W的意思。这h较便于Makefile的易诅R我们可以把q个内容保存在文件ؓ“Makefile”或“makefile”的文g中,然后在该目录下直接输入命令“make”就
可以生成执行文gedit。如果要删除执行文g和所有的中间目标文gQ那么,只要单地执行一?“make clean”就可以了?br />
在这个makefile中,目标文gQtargetQ包含:执行文gedit和中间目标文Ӟ*.oQ,依赖文gQprerequisitesQ就是冒号后面的那些 .c 文g?.h文g。每一?.o 文g都有
一l依赖文Ӟ而这?.o 文g又是执行文g edit 的依赖文件。依赖关pȝ实质上就是说明了目标文g是由哪些文g生成的,换言之,目标文g是哪些文件更新的?br />
在定义好依赖关系后,后箋的那一行定义了如何生成目标文g的操作系l命令,一定要以一个Tab键作为开头。记住,makeq不命令是怎么工作的,他只执行所定义的命令?br />
make会比较targets文g和prerequisites文g的修Ҏ期,如果prerequisites文g的日期要?targets文g的日期要斎ͼ或者target不存在的话,那么Qmake׃执行后箋定义?br />
命o?br />
q里要说明一点的是,clean不是一个文Ӟ它只不过是一个动作名字,有点像C语言中的lable一P其冒号后什么也没有Q那么,make׃会自动去找文件的依赖性,也就不会?br />
动执行其后所定义的命令。要执行其后的命令,p在make命o后明昑־指出q个lable的名字。这LҎ非常有用Q我们可以在一个makefile中定义不用的~译或是和编译无?br />
的命令,比如E序的打包,E序的备份,{等?br />
三、make是如何工作的
在默认的方式下,也就是我们只输入make命o。那么,
1、make会在当前目录下找名字叫“Makefile”或“makefile”的文g?br />
2、如果找刎ͼ它会找文件中的第一个目标文ӞtargetQ,在上面的例子中,他会扑ֈ“edit”这个文Ӟq把q个文g作ؓ最l的目标文g?br />
3、如果edit文g不存在,或是edit所依赖的后面的 .o 文g的文件修Ҏ间要比editq个文g斎ͼ那么Q他׃执行后面所定义的命令来生成editq个文g?br />
4、如果edit所依赖?o文g也不存在Q那么make会在当前文g中找目标?o文g的依赖性,如果扑ֈ则再Ҏ那一个规则生?o文g?br />
5、当Ӟ你的C文g和H文g是存在的啦,于是make会生?.o 文gQ然后再?.o 文g生命make的终极Q务,也就是执行文件edit了?br />
q就是整个make的依赖性,make会一层又一层地L文g的依赖关p,直到最l编译出W一个目标文件。在扑֯的过E中Q如果出现错误,比如最后被依赖的文件找不到Q那?br />
make׃直接退出,q报错,而对于所定义的命令的错误Q或是编译不成功QmakeҎ不理。make只管文g的依赖性,卻I如果在我找了依赖关系之后Q冒号后面的文gq是不在
Q那么对不vQ我׃工作啦?br />
通过上述分析Q我们知道,像cleanq种Q没有被W一个目标文件直接或间接兌Q那么它后面所定义的命令将不会被自动执行,不过Q我们可以显C make执行。即命o—?br />
“make clean”,以此来清除所有的目标文gQ以侉K~译?br />
于是在我们编E中Q如果这个工E已被编译过了,当我们修改了其中一个源文gQ比如file.cQ那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在q个依性关pd
面所定义的命令)Q于是file.o的文件也是最新的啦,于是file.o的文件修Ҏ间要比edit要新Q所以edit 也会被重新链接了?br />
而如果我们改变了“command.h”,那么Qkdb.o、command.o和files.o都会被重~译Qƈ且,edit会被重链接?br />
四、makefile中用变?br />
在上面的例子中,先让我们看看edit的规则:
edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
gcc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
我们可以看到[.o]文g的字W串被重复了两次Q如果我们的工程需要加入一个新的[.o]文gQ那么我们需要在两个地方加(应该是三个地方,q有一个地方在clean中)。当Ӟ?br />
们的makefileq不复杂Q所以在两个地方加也不篏Q但如果makefile变得复杂Q那么我们就有可能会忘掉一个需要加入的地方Q而导致编译失败。所以,Zmakefile的易l护Q?br />
在makefile中我们可以用变量。makefile的变量也是一个字W串Q理解成C语言中的宏可能会更好?br />
比如Q我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJQ反正不什么啦Q只要能够表Cobj文gp了。我们在makefile一开始就q样定义Q?br />
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
于是Q我们就可以很方便地在我们的makefile中以?(objects)”的方式来用这个变量了Q于是我们的改良版makefile变成下面这个样子:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
gcc -o edit $(objects)
main.o : main.c defs.h
gcc -c main.c
kbd.o : kbd.c defs.h command.h
gcc -c kbd.c
command.o : command.c defs.h command.h
gcc -c command.c
display.o : display.c defs.h buffer.h
gcc -c display.c
insert.o : insert.c defs.h buffer.h
gcc -c insert.c
search.o : search.c defs.h buffer.h
gcc -c search.c
files.o : files.c defs.h buffer.h command.h
gcc -c files.c
utils.o : utils.c defs.h
gcc -c utils.c
clean :
rm edit $(objects)
于是如果有新?.o 文g加入Q我们只需单地修改一?objects 变量可以了?br />
五、让make自动推导
GNU的make很强大,它可以自动推导文件以及文件依赖关pd面的命oQ于是我们就没必要去在每一个[.o]文g后都写上cM的命令,因ؓQ我们的make会自动识别,q自己推导命
令?br />
只要make看到一个[.o]文gQ它׃自动的把[.c]文g加在依赖关系中,如果make扑ֈ一个whatever.oQ那?whatever.cQ就会是whatever.o的依赖文件。ƈ?cc -c
whatever.c 也会被推导出来,于是Q我们的makefile再也不用写得q么复杂。那么新的makefile变为?br />
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
clean :
rm edit $(objects)
六、清I目标文件的规则
每个Makefile中都应该写一个清I目标文Ӟ.o和执行文Ӟ的规则,q不仅便于重~译Q也很利于保持文件的清洁。其风格如下Q?br />
clean:
rm edit $(objects)
更ؓE_的做法是Q?br />
.PHONY : clean
clean :
-rm edit $(objects)
.PHONY意思表Cclean是一个“伪目标”,。而在rm命o前面加了一个小减号的意思就是,也许某些文g出现问题Q但不要,l箋做后面的事。当Ӟclean的规则不
要放在文件的开_不然Q这׃变成make的默认目标。不成文的规矩是——“clean从来都是攑֜文g的最后”?br />
转自:http://bbs.dameng.com/viewthread.php?tid=1506
]]>
]]>
Winsock 扩展函数 AcceptEx 是唯一能够使用重叠 I/O 接受客户q接的函数。下面主要深入探讨用该函数接收q接的问题?
前面已经讨论q,当客戯接进来时Q服务器需要创Z个套接字来负责维护与一个客L的会话。?AcceptEx 函数之前必须创徏一些套接字Qƈ且这些套接字必须是未l定、未q接的,即它们可能在调?TransmitFile, TransmitPackets, ?DisconnectEx 后可以重用?
响应服务器必LLh_?AcceptEx 在站岗,以便在有客户q接h时调用。但是,q没有具体的数量能够保证服务器能够立卛_应连接。我们知道在调用 listen 监听套接字|于监听状态后Q?TCP/IP 堆栈会自动接受到来的q接Q直到达?listen ?backlog 参数讑֮的限制。对?Windows NT 服务器而言Q支持的 backlog 的最大gؓ 200 。如果服务器投递了 15 ?AcceptEx 调用Q然后突然有 50 个客戯求连接服务器Q它们的q接h都不会遭到拒l。服务器投递的 AcceptEx I/O 会满_面的 15 个连接,剩下?35 个连接都被系l默认连接了。检查一?backlog 的值发玎ͼpȝq有能力默认接受 165 个连接。之后,如果服务器投?AcceptEx 调用Q它们会立即成功q回Q因为系l会默认接收的q接攑օ ?{待q接队列 ?中?
服务器的Ҏ是军_要投递多个 AcceptEx 操作的重要因素。例如,希望处理大量短时间即时连接的客户要比处理量长时间连接的客户投递更多的 AcceptEx I/O 。一个好的策略是允许 AcceptEx 的调用数量在最值和最大g间变化。具体做法是Q应用程序跟t未决的 AcceptEx I/O 的数量,当一个或多个 I/O 完成使这个未?I/O 数量变得比最D时Q就再投递额外的 AcceptEx I/O ?
?Windows 2000 和以后的 Windows 操作pȝ版本中, Winsock 提供了一U机Ӟ用来定应用E序是否投递了_?AcceptEx 调用。创建监听套接字Ӟ使用 WSAEventSelect 函数为监听套接字兌一个事件对象,注册 FD_ACCEPT 事g。如果投递的 AcceptEx 操作用完Q但是仍有客戯求接入(pȝҎ backlog 值决定是否接受这些连接)Q事件对象就是受信,说明应该投递额外的 AcceptEx 操作了。这实际上还是利用事件对象来使调用线E处于一U??可警告状??Q当有客戯接请求时Q就Ҏ当前 AcceptEx 操作是否用完来警告(通知Q是否需要投递新?AcceptEx 操作来处理新的客戯接?
使用 AcceptEx 处理q接的另外一个功能就是在处理q接时还可以接收用户发来的第一块数据(前提是ؓ AcceptEx 提供了接收缓冲区Q,q对于那些请求连接的同时发送了一些数据过来的客户来说很适用。但是,此时Q除非接收连接的同时接收C客户发送过来的一些数据,否则 AcceptEx 是不会返回的?
Z满客户的需求,服务器不得不投递更多的接受 I/O Q这会占用大量的pȝ资源。如果客户仅调用 connect 函数q接服务器,长时间既不发送数据,也不关闭q接Q就可能造成 AcceptEx 投递的大量重叠 I/O 操作不能q回。这是 ?恶意q接 ?。ؓ此,服务器应该记录每?AcceptEx 投递的未决 I/O, 定时扫描它们Q设|?SO_CONNECT_TIME 参数调用 getsockopt 查它们连接的旉Q如果超Ӟ将q接关闭。如果?WSAEventSelect 模型来通知有连接事Ӟ则当事g受信Ӟ是检查客户套接字Q?AcceptSocket Q是否真正连接了?
每当调用 AcceptEx 接受客户端连接时Q它也在{待接受客户发送过来的W一个数据块Q这时不允许投递另外一?AcceptEx 。当 AcceptEx q回后,如果事g对象再次受信则表明有新的q接到来。需要注意的是,无论何时Q千万不要关闭一个调?AcceptEx q没有返回的套接字( AcceptSocket Q,因ؓq会D内存泄露。因Z内部执行逻辑看,当没有连接的套接字句柄被关闭Ӟ调用 AcceptEx 所涉及到的内核模式的数据结构ƈ不会清除掉,直到有新的连接徏立或者监听套接字被关闭?
管在一个等待完成通知的工作者线E中Q投递一?AcceptEx 操作Q看h既简单又合情合理Q但是应量避免q样做,因ؓ创徏套接字还是很耗费资源的。另外,也不要在工作者线E中q行M复杂的计,以便处理器可以尽快的在接到完成通知后进行后l处理。创建套接字耗费资源的一个原因在?Winsock 2.0 本n的架构很复杂Q成功地创徏一个套接字可能需要调用很多内核服务。因此,服务器应该在单独U程中创建套接字Q投?AcceptEx 操作。当调用U程投递的 AcceptEx 重叠操作完成Ӟ一个受信的事g会通知处理U程?
6.2.2 数据传输问题
数据传输是通信E序执行的核心操作。当一个客户与服务器徏立连接后Q它们的主要工作是传输数据Q因为数据是信息的表C。由上一节几U?I/O 模型的性能试分析可知Q当q接数量很大Ӟ数据吞吐量是一个重要的性能考核指标?
从性能角度考虑Q所有的数据传输最好都应采用重?I/O 处理。默认情况下Q系lؓ每个 socket 分配一个的接受~冲区和一个发送缓冲区Q用来缓存接收和发送的数据。但在重?I/O 中,q些~冲区往往不用Q可以传递参?SO_SNDBUF ?SO_RCVBUF 调用 setsockopt Q来它们设|ؓ 0 ?
让我们来看看Q当发送缓冲区没有讄?0 Ӟpȝ是怎么处理一个典型的 send 操作的。当一个应用程序调?send 函数Ӟ如果有充的~冲I间Q需要发送的数据被拯到套接字的发送缓冲区Q?send 函数立即成功q回Qƈ且一个完成通知被抛出。另外一个方面,如果套接字的发送缓冲区已满Q则应用E序提供的发送缓冲区被锁定,再次?send 函数的调用将会返?WSA_IO_PENDING 错误。当发送缓冲区中的数据被处理(例如Q提交给传输层处理)Ӟ Winsock 实际上直接处理锁定在~冲Z的数据,也即l过套接字的发送缓冲区Q直接从应用E序~冲Z提交数据l传输层?
接收数据的情冉|好相反。当一个重叠的 receive h抛出后,如果数据已经接收成功Q它会被~存在套接字接收~冲区。数据会拯到应用程序缓冲区Q直到饱和)?receive 调用q回Qƈ且一个完成通知被抛出。当套接字缓冲区被设|ؓI时Q如果调用重叠的 receive 操作返?WSA_IO_PENDING 错误。当有数据到达时Q它绕q套接字~冲直接被拯到应用程序缓冲区?
讄单套接字~冲Zؓ 0 Qƈ不能提高性能Q因为只要一直有大量的重叠接发请求被抛出Q就不会有额外的内存拯。设|套接字发送缓冲区为空比设|套接字接收~冲ZؓI对pȝ的性能影响要小。因为应用程序的发送缓冲区会被l常锁定直到它被提交l传输层处理。然而,若将接收~冲|ؓ 0 Qƈ且没有重叠的 receive 调用QQ何传q来的数据只能缓存在传输层。传输层驱动E序只会~存滑动H口寸的数据,?17KB?传输层可以分配的~冲区大的上限。实际的~冲?17KB 。传输层~冲区(针对一ơ连接)是在非分|之外分配的,q意味着Q当服务建立?1000 个连接时Q即使没有抛?receive hQ非分页池中也会分配 17MB 的内存。而非分页池是很珍늚资源Q除非服务器可以保证L有接收请求抛出,否则套接字接收缓冲区应该不需讄?
只有在一些特D情况下Q对套接字接收缓冲区不予讄会D性能降低。考虑服务器需要处理成千上万个客户q接Q而每个连接上又都没有投?receive h的情况,如果客户端零星地发送数据过来,传输q来的数据将被缓存在套接字接收缓冲区中。当服务器处理一?receive 重叠 I/O Ӟ它会做一些不必要的工作。当完成通知到达Ӟ重叠操作会处理一?I/O h包( IRP Q。在q种情Ş下,服务器不能保留很多抛出的 receive h。因此,最好用简单的非阻塞接收函数?
6.3 内存资源理问题
׃机器g条g所限,pȝ资源是有限的Q因此不得不考虑内存资源的管理问题。从上一节对不同 I/O 模型q行的性能试l果分析可知Q维持大规模的通信q接Q不仅会耗费掉大量内存,而且?CPU 的占用也是很高的?
对于配置比较高的服务器而言Q处理成千上万个q接q不成问题。但是随着q接量的剧增Q内存资源的限制逐渐凸现。最有可能遇到的两个限制因素是锁定和非分|。锁定页的限制不是太严重Q更应该避免的是非分|被耗尽。每一ơ调用重叠的 send ?receive hQ提交的~冲区都可能被锁住。当内存被锁定时Q它׃能从物理内存换出。操作系l对锁定内存的数量是有限制的Q当辑ֈ极限Ӟ重叠操作会q回 WSAENOBUFS 错误。如果服务器在每个连接上投递多个重叠接收操作,随着客户q接数量的增多,极限׃辑ֈ。如果期望服务器能够处理高ƈ发通信Q服务器可以在每个连接上投递一?0 字节的接受操作,q样׃会有内存锁定?0 字节的接受完成以后,服务器可以简单地执行一个非d的接收函数来获取~存在套接字接收~冲Z的所有数据。当非阻塞接收调用返?WSAEWOULDBLOCK ӞpCZ再有未决的数据了。这U方法非帔R合用来设计那些希望通过牺牲每个套接字上的吞吐率来获取更大规模ƈ发连接的服务器?
当然Q最好还要了解客L与服务器通信的方式。在上面的例子中Q当 0 字节的接收完成后Q再投递一个异步接收操作,接收到所有缓存在套接字接收缓冲区中的数据。如果服务器知道客户端将会连l不断发送数据,那么?0 字节的接收完成后Q假如客L发送大数据块(过单套接字~冲?8KB 的容量)q来Q服务器抛Z个或多个重叠的接收操作?
另外一个需要重点考虑的问题就是系l所需늚数量。当pȝ锁定传递给重叠操作的内存时Q它是在边界上q行的。在 x86 体系l构上,内存늚大小?4KB 。如果一个操作投递了 1KB 的缓冲区Q系l实际上会ؓ它锁?4KB 大小的内存块。ؓ避免q种费Q重叠发送和接收~冲区的大小应该是页大小的倍数。可以?GetSystemInfo q个 API 来获知当前系l页的大?
如果H破非分|极限Q将会导致更严重的错误,q且很难恢复。非分页池是内存的一部分Q它帔R内存Qƈ且永q不会被交换出去。内核模式的pȝlgQ如驱动E序Q通常使用非分|Q其中包?Winsock 和协议驱动程序,例如 tcpip.sys 。每个套接字的创建将消耗一部分非分页池,用于l持套接字状态信息。当套接字绑定到一个地址后, TCP/IP 堆栈分配额外的非分|来保存本地地址的信息。当一个对{套接字接入后, TCP/IP 堆栈也将分配部分非分|来保存远E地址信息。基本上Q一个徏立连接的套接字占?2KB 非分|内存 , ?accept ?AcceptEx q回的套接字则占?1.5KB 非分|内存。之所以出现这个区别,是因为服务器本地地址信息已经存储在监听套接字中,?accept ?AcceptEx q回的套接字只需保存q程L地址信息。此外,每个在套接字上投递的重叠操作都需要给 I/O h包( IRP Q分配内存,一?IRP 使用大约 500B 非分|内存?
从以上分析可以看出,为每个连接分配的非分|内存q不是很大。然而,随着客户q接量逐增Q服务器寚w分页池的使用是非常大的。考虑q行在只?1GB 物理内存?Windows 2000 或以后版?Windows pȝ上的服务器,有 256MB 的内存非配给非分|。通常Q非分页池大是机器物理内存?1/4 Q?Windows 2000 及以后版本的 Windows pȝ上,非分|大小?256MB Q?/1GB Q,?Windows NT 4.0 限制?128MB Q?1GB Q。拥?256MB 的非分页池的服务器可以支?50,000 或更大的q接量。但是必限刉叠的 accept 数量Q以及在已经建立q接的重叠收发操作。在q个例子中,如果已经建立q接的套接字Q按每个 1.5KB 计算Q将耗费 75MB 的非分页池内存。如果采用了上面提及的投?0 字节接收的方法,q样为每个连接分配的 IRP 占?25MB 的非分页池内存?
如果pȝ耗尽了非分页池,会有两种可能的后果。在最好的情况下, Winsock 调用返?WSAENOBUFS 错误。最p糕的情冉|pȝ崩溃Q这U情况通常是系l没能正处理内存非配的问题造成的。没有一U可行的Ҏ能够恢复非分|耗尽的错误,q且也没有可行的Ҏ来监视非分页池可分配的大,因ؓ非分|耗尽Dpȝ崩溃?
׃上探讨,可以得出l论Q没有一U方法可以确定服务器到底支持多大的ƈ发连接和重叠操作Qƈ且也不可能准地L非分|是否耗尽或者锁定内存页数超q极限。因为它们都导?Winsock 调用都返回相同的错误 —WSAENOBUFS 。因Z上因素,针对服务器的试必须试不同数量的连接情况以及重叠操作完成情况,以便在ƈ发通信规模和数据吞吐率q两个指标之间选择一U折中的Ҏ。如果在Ҏ中强加限Ӟ以防止服务器耗尽非分|Q则q回 WSAENOBUFS 错误Ӟ我们q道是因ؓ过了锁定页的限制。ƈ且可以以一U更优化的处理方式编写程序,如进一步限制一些待决的操作或关闭某些连接?
包重新排序问?
q个问题与~性没有多大关联,但是却是实际通信中不得不考虑的一个问题,因ؓ它涉及到能否正确通信的问题?
虽然使用完成端口?I/O 操作L会按照它们被提交的顺序完成,但是U程调度问题可能会导致关联到完成端口上的工作不能按正帔R序完成。例如,有两?I/O 工作U程Q应该接??字节?1 Q字节块 2 Q字节块 3?Q但是你可能以错误顺序接收这 3 个字节块Q??字节?2 Q字节块 1 Q字节块 3?。这也意味着在完成端口上投递发送请求发送数据时Q数据实际也会以错误序被发送出厅R?
当然Q如果只使用一个工作线E,仅提交一?I/O 调用Q是不存在顺序问题的。因为同一时刻Q一个工作线E只能处理一?I/O 操作。但是,q样没有发挥出完成端口的真正优炏V?
如第 3 章《自定义应用层通信协议》所qͼ一个简单的解决Ҏ是为每个封包添加一个协议头。协议头主要是一个封包的实际字节敎ͼ如自定义 Package 包的W一个字D?m_nCmdLen 是q个包占用的字节数。通信的接受方通过分析协议头分析本ơ通信有多数据要接收Q然后l读后面的数据,直到一个封包被完整接收完才接收下一个封包?
当服务器一ơ仅做一个异步调用时Q上q封包协议头的解x案是很有效的。但是,如果要充分发?IOCP 服务器的潜力Q肯定有多个未决的异步读操作{待数据的到来。这意味着Q多个一步操作不能按序完成Q未册 I/O q回的字节流不能按顺序处理,接收到的字节可能组合成正确的封包,也有可能l合成错误的包。因此,要解册个问题,q必Mؓ提交的读 I/O 分配序列受?
说明Q?
本文主要译自?Network programming for microsoft windows 》一书的 6.2 节《可伸羃的服务器体系l构》和 6.3 节《资源管理》?
其中包重新排序问题,参?王艳q ?Windows |络与通信E序设计 ?4.3.4 节《包重新排序问题??