| Yongqing's profile花开不谢PhotosBlogLists | Help |
|
|
14 December 内存泄漏的诊治真正做过C++项目的人应该知道,内存泄漏是一种常见问题,但又是一种最难以发现(测试)和解决的问题之一。我也能体会到这点,而且,内存泄漏常被我们的用户发现(我们测试还是不够啊),用户又会经常说,如果这个问题不解决,我就不买你们的产品了,然后销售就来找研发的工程师,希望短时间内就能解决,因此每每遇到这样的case,我都压力比较大,而且经常一头雾水。当然,解决这样的问题之后,还是颇有成就感的,不过也不得不承认,这样的问题还是越少越好。最近花了3天时间,解决掉两个内存泄漏的问题,之前也解决过若干次,因此想写篇文章总结一下,也借此与能看到这篇文章的高手交流一下。高手无处不在(比如小健健应该能看到这篇文章),如果看到,也请轻拍并请不吝赐教。 什么是内存泄漏? 内存泄漏判断的方法基本比较简单,打开Windows Task Manager,找到你的应用程序的process,看它使用了多少内存,如果一直在增长,那说明十有八九是有内存泄漏了。我曾经也遇到过VxWorks上的内存泄漏,我们也有个类似于Task Manager的东西,比较容易发现。但是这个判断并不是完全准确,有两种情况:一种是程序在运行(比如在放电影),内存使用一直在增加,程序运行结束(电影放完),内存使用还是不能恢复到刚开始的水平;另一种,程序运行结束,内存使用恢复到了刚开始的水平。前一种基本上能确定是内存泄漏,后一种就未必了,具体问题具体分析。我两种都遇到过,后面那种情况是这样的:程序会一直运行,7×24,运行一段时间会把Windows的可用内存全部吃光。不管这个是不是真正意义上的内存泄漏,对于用户来说,都是不可接受的。 如何诊治? 诊治包括诊断和治疗,即分析问题和解决问题的过程。分析问题就是如何找到究竟是哪段代码引起了内存泄漏。我采用的基本方法就是narrow down,注释掉某段程序,看看内存泄漏是否消失,如果消失,再缩小注释的代码范围,直到找到罪魁祸首。前面提到的VxWorks上的内存泄漏就是用这种方法解决的,narrow down下来终于发现了某一行代码引起了内存泄漏,我调用了别人写的函数,返回一个指针,我先new了一块内存,给这个指针,然后传入函数,但是事实上,在这个函数内部,又分配了一块内存,函数执行完,我只delete掉一次。问题很明显,我没有仔细看别人函数的头文件,其实人家写清楚了,在这个函数里面会有内存的分配,我们调用方无需再另行分配内存。 但是这样的方法往往并不奏效,比如这几天改掉的内存泄漏问题用这样的方法基本没有用。好不容易narrow down下来,是那一两行代码引起了内存泄漏,注释掉,确实就没有泄漏了,但是这两行代码无论如何都看不出有什么泄漏。其实最终发现都不是这两行代码引起的问题,问题都是在其他地方,只不过注释掉这几行可能在逻辑上避免了真正引起内存泄漏代码的运行。如果遇到这样的情况,还有什么办法?我目前的答案是——暂时还没有什么好办法,要多想。。。 其实也并不是束手无策,比如这次就借助于这样一段代码,找到了一些线索。这段代码是我的tech lead写的,不得不佩服姜还是老的辣啊。 1)我们主要的module是一个dll,在dll中所有分配内存和释放内存的代码都统一用函数A和D来表示。 2)建立一个全局变量counter,初始化为0,函数A中+1,D中-1,相当于一个计数器。 3)dll中的每个export的函数,都去check counter,怎么check? 4)造一个类C,这个类有个成员变量m,构造函数中用counter去初始化这个成员变量m,析构函数中check当前的counter和成员变量m是否是同一个值。 5)每个export的函数中,都去实例化一个C类,函数结束,实例被析构,就去check counter和m,如果有不一致,可以输出信息。比如这个函数中,可能分配了一些内存,但是忘记了释放,这时候counter和m就不一致了。 6)DllMain函数中最后放一个aasert,看看counter是否为0。 7)注意,第5)步中即便不一致也不一定表示有内存泄漏,但是多多少少能给出点有用的信息。 一般分析问题的过程是比较重要的,找到了问题,解决它们都是片刻之间的事情。 有一些技术可以一定程度上减少内存泄漏发生的可能性,比如使用智能指针。也有一些分析内存泄漏的工具,比较出名的是BoundCheck、AQTime等,我没有用过,据称还是有点帮助的。 内存泄漏是最让我头疼的问题之一,本质的问题还是要归结到自己技术的不成熟。也许分析并解决内存泄漏的问题的能力,也可以算得上C++程序员之间的一种比较重要的区别吧。希望这样的问题越来越少。 18 July IE打开windows live space很慢的问题我对IE向来不喜欢,但是在windows live messenger中点击某人的space,总是会用IE打开。最近发现用IE打开非常慢,而且页面排布一塌糊涂,但是在firefox中却没有问题,于是我上网查了一查,发现只要你运行这样一条命令就可以了。 regsvr32 softpub.dll 关于这个dll,我暂时没找到什么有用的信息,只说是用于支持加密的动态链接库文件。我用depends打开看了看函数名称,应该确实和加密有关吧。 偶然又发现了一个很酷的firefox插件,Split Browser,如其名,安装之后就可以在firefox中分屏浏览不同的页面了,相当方便。 27 May 有没有一个同步备份文件的方案?玩电脑也好多年了,重装机器无数次,基本上没遇到过忘记保存重要文件的情况。我一般的方法是将硬盘格式化成3-4个分区,然后系统和应用软件放在C盘,每次重装系统之前都会把C盘上的重要文件备份下来,这个方法我自认为还是合理的。 前段时间在公司安装了Windows XP SP3,装完重启,结果发现一直蓝屏,用尽办法也不能进系统,这是我始料未及的,老革命遇到新问题了。无奈之下,只好重装系统,这样有些在C盘的文件我就没能保存下来,蒙受了一些损失。比如我在工作中惯用Office OneNote,比如我是如何fix掉某些bug的,我项目中的总结等等。这些文件都默认的放在了C盘,结果就一去不复返了。幸好工作中的程序都在服务器上有备份,问题不大。 所以我一直在想是否有这样一个解决方案,有几点要求: 1)能够备份我电脑上的所有文件,现在硬盘已经很便宜了。 2)文件能够同步,比如我修改了电脑上的某个文件,程序能够自动发现这个改动并且把最新的文件放到备份的目录下。 3)备份是自动的,无需我多操心,使用CPU空闲时间来做这件事。 暂时我还不太清楚是否有这样的应用软件,苹果公司有个叫做Time Capsule的产品,与我所说的类似,不过价钱应该不菲。其实自己做这样一个软件倒也不是个难事。 22 November 最近遇到的几个技术问题我目前在公司做的一套程序其实也不算是个太大的东西,我初步估算大概几万行代码,几十个类。开发以来,算是各种问题都遇到了。 首先是character set encoding的问题,就是字符串编码。我在blog里面也写过,虽然对encoding本身还比较了解,但是自己程序当中的字符串编码转换还没仔细研究,知其然不知其所以然。 其次是endian conversion的问题,道理比较简单,但是刚开始做的时候还真不知道什么地方该转,什么地方不该转。 最近遇到的是一个alignment的问题。头疼了好几天,最后遇到一个VxWorks的专家才解决了问题。同样的代码在Window上没有问题,在VxWorks上就是不对,我把所需内存中的每个字符都打印出来了,Windows上和VxWorks上都一样,结果显示的时候两者不一致。一直没想到是对齐的问题,后来遇上那个专家才想起来。本质原因还在于对LabVIEW的源代码不了解。其实至今对LabVIEW中Array的具体存储方式还是不甚了解。话说回来,当初还是应该想到对齐的,因为每次显示,我内存中的字符都被砍掉了头4个字节。 以前在学校或者实习的时候没遇到过这么大代码量并且如此复杂的程序,最多也就一万多行的C程序,当然ASP程序不算。。。其实上述问题在学校的时候都是学过的,现在差不多也都快还给老师了。 我前段时间还研究了一个javascript+css的程序,怎么看怎么不懂,但是那个东西很酷也很有用。后来静下心来把javascript中类的基本概念先了解了一下,结果基本完成了自己想要的功能。看来不仅女人要静心,程序员也要静心。 虽然最近稍有压力,不过静下心来想想也算是自己的修炼,就像打游戏吧,闯过一关自己就高了一个等级。忙点也挺好,能学不少东西,修炼的也更快。成功就是自己不断调整自己,不断提高自己。况且,解决掉问题后的愉悦也很让人享受。 18 September Character Set Encoding Basics (5)今天给这个题目来个了结。其实也基本上写完了。目前用的比较多的就是Unicode的编码方式。不过由于历史原因,还同时存在着其他很多不同的编码方式。所以,不同编码之间的转换就是一个问题。我当初为什么想来了解一下字符串编码方式,就是因为工作中遇到了字符串编码转换的问题。 其实,在各种语言或平台中(java/.net)都或多或少的有一些现成的方法。其他的不了解,windows api可以谈谈,主要有两个函数可以干这件事: WideCharToMultiByte(...) MultiByteToWideChar(...) 这里的multibyte可以选择codepage的,ansi或者utf-8都可选。widows api有点不爽,就是不能用在其他平台上。幸好我工作中遇到的问题基本上用NI自己的库函数也可以解决。其实有了前文所说的一些方法,自己写一个程序来进行转换也不是太难的事。 ===================================================================== 计划接下来写一下读历史的心得,并且写几篇文章分享一下我家装修房子的经验,先让我拍几张照片再说:) 04 September Character Set Encoding Basics (4)GB2312对于欧洲的文字,用一个字节(8位)来表示一个字符还是比较容易的,难的是东亚的字符串,如中文,具有大量的符号,远远超过256个,因此人们想出了一个办法,就是用两个字节来表示一个字符。这样字符的数量就得以大大的扩充,比如GB2312-80,就可以用来表示6768种汉字。6768这个数字其实只能涵盖大部分常用的汉字,不能覆盖所有的汉字,这就是为什么“朱镕基”的“镕”原先都打不出来的原因。OK,为什么当时(80年)创造的GB2312不能表示256×256个汉字呢?这个问题留给大家。 GB2312编码实际上与ISO8859是冲突的,所以,当时我们在中文环境下看西文的文章,经常会发现有乱码,会出现一些很古怪的汉字,原因就是一些西文的编码让中文的系统硬生生的翻译成了中文。另外,值得一提的是,GB2312-80还是不能收录所有的汉字,因此,1995年又出现了一个《汉字内码扩展规范》,收录了21886个符号和21003个汉字,即GBK编码。 ANSI我们再把视线拉回老外的世界,老外们也意识到传统的ASCII编码是不能解决问题的,所以创造出了一种ANSI编码,ANSI编码实际上是一种多字节字符串编码(Multi Byte Character Set),也就是每个字符用一个或者两个字节来表示,比如,"中文123" 在中文 Windows 95 内存中为7个字节,每个汉字占2个字节,每个英文和数字字符占1个字节: 要注意的是,这里的概念比较容易混淆,这个似乎要怪微软。在微软开发Windows 95时,为了以示区别(区别于Unicode字符串,我们下文会提到),便用ANSI来表示单字节字符编码,虽然实事上的ANSI表示多字节字符串,但是这种说法越来越广泛,便流行开了。因此我们在后文中也将尽量少用ANSI的概念,用单字节字符编码和多字节字符编码更加容易说清楚。 Unicode问题的根源在于存在这太多的编码表,并且这些编码表互相之间不兼容。要是世界上有一个统一的编码表就好了。于是,Unicode横空出世。 当时出现了两个针对全人类所有字符的编码字符集,一个是UCS(Universal Character Set),它是国际标准化组织弄出来的,也就是ISO/IEC 10646;另一个是Unicode(Universal Code),Unicode同时也是一个联盟的名字,也就是HP、Microsoft、IBM、Apple等几家知名的大型计算企业所组成的联盟集团,他们为了推进多种文种的统一编码而制定出了Unicode。 这两个组织同时干了同一件事,就是给全人类的字符进行编码,这可不是一件好事,想当年巴比伦塔就是因为上帝搞乱了俺们人类的语言才没有建成的,所以在91年的时候,国际标准化组织做了让步,将Unicode和UCS进行统一。所以,实际上,现在的情况是:全人类现有所有字符是Unicode的一个子集,而Unicode又是UCS的一个子集。 由于要表示的字符如此之多,所以一开始的 Unicode1.0编码就使用连续的两个字节也就是一个WORD 来表示编码,比如“汉”的UCS 编码就是 6C49。这样在 Unicode 的编码中就可以表示 256*256 = 65536 种符号了。直接使用一个WORD 相当于两个字节(byte)来保存编码可能是最为自然的 Unicode 编码的方式,这种方式被称为 UCS-2,也被称为 ISO 10646,,在这种编码中,每一个字符使用两个字节来进行表示,例如,“中” 使用 11598 来编码,而大写字母 A 仍然使用 65 表示,但它占用了两个字节,高位用 0 来进行补齐。由于每个WORD 表示一个字符,但是在不同的计算机上,实际上对 WORD 有两种不同的处理方式,高字节在前,或者低字节在前,为了在UCS-2编码的文档中,能够区分到底是高字节在前,还是低字节在前,使用 UCS-2 的文档使用了一组不可能在UCS-2种出现的组合来进行区分,通常情况下,低字节在前,高字节在后,通过在文档的开头增加 FFFE 来进行表示。高字节在前,低字节在后,称为大头在前,即Big Endian,使用 FFFE 来进行表示。这样,程序可以通过文档的前两个字节,立即判断出该文档是否高字节在前。 反之,则为Little Endian。 “endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。 Unicode有两套标准,一套叫UCS-2(Unicode-16),用2个字节为字符编码,另一套叫UCS-4(Unicode-32),用4个字节为字符编码。UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。 但是,这个世界不是理想的,不可能在一夜之间所有的系统都使用Unicode来处理字符,所以Unicode在诞生之日,就必须考虑一个严峻的问题:和ASCII字符集之间的不兼容问题。我们知道,ASCII字符是单个字节的,比如“A”的ASCII是65。而Unicode是双字节的,比如“A”的Unicode是0065,这就造成了一个非常大的问题:以前处理ASCII的那套机制不能被用来处理Unicode了。 另一个更加严重的问题是,C语言使用'\0'作为字符串结尾,而Unicode里恰恰有很多字符都有一个字节为0,这样一来,C语言的字符串函数将无法正常处理Unicode,除非把世界上所有用C写的程序以及他们所用的函数库全部换掉。 于是更伟大的UTF出现了。 UTF我们现在说的UTF就存在于我们的电脑中,当然UTF也属于Unicode的大范畴。UTF常见的有两种UTF-8和UTF-16,还有一些少见的,比如UTF-7。其中UTF-16和上面提到的Unicode本身的编码规范是一致的,因此可以想像,UTF-16是需要注意big-endian和little-endian的。而UTF-8不同,它定义了一种“区间规则”,这种规则可以和ASCII编码保持最大程度的兼容。下表为UCS-2到UTF-8的转换表: U+0000 – U+007F: 0xxxxxxx U+0080 – U+07FF: 110xxxxx 10xxxxxx U+0800 – U+FFFF: 1110xxxx 10xxxxxx 10xxxxxx U+10000 – U+10FFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 从这张表可以看出几点:1)UTF-8用1~4个字节表示一个字符,据称,目前还没有4个字节的UTF-8字符。2)UTF-8可以与ASCII兼容。 UTF-16:也就是我们通常所说的Unicode编码,它用2个字节或者4个字节来表示一个字符。 在讲UTF-16之前,先来看看他们的字符编码方案: 总体结构是一个四维的编码空间,先将所有的代码点分为128个三维组(group),每一组包含256个平面(plane),每一个平面包含256行(row),每一行包含256个字位(cell),又称谓“列”。其中group的值范围是从00到7F,plane、row、cell的值范围都是从00到FF全编码。整个编码字符集的每个字符都是由4个八位序列表示,按照组八位、面八位、行八位、列八位的顺序。 ISO/IEC 10646(UCS的字符编码方案)将其第一个平面(00组中的00平面)称作Basic Multilingual Plane(基本多文种平面),简称BMP。人类对BMP中的256×256=65536个字符的使用频率超过99.9%,这就使得人们对BMP格外青睐。ISO/IEC 10646规定BMP上的字符可以作为双八位编码字符集使用,即:在此平面上仅用行、列两个八位就可以表示一个编码字符。 按照行可以将BMP中的代码点分为下面四个区域:
国际标准化组织和Unicode的合并是把Unicode并为ISO10646的第一个字面BMP,也就是说Unicode中代码点在U+0000到U+FFFF间的字符和BMP中的完全一样。也就是说,BMP中的字符,在UFT-16中是用2个字节来表示的。 而Unicode的代码点范围是U+0000 至 U+10FFFF ,所以我们把Unicode代码点在 U+10000 至 U+10FFFF 范围之间的字符称为增补字符。但增补字符在Unicode编码(UFT-16)中怎么表示呢? 在BMP中定义了一个代理区(Surrogate Zone)(D800至DFFF),这个区域内没有定义任何的字符或符号。将这个区域平分为前后两个各容纳1024(1K)个编码的区域(D800-DBFF及DC00-DFFF),分别称作高半代理(high surrogate)及低半代理(low surrogate)区域。从这两个区域分别各取一个编码,由这两个编码组合成一个4 bytes代理对(surrogate pair)来表示一个编码字符,而且只有将这两个代理对(surrogate pair)结合在一起才能表示一个字符,单独使用其中的任何一个都没有意义。 高低半字符的编码位置各为1,024=4×256,因此UTF-16总计可提供(4×256)×(4×256)=16×65536个编码位置,亦即16个字面,也就是U+0000 至 U+10FFFF。对BMP而言,当然无需使用UTF-16转码,所以UTF-16的转码主要应用于ISO10646的第1~第14字面(第15字面为专用字面),也就是说只有第1~第14字面的字符才需要两个UTF-16编码来表示,即4个字节表示一个增补字符。 我们可以看到:UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于 0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF -16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。 31 August typename今天遇到一个typename的问题,花了半天才解决。 问题发生在C++模板类中,当使用STL时要特别注意,比如以下的代码: template <typename T> class MyClass{ typedef vector<int> myVect; myVect::iterator myIter; }; 这个时候就有问题了,myVect::iterator其实是有歧义的,某些编译器是要搞糊涂的。具体原因不讲了,在C++里面这样的定义叫做nested dependent name。当时我在windows下用vc编译是没有问题的,放到vxworks下面用gcc编译就不行了,当时就怀疑是不是这样的定义有二义性,后来请教了一些同事果然如此。 解决问题的方法,在前面加上typename关键字! 28 August Character Set Encoding Basics (3)花一些篇幅介绍一下各种字符集的历史,以抄袭为主。 第一个编码表 ASCII在最初的时候,美国人制定了第一张编码表《美国标准信息交换码》,简称 ASCII,它总共规定了128 个符号所对应的数字代号,使用了7位二进制的位来表示这些数字。其中包含了英文的大小写字母、数字、标点符号等常用的字符,数字代号从 0 至 127。谁叫计算机是美国人发明的呢?总要近水楼台一点。由于 ASCII 出现最早,因此各种编码实际上都受到了它的影响,并尽量与其相兼容。这时候的计算机也只支持英语,其它语言不能够在计算机上存储和显示。操作系统比如英文DOS。 扩展 ASCII 编码 ISO8859美国人顺利解决了字符的问题,可是欧洲的各个国家还没有,比如法语中就有许多英语中没有的字符,因此ASCII不能帮助欧洲人解决编码问题。为了解决这个问题,人们借鉴 ASCII 的设计思想,创造了许多使用 8 位二进制数来表示字符的扩充字符集,这样我们就可以使用256种数字代号了,表示更多的字符了。在这些字符集中,从 0 - 127 的代码与ASCII保持兼容,从128到255用于其它的字符和符号,由于有很多的语言,有着各自不同的字符,于是人们为不同的语言制定了大量不同的编码表,在这些码表中,从128-255表示各自不同的字符,其中,国际标准化组织的 ISO8859 标准得到了广泛的使用。在ISO8859的编码表中,编号0–127与ASCII保持兼容,编号128–159共32个编码保留给扩充定义的32个扩充控制码,160为空格,161-255的95个数字用于新增加的字符代码。编码的布局与 ASCII 的设计思想如出一辙,由于在一张码表中只能增加 95 种字符的代码,所以ISO8859实际上不是一张码表,而是一系列标准,包括14个字符码表。例如,西欧的常用字符就包含在ISO8859-1字符表中。在 ISO8859-7则包含了 ASCII 和现代希腊语字符。 单字节字符编码从最初的ASCII编码到后来的部分ISO8859(比如ISO8859-1),这些字符编码都属于“单字节字符编码(SBCS, Single Byte Character Set)”。当然,后面还会介绍“多字节字符编码”和“Unicode编码”等其他类别。这些概念很重要,本质上讲,字符编码转换的问题都出在这里。单字节字符编码,就是指用一个字节来存放一个字符编码,一个字节(byte)是8个bit,即8个二进制数,一般可以用两个16进制数来表示。例如,Bob123" 在内存中为: 问题出现了! 23 August Character Set Encoding Basics (2)今天首先回答ArrowPig的问题,也介绍一下这方面的背景。 1)为什么要做这方面的工作?TDMS(http://zone.ni.com/devzone/cda/tut/p/id/5696)是NI大为推广的一种文件格式,主要用于读写测试测量方面的数据。既然是文件格式,就不能不考虑写在文件中的内容是以什么格式编码(encoding)的。因此我需要了解这方面的知识。更细节的内容在blog里面不能多说了,呵呵。 2)数据库的编码问题。数据库与文件类似,也会遇到编码的问题。其实也比较容易理解,写入数据库的编码格式与读数据库的编码格式必须能互相转化,当然,如果读写的编码格式都一样那是更好。网页也是如此,写ASP网页时最好也需要指定相应的编码方式。 下面说说正题: 什么是编码(Encoding)?题外话:虽然我学密码学,但是这里谈的encoding和密码学中的encrypt没有太大关系。当然,密码学和这里所说的encoding也有异曲同工之妙,究其根源,两者其实是一回事,同属于一个老祖宗——信息学范畴。这里说的编码,我们其实每天都在用——中文汉字!所谓编码,就是将信息通过某种形式表达出来。我们的中文汉字,很多都是象形文字,这就是一种编码。 计算机中将文字表达出来也是用了同一种方式。计算机中显示的文字(不管中英文),其实很好理解,本质上也都是图形。整个过程就和中国发明的活字印刷一样,首先要把每个字符(charater)刻好,然后放在一个仓库里(我母亲工作的学校里有个校办印刷厂,我小时候就见过这样的仓库,和图书馆的layout差不多),印刷的时候把每个字符拿出来就行。这就遇到一个问题,这些字符放在仓库里必须是有序的存放,否则不好找,每个字符都应该有个序号,找起来就方便了。计算机系统中做了同样的事情,先建立每个字符的图形,然后为每个字符分配一个序号,这些序号的集合就是一个编码表,这一套编码的机制就叫做字符编码(Character Set Encoding)。计算机系统只能认识0和1,因此你写到文件中的字符,其实都是编码,最终还必须翻译成0和1,计算机才认识。读文件的时候,计算机先读出0和1,拿到这个编码之后再去找对应的图形,将文字显示出来。因此,可以理解,每一套字符编码都必须包含两部分内容,其一为一套character的图形,其二为编码规则,下图便是一例(图中的十进制数字在计算机系统中毫无疑问其实都是二进制的): 有了这些概念,我们可以给出以下定义: 字符(character):它是抽象的最小文本单位。字符就是对某种意义的图画表示,或者说形状表示。“A”是一个字符,“¥”也是一个字符。 字符集:字符的集合。比如汉字字符集,拉丁字符集,全人类所有的字符的集合。
编码字符集:也就是一个字符集的编码形式,它为每一个字符分配一个唯一数字。 注意,还有一个重要概念,下面会经常用到,就是“字节”,不可和“字符”混淆。 字节(byte):计算机中存储数据的单元,一个8位的二进制数,是一个很具体的存储空间。 Codepage(代码页):目前存在着各种各样的字符编码集,在没有包含全人类的字符编码集出来之前,各个国家和组织都是各编各的,也就出现了西欧语言(又叫Latin-1)编码字符集(ISO 8859-1),中文编码字符集(GB2312/BIG5/GBK/GB18030),日文编码字符集,CJK中日韩统一编码字符集等等。这些不同的编码字符集,就叫做Codepage,也就是编码字符集的代号。我为什么要提codepage呢?相信写过ASP程序的人都见过这样的代码:<%@LANGUAGE="VBSCRIPT" CODEPAGE="936"%>,以前不明白重要性,现在知识就串连起来了。原来,IIS服务器会将所有的ASP页面都存成unicode形式(这个还比较聪明),处理页面时,再按照给定的codepage进行转换。 22 August Character Set Encoding Basics (1)序言前段时间遇到的build LabVIEW的问题,归根结底是字符串编码的问题。最近又正好在做关于UTF-8、UTF-16以及MBCS之间的转换工作,于是仔细研究了一下。今天主要看了一些中文的资料,建立了一些基本概念,准备今后有时间再看一点英文文章,毕竟还是英文的比较有说服力。通过大半天的学习,我也已经基本了解了字符串编码的来龙去脉,所以想在blog里面稍作总结,做一个系列吧,随便写点。 其实字符串编码的问题基本上所有用过电脑的人多多少少都遇到过,先说说我遇到的一些情况:打开的网页全是乱码;用EditPlus编写的ASP程序放到网站上又是乱码,后来就不敢用EditPlus了;打游戏的时候,要用把系统设置成繁体中文,然后重启;从FTerm里面copy出来的字符串paste到notepad里面也有乱码等等。遇到了这么多问题了,作为一个靠计算机吃饭的张江男,还是有必要稍微仔细的研究一把。 20 August Building LabVIEW我在去Austin之前就一直build不了LabVIEW,老是报错,当时也没有在意。后来去Austin,在那边的机器build成功了,但是回来之后发现在自己的机器上还是不行。 情况确实比较复杂:1)Build的过程比较复杂,有很多步骤,我用了相同的步骤在Austin是成功的,后来到了上海我又按照相同的步骤做了一遍竟然不行。2)上海的同事的机器上build也是成功的,就是我的机器不行。这个问题困扰了我很久,如果问题不解决,将会影响到后续的工作。我还在公司的论坛上问过,并且给几个专家级的老外发过信,都没人知道。最后问了我的tech lead,他就问我到底是什么错误,我说一直报错14个compile error,他说哎呀,你就自己打开程序去改嘛! 我倒是没有想到这个办法,因为程序也是别人写的,别人都能build成功,为什么我不行呢?大家机器上的程序都是一样的,所以我不相信是程序中的问题。无奈之下我只好用VC打开工程,发现还确实是程序中的问题。一段程序原本应该是/*...*/的,到了我的机器上变成了/*...?+/,这样引起了语法错误,直接导致我的build失败。问题的原因可能在于系统语言的一些设置,某些英文字符到了我的机器上变成了乱码。 在佩服tech lead之余,不禁感概:这个问题至少花费了我两个礼拜的时间,为什么一开始后我没想到要跟进程序里面看看呢?还是因为自己的惰性!作为工程师,确实应该具备对任何问题都要探个究竟的冲动和能力。 05 February 推荐一下M$ Office OneNote 2003我平时有个习惯,喜欢搞个小草稿本,记录下来一些东西,主要是to do list之类的(比如我准备哪天买点水果什么的。。),或者一些随手写下来的东西,我一直在想,为什么没有这样的软件,最好还比较小巧灵活。恰好这个周末俩同学到我家来,一个intel小牛牛,一个cisco小牛牛,我发现他们都在使用MS的OneNote,后来自己也装了一下,感觉非常好。 一句话,就是随手记录信息。比如to do list;比如感想,我平时有点什么想法,都可以记录下来;还比如开会的meeting minutes,非常方便。基本上常驻内存(内存耗费应该不会太多),开机就有,随时打开就可以写东西。我举点例子,我现在记录的东西就包含:过年回家要带的东西和计划要做的事情;平时学英语看到的单词、短语、句子;我平时随时想到的一些东西(我总是想的比较多,比较悲观)等等。 这个玩艺儿貌似也是Office2003才开始推出的,有个SP1,也推荐装一下。虽然M$的东西我是越来越看不惯了,live */IE7/MSN,怎么就不能学学google呢,这么麻烦的东西谁还受得了?但是,OneNote除外,OneNote,配合个人笔记本,嘿,还真有点商务人士的感觉。 17 November 如何做记录?我这个人脑子不行,记性不好,年纪大了尤甚。很多重要的事情经常想不起来。现在越来越依赖电脑,好多事情都放在outlook的calendar和task里面。不过感觉还是不行。 首先,比如工作上。今天就又一次遇到了这样的问题。一个月前写的程序,准确地说应该是修改的程序,我当时肯定有理由将这段代码修改成这样,今天发现就是这个修改,引起了一个bug。去掉这个修改,一切OK,测试也通过了,没发现什么问题。但是我实在想不起来一个月前我为什么要作这样的修改,很苦恼。这样的问题已经遇到过无数次了。 这个问题,理论上主要通过程序注释来解决。好的代码风格应该注释比程序更多,当然我自己从来未曾做到过。但是对于LabVIEW程序,如果注释比程序还多,简直是没法看。另一个办法可能是用一些文档来记录。但是也有两个问题,比如,第一是习惯问题,写程序的时候只想把程序快点写完,哪有时间去写文档,其次,记录下来,具体用什么样的方法和形式我也没有一个好的idea。 其次,是生活上的。我经常感觉好像有什么事情没干,但是经常想不起来,最好有这样的工具或者办法能够记录。比如我过生日,远在苏州的哥们儿都能想起来给我发个消息什么的,但是他的生日,我无论如何都想不起来,这样让我颇觉不爽。这里面其实也蕴含了一些巨大的商机,social network,这样的web2.0内容,其实还是有生存的道理的,很多网站已经做了类似的工作。 在这里也算是请教各位看官大虾,看看大家有什么好的方法,不妨指点一二:) 19 October 《Ajax基础教程》 对于web开发技术,我向来保持着比较浓厚的兴趣。ASP有了两年以上的经验,还稍微看过点JSP/ASP.net之类的,基本上也全部忘光了。话说回来,Web技术应该是可以大有所为的,随着计算机处理能力以及网络速度的逐渐提高,Web应用会越来越多的取代桌面应用,最好的例子就是Google的spreadsheets和docs。最近正好看了这期的《程序员》杂志,对主流的几种web技术作了介绍和对比,又勾起了我更多的兴趣。 1)Ajax = Asynchronous, XMLHttpRequest, Javascript, CSS, DOM. Asynchronous JavaScript + XML。允许浏览器与服务器通信而无需刷新当前页面的技术。这个便是所谓的“异步”。异步通信是Ajax的本质所在。原先基本上所有的web技术都是所谓的“同步”、“交互式”的通信,也就是必须一问一答,客户端有一个请求,服务器端随即给出一个响应,之后,客户端才可以继续下一个请求。做过网页的人就知道,这个同步是有很多弊端的,客户端必须等到服务器端有响应之后才能做下一步事情。得到响应基本上就意味着一次刷新页面的过程,这个实在让人受不了,用户不会每次都有耐心等待。 Ajax技术也已经不算新了,很多地方都已经应用到了,比如gmail等等。对于Web技术的学习,我的计划是这样:ASP -> AJAX ->ASP .net -> Atlas。同时,有时间可以学习一点java/js/jsp之类的技术。 01 June 《Effective STL》最近一直在研究C++ STL,看了一下《effective STL》,感觉不错,不过比起《effective c++》和《More Effective C++》还有点距离。只是粗看了一遍,准备有时间再细读。
1)typedef是个好东西,尽量配合STL使用。必要的时候还可以把容器、迭代器放到class中,作为private成员。
2)拷贝对象是STL的方式,通过对象的拷贝构造函数和赋值符。耗费较大,所以可以建立对象指针的容器。但是指针的容器也有问题,比如delete方面的,所以更好的选择是建立智能指针的容器。 3)用emtpy()代替size()==0,empty()常数时间,size()对于某些list实现来说,是线性时间(考虑到splice()的实现)。 4)尽量使用区间函数,assign以及insert的区间表示方法等。copy经常被滥用,copy的区间形式其实实现中和显式的循环一样,性能不佳(比如元素的移动、内存的重新分配等)。区间函数基本上包括区间构造、区间插入、区间删除、区间赋值等。 5)插入迭代器,inserter/back_inserter/front_inserter。 6)如果容器容纳了new出来的指针,记得delete掉,其实最好用智能指针。 7)永不建立auto_ptr的容器,auto_ptr的一个特性是,拷贝一个auto_ptr,他所指向对象的所有权被转移到拷贝的auto_ptr,而被拷贝的auto_ptr被设为null。 8)STL线程不安全的,除非借助信号量、互斥体之类的东西。 9)动态数组没意思的,尽量用vector和string。 10)对vector和string,realloc会有很多的开销,并且,更为严重的是会让当前的iterator/pointer/ref全部失效,所以尽量使用reserve()来避免。注意以下函数的区别:size()/capacity()/resize()/reserve()。 11)有一些c遗留的api只接受指针参数,怎么传vector和string中的某个元素?vector可以用&v[0],string可以用s.c_str()。但是vector和string的数据最好都传给只读取而不修改的API,对string,完全是这样,对vector还有一点余地,但是绝对不能修改vector的个数这样的属性。此外,还可以利用vector和数组潜在的内存分布兼容性用C风格API初始化vector。对于其他的容器,可以让API将数据放入一个vector,然后再拷贝到容器中。 12)用交换技巧修整过剩容量。比如一个vector膨胀之后又删除了很多元素,这时候容量仍然较大,怎么改?用vector<Contestant>(contestants).swap(contestants)。vector<Contestant>(contestants)制造一个临时vector,但是仅含有contestants的有效元素,然后交换。 13)避免使用vector<bool>,不是一个真正的容器,每一个bit就放了一个bool,而不是每一个字节,所以&v[0]并不能返回一个bool*。 14)相等和等价的区别。相等,==,可以被重载。等价,!(a<b) && !(b<a),关联容器都是基于等价的。 15) 关联容器的less than注意相等的情况,对相等返回false。 16)有4种interator,包括const以及reverse的iterator,尽量用iterator,比如insert和erase的时候不能用const的。 17)不能用const_cast将const_iterator转换成iterator,因为这两者之间不存在什么关系,完全不同的类。 18)partial_sort(), nth_element(), stable_sort()。 19)remove()之后记得要erase()。remove()接受的iterator,并不能改变容器的size。remove完之后所有的元素往前移,但是残留下来的空间仍由原值填充。所以要v.erase(remove(v.begin(), v.end(), 99), v.end());但是这样erase-remove可能对指针的容器造成资源泄漏,要注意。 20)STL中使用仿函数,都是传值的。 21)标准库提供的函数适配器分为两类:(1)绑定器(binder),通过把二元函数对象的一个实参绑定到一个特殊的值上,将其转换为一元函数对象,有两种绑定器,bind1st和bind2nd,分别将值绑定到第一个参数和第二个参数上;如下代码: count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));(2)取反器(negator),将一个函数的值翻转的适配器,标准库提供了两个not1和not2。 补充:
1)注意写template的时候,typename和class关键字的区别,放入google notebook中了。 2)迭代器失效的问题。一般说来,在连续内存容器上插入和删除会使所有指向容器的迭代器、指针、引用失效,要想减少到最低程度,可以考虑基于节点的容器,list/map/set。 3)注意c++的fuax pas,默认会将所有东西都分析成函数声明。比如一个class A有默认ctor,然后你搞了个A a(),结果根本没有造出实例出来,vs2005会warning。
4)关于allocator。这是个奇怪的东西。有以下几点要注意:
分配器做成模板。
5)string的实现有很多种,比如有些用引用计数等等,注意不要被迷惑。 |
|
|