Debug 思考模式(1)—关注更改
程序员就像诗人,他的工作几乎全是纯思考 —弗里德里克·布鲁克斯
write by 九天雁翎 (JTianLing) – www.jtianling.com
讨论新闻组及文件
假如设计有模式,分析有模式,那么,程序员最希望有的自然是Debug的模式了。。。真希望有人已经总结出来了,以节省我们曾经浪费的大量时间。。。。可惜。。呵呵,好像没有看到,我碰到一些问题,顺便总结一下方法吧,当方法重复的被使用,那么也就成为一种Debug思考模式了。
前段时间为公司的TCP网络模块添加加密的功能,当时用的加密算法是从网上找的。。。为了安全,就不透露具体的算法了,算法的加密解密函数都写的很简单,就两个指针参数,由于仅仅是简单的算法实现,主要用于描述算法,没有配套的使用文档,也没有示例代码,一番折腾,调试很久,总算明白了2个参数的作用,不就一个传入需要加密解密的数值,一个传入自定义的key嘛。Debug通过后,直接编译了release版本给测试组测试,发现完全没有发挥作用。相当的郁闷,回来再调,还是正确的,但是编译成release版本后的确不行了。一般情况下碰到类似问题,不知道怎么的,都是第一反应没有初始化,查找外围代码老半天,并没有发现问题,由于Release下的调试功能比较弱,所以在算法加密解密函数调用前将内存中的值用日志的形式写了下来,发现加密本身就与debug下不一致,确定了问题改变的地点,肯定是加密算法的问题。仔细在release下调试加密算法,还好学会汇编,找到具体地点时release用汇编看就不那么吃力了,原来此算法的传入加密数值是64bit的,但是key却是用了128bit的-_-!在没有任何说明注释的情况下,仅仅以两个指针作为参数传入,不自己调试根本没有办法发现。。。。。唉。。。碰巧Debug能对,在于MS为所有的Debug未初始化变量赋值0xcc。。。也就是在Debug下,我的key指针传入实际相当于是以我自己的key加上两个连续的0xcc,无论加密解密的key都还是一样的,所以一切正常,当release时,越界访问的值属于随机状态,key不一致了,加密解密自然也就不肯能对了。。。。。
首先还是的说一点,好的接口是容易用对的,坏的接口设计(比如这种)是容易用错的。这是《Effective C++》一书中讲过的,但是看来还是很多人没有真正理解,我就是受害者之一。。。。到了这里,可能会有人批评我,说假如我具体的了解了算法和看清楚了算法的实现就不会出这样的错误了,但是我想说的是,好的接口给人使用,就是让人可以不了解实现的,甚至接口和实现都可以分离,使用接口的人根本就需不要关心实现,这样才能更加关注于自己需要关注的东西。何况,工作嘛,哪能真的用什么就理解透了才去做啊。。。。。。
我这里想说的是,我犯的最大的错误在于出现问题时,没有很快的定位到错误所在的地方,事实上TCP网络模块公司一直在使用,我仅仅添加了加密模块,然后出现问题,那么首先可以想到的自然是加密模块的问题,但是由于我平时对公司的TCP网络模块没有太深入的了解,仅仅是平时调试的时候偶尔跟进去看看,所以对其还有怀疑,然后出现错误,特别是这种debug没有错误,release错误时,一下子没有了方向,开始漫无目的的乱怀疑,其实根本没有必要,一开始就应该定位到错误所在。
这就是总监最后批评我没有使用排除法的地方,此处我应该将原来正确的代码排除掉。
这是我想提出的一种Debug思考模式:
假如在更改此模块之前其他模块都能正常工作,首先关注于这次更改的模块。
这种Debug模式其实真的能深入到思想中,其实作用很大,但是需要注意一些特殊的例子。比如有一次,我为公司的世界地图服务器添加GM模块,此时由于公司内网测试使用的GM指令与此非常类似,于是我主要通过此GM指令系统来搭建新的模块,一切都非常顺利,由于新模块建立在旧模块上,代码的编写速度也是非常快,但是到了最后,又出现了问题,程序一运行就崩溃。。。。这时又另外一个问题,加重了问题的严重性,我从程序一开始的地方到崩溃的地方下了很多断点,但是断点竟然都断不上,一般出现这种情况,属于库和头文件不统一造成,但是此次的调试仅仅在本模块中,没有进入其他的库,所以让人很郁闷,后来发现的问题在于原有的GM指令根本不需要了解是谁在执行,所以没有使用IPlayer的指针,但是我由于需要详细记录GM的每个动作,所以必须使用到此指针,使用的时候由于是复制代码,所以完全复制原有代码过来,也没有思考,原来的使用就是通过OnMessage的参数WPARAM强转过来的,但是原来并没有使用,我在后面将其传入另一个函数并使用了此指针,导致程序崩溃,按总监的话来说,由于IPlayer属于多重继承而来,不能再像普通类型一样用强转了,以前的代码是老代码,所以使用的并不正确,但是由于没有使用,所以没有问题。也就是说,我栽倒在了一次原来的老代码强转错误上。这里,原有模块同样没有出现问题,而我的程序出现了。区别在于原有代码强转了指针却没有使用,我使用了其强转,接下来还是用了指针。
这里需要提出的是:
原有程序没有出现问题,不代表代码没有问题,关注于更改,就要关注每个细微的更改。
write by 九天雁翎 (JTianLing) – www.jtianling.com
阅读全文....
看到Dreaming in code一书中提到这个词,才想起gmail中的字段的确切含义-_-!顺便在网上查了一下:
官方版本说,它是“Specially Processed Assorted Meat”特殊加工过的混和肉。这种SPAM肉有段时间非常普及,到了无处不在,令人讨厌的程度.
垃圾邮件之所以取名spam,在于互联网那一批先驱为其取名的时候,有一部很流行的剧集,名叫“Monty Python’s Flying Circus”,剧集中有个小餐厅,他们的菜单上只有“鸡蛋,香肠,spam,spam,spam,and spam”。。。。。。。。。呵呵
看到剧集名,是不是很眼熟啊。。。。。。。的确,Python语言的由来并不是因为Guido van Rossum觉得大蟒蛇很厉害,而且因为他如此的喜欢此剧集。。。。。。。所以如此命名Python语言
阅读全文....
假如说Debug是减少Bug的过程,那么编程就是制造Bug的过程
write by九天雁翎(JTianLing) – www.jtianling.com**
讨论新闻组及文件
有人说程序员工作中干的最多的事情不是编程,而是Debug,此言实在不虚。按我工作的经验来看,一般而言,设计占1/3,编码占1/3,Debug/测试占1/3。但是实际上。。。由于工作中慢慢发现问题,除了专门的Debug调试及测试时间,在后期产品维护的时候也需要很多实现来Debug,在初期往往是Debug的定位容易,修改也容易,到了中期要定位一个Bug就很难了,但是改起来却非常容易,到了后期假如还有Bug,那么常常会发现。。。要改动的几乎就是架构。
往往实际工作中的编程高手不是那种代码写的多么有技巧,架构多么完善灵活,程序效率多么高,真正让人佩服的就是一针见血,准确定位Bug的人。
程序员也算是一个常常以工作经验来衡量水平的群体,并且此工作经验往往是以工作年份来计算的,做为刚工作的新人,也许偶尔看到公司的老员工写出来的代码会不屑,觉得自己也能写出来,甚至写的更好,偶尔看到公司的程序框架觉得设计太烂,与自己学习的设计模式经验相差太远,但是真正碰到Bug的时候却会发现差距。。。。新手最容易说的话就是这个程序不是我写的,所以改这个Bug我得先熟悉一下代码,而真正的高手,拿到代码碰到Bug基本就能猜到是什么问题。。。。
今天碰到的事情让我对上述言论有了更加深刻的认识。
公司的游戏专卖店系统首先出现了上架的物品加载不上的问题,修复后,出现了物品与账户不同步的问题。按道理有上架物品就应该有账户,没有一个上架物品时删除此物品。当出现有上架物品没有玩家账户的时候,我一筹莫展,仅仅是反驳总监提出的上架时物品没有添加的问题。然后找到代码,明明白白指出,当物品上架时,的确是添加了账户。但是总监发现的确是正确添加账户后,马上查询了删除账户的情况,发现原来是因为以前物品没有加载上来,我判断没有上架物品,将其账户删除了。
这就是总监的Debug水平高于我的地方,尽管世界地图服务器的专卖店系统是我写的,但是,Bug确是他先定位到。一方面总监能够逆向的去思考Bug的起因,当账户和物品不统一的时候,添加账户也成功了,自然的就去看看删除账户的时候。并且还能够联想到刚才发生的物品加载错误的情况。这些真是经验,为了吃一堑,长一智,我决定都将其记录下来。
今天的第一智自然就是逆向的思考Bug的起因,当账户不存在的时候,添加页正确了,那么是不是删除错了呢?
write by九天雁翎(JTianLing) – www.jtianling.com
阅读全文....
Bug实际不一定出现在看起来出现的地方
write by 九天雁翎 (JTianLing) – www.jtianling.com
讨论新闻组及文件
作为初学者,一般都是看到Bug在哪发生的就只看到哪的代码,然后反复的想为什么会出,加日志吗,在Bug出现的前后两句加上日志,然后苦苦分析而得不出原因。当一个断言出现的时候更加是这种方法最派上用场的时候,因为Bug出在哪一行更明显了啊。
事实上,Bug却常常是因为其他原因引起的。最近的一个情况就是,文件打包工具在打包游戏数据时,总是有一个目录不能打进包中。我首先的方式是在Bug前后两句加上了日志,发现递归遍历文件和目录的时候并没有遍历到漏掉的那个目录。我是百思不得其解啊,不就是CFind的使用吗?MFC的东西不至于出现这样的错误吧?然后此问题在我的机器上又没有办法重现,所以我没有办法实际的调试代码。假如还是靠这样的思路几乎没有办法发现Bug了。
最后我回过头去,将日志添加了几个作用域,发现外面的遍历是正确的,再反过来看代码,发现原来是打包工具在定制哪个文件是确定打到那个包中时用的是平面结构,而测试组总监打包的时候用的是老的脚本,新的游戏数据添加了一个目录,打包工具的脚本中并不存在,在打包工具脚本遍历那一层就已经将那个目录丢掉了,所以里层的循环自然没有此目录,但是外层却能发现。。。这就是问题所在,假如我能早一点回过头去一层一层的看循环的条件,那么我可能很快就发现Bug了。。。。这也算是我不成熟的一个地方。虽然此例中有打包工具不是我做的,不是太熟悉的原因,但是不属于自己需要改Bug的代码不就是新手经验不足水平不够最最劣质的理由吗?(参考前一篇)
另外,这里举另外的例子,以前做文件打包系统的时候老是打包到一半出现ESP错误。。。。。。狂折腾代码,到了晚上十点总监亲自出马,全取代码重编,问题解决。。。。。。原来是编写工具的兄弟没有完全同步文件打包系统的库,头文件和lib不统一导致的问题。现在我是有经验了,碰到类似的问题,第一反应就是库没有统一。
还有一个例子,也是文件打包系统的问题,当时公司的游戏新加了声音,出现的问题是一旦声音打到我的包中,游戏运行会出现各种各样的错误,地图加载失败啊啥的,将声音从包中剥离出来,一点问题都没有。刚开始总监和我都怀疑是文件打包系统在频繁小批量读取时出现Bug,结果不是,然后只好跟代码,发现Bug时出时不出,这时总监和我的反应又都是哪个地方内存出现越界,导致这样莫名其妙的问题。但是最后狂看代码,折腾到十一点,突然总监一拍脑袋,不是吧,你的文件系统支不支持多线程啊?然后翻了翻声音系统的文档,明确说明,读取数据时需要线程安全。我彻底崩溃。。。。由于那个是我工作以来的第二个任务,我只需要关注,水平也就只够光注自己的的工作,根本就不知道声音那边是独立线程的。。。。一般而言,我们客户端是单线程的。。。。没有明确的要求,我怎么可能将文件系统做成线程安全啊?。。。。
第二天将文件系统做成线程安全以后。。。问题解决。。。这属于我最最刻骨铭心的Debug经历之一,还是那句话:Bug实际不一定出现在看起来出现的地方。
—题外话:由于文件系统中为了统计数据等多种功能,太多函数带有状态和使用了类成员(特别是list),所以改成线程安全后效率惨不忍睹,而又没有办法将声音的线程取消,至今我们公司的游戏,声音还是没有打入我写的文件打包格式中,独立于打包系统之外。。。
write by 九天雁翎 (JTianLing) – www.jtianling.com
阅读全文....
序列化支持(3)—Boost的序列化库的使用
write by九天雁翎(JTianLing) – www.jtianling.com**
讨论新闻组及文件
本来来说,Boost的文档属于开源库中最最详细的一列了,基本上跟着文档走就都能学会了,但是对于初学者来说可能有的地方太过简略,当然,对于熟悉boost的人来说那叫主题吐出,一针见血。我这里主要摘文档中的例子来讲讲,偶尔发表一下自己的见解,有的地方也跟进实现去看看。毕竟原有的例子仅仅是很简单的。这里自然还是推荐任何学习者都像我一样,调试其中的每一个例子,而不仅仅是看看而已。
关于Boost的编译和配置假如觉的麻烦,可以下一个自动下载安装的程序来完成,在windows下,从1.35开始我就一直使用此自动安装程序,安装和卸载非常方便,接下来需要做的就仅仅是简单的添加好工作路径就行了。
以下例子如无特别说明都来自于boost的文档。
例一:
// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <fstream>
// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
/////////////////////////////////////////////////////////////
// gps 座标
//
// 举例说明简单类型的序列化
//
class gps_position
{
private:
friend class boost::serialization::access;
// 如果类Archive 是一个输出存档,则操作符& 被定义为<<. 同样,如果类Archive
// 是一个输入存档,则操作符& 被定义为>>.
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & degrees;
ar & minutes;
ar & seconds;
}
int degrees;
int minutes;
float seconds;
public:
gps_position(){};
gps_position(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
};
int main() {
// 创建并打开一个输出用的字符存档
std::ofstream ofs("filename");
// 创建类实例
const gps_position g(35, 59, 24.567f);
// 保存数据到存档
{
boost::archive::text_oarchive oa(ofs);
// 将类实例写出到存档
oa << g;
// 在调用析构函数时将关闭存档和流
}
// ... 晚些时候,将类实例恢复到原来的状态
gps_position newg;
{
// 创建并打开一个输入用的存档
std::ifstream ifs("filename", std::ios::binary);
boost::archive::text_iarchive ia(ifs);
// 从存档中读取类的状态
ia >> newg;
// 在调用析构函数时将关闭存档和流
}
return 0;
}
首先,对于两个archive类我并不是很熟悉,这里摘其代码:
template<class Archive>
class interface_oarchive
{
protected:
interface_oarchive(){};
public:
/////////////////////////////////////////////////////////
// archive public interface
typedef mpl::bool_<false> is_loading;
typedef mpl::bool_<true> is_saving;
// return a pointer to the most derived class
Archive * This(){
return static_cast<Archive *>(this);
}
template<class T>
const basic_pointer_oserializer *
register_type(const T * = NULL){
const basic_pointer_oserializer & bpos =
boost::serialization::singleton<
pointer_oserializer<Archive, T>
>::get_const_instance();
this->This()->register_basic_serializer(bpos.get_basic_serializer());
return & bpos;
}
template<class T>
Archive & operator<<(T & t){
this->This()->save_override(t, 0);
return * this->This();
}
// the & operator
template<class T>
Archive & operator&(T & t){
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
return * this->This() << const_cast<const T &>(t);
#else
return * this->This() << t;
#endif
}
};
这个函数的实现很有意思,利用了模板参数,然后强转,最后通过子类一层一层的传递,导致这里强转实际得到的是最深层次子类的指针。技巧性非常强:)虽然我实际中从来没有用到过-_-!这里相当于父类在实现某些函数的时候,直接使用子类的函数实现。
Archive * This(){
return static_cast<Archive *>(this);
就例子中
oa « g;
一句,其中函数来回调用,从子类到父类再到子类再到父类….#@$#@%#@相当的扭曲,利用的就是上面的This指针函数形式,为什么用这么多的语句来实现本来一句简单的memcpy就能完成的功能,值得思考,也许要进一步对其整体的框架构造有所了解才行。但是这一点在文档中肯定是没有论述的,我们寻找答案的唯一方法就是源代码了。
先将其类互相之间的关系锊一下。
text_oarchive继承自参数化类text_oarchive_impl,主要功能全由其提供。
text_oarchive_impl参数化类继承自basic_text_oprimitive<std::ostream>与参数化类basic_text_oarchive。text_oarchive_impl自身实现的代码主要是对各个字符串(包括char,wchar,string,wstring)的序列化。
text_oarchive_impl又是从参数化类basic_text_oprimitive和basic_text_oarchive继承过来。这里最好是画个UML图那就清晰了。但是由于我是如此的懒,所以我没有画-_-!
其中basic_text_oprimitive的实现,告诉了我们,为什么需要This函数来调用子类的函数。
通过跟踪源代码的boost::archive::text_oarchive oa(ofs);
此句,会发现,最终ofs这个ofstream最终是传入到了这个参数化basic_text_oprimitive,并作为其引用成员变量os保存的,然后basic_text_oprimitive C++的basic类型的save函数重载,而save函数实际的实现又都是通过os的 «操作符来实现的。
这里最引人注目的就是&操作符的重载,使得使用起来非常方便,兼有使用Serialize函数和«,»操作符的两种方案的好处。
我刚开始工作的时候以为序列化最大的好处就是对类的类成员变量与普通变量都使用了统一的处理接口,这自然也是序列化所需要的基本功能之一。
可序列化的成员
一下例子从文档衍生而来,经过添加必要代码使其可以执行
// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <fstream>
// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
/////////////////////////////////////////////////////////////
// gps 座标
//
// 举例说明简单类型的序列化
//
class gps_position
{
private:
friend class boost::serialization::access;
// 如果类Archive 是一个输出存档,则操作符& 被定义为<<. 同样,如果类Archive
// 是一个输入存档,则操作符& 被定义为>>.
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & degrees;
ar & minutes;
ar & seconds;
}
int degrees;
int minutes;
float seconds;
public:
gps_position()
{
degrees = 0;
minutes = 0;
seconds = 0.0;
};
gps_position(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
};
class bus_stop
{
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & latitude;
ar & longitude;
}
gps_position latitude;
gps_position longitude;
public:
bus_stop(){ }
bus_stop(const gps_position & lat_, const gps_position & long_) :
latitude(lat_), longitude(long_){ }
virtual ~bus_stop(){ }
};
int main() {
// 创建并打开一个输出用的字符存档
std::ofstream ofs("busfile");
// 创建类实例
const gps_position latitude(1, 2, 3.3f);
const gps_position longitude(4, 5, 6.6f);
bus_stop stop(latitude, longitude);
// 保存数据到存档
{
boost::archive::text_oarchive oa(ofs);
// 将类实例写出到存档
oa << stop;
// 在调用析构函数时将关闭存档和流
}
// ... 晚些时候,将类实例恢复到原来的状态
bus_stop newstop;
{
// 创建并打开一个输入用的存档
std::ifstream ifs("busfile", std::ios::binary);
boost::archive::text_iarchive ia(ifs);
// 从存档中读取类的状态
ia >> newstop;
// 在调用析构函数时将关闭存档和流
}
return 0;
}
这样,对于gps_positon这样的类成员变量,由于为其写过序列化函数了,就可以直接将其序列化
在这里是用:
ar & latitude;
ar & longitude;
的形式,这就是我刚开始唯一知道的序列化的好处。
当然,对于派生的类,应该也能调用基类的序列化函数,这在C++中也应该属于序列化的基本功能。
见下例:
// BoostLearn.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <fstream>
// 包含以简单文本格式实现存档的头文件
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
/////////////////////////////////////////////////////////////
// gps 座标
//
// 举例说明简单类型的序列化
//
class gps_position
{
private:
friend class boost::serialization::access;
// 如果类Archive 是一个输出存档,则操作符& 被定义为<<. 同样,如果类Archive
// 是一个输入存档,则操作符& 被定义为>>.
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & degrees;
ar & minutes;
ar & seconds;
}
int degrees;
int minutes;
float seconds;
public:
gps_position()
{
degrees = 0;
minutes = 0;
seconds = 0.0;
};
gps_position(int d, int m, float s) :
degrees(d), minutes(m), seconds(s)
{}
};
class bus_stop
{
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & latitude;
ar & longitude;
}
gps_position latitude;
gps_position longitude;
public:
bus_stop(){ }
bus_stop(const gps_position & lat_, const gps_position & long_) :
latitude(lat_), longitude(long_){ }
virtual ~bus_stop(){ }
};
class bus_stop_corner : public bus_stop
{
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
// 序列化基类信息
ar & boost::serialization::base_object<bus_stop>(*this);
ar & street1;
ar & street2;
}
std::string street1;
std::string street2;
public:
bus_stop_corner(){}
bus_stop_corner(const gps_position & lat_, const gps_position & long_,
const std::string & s1_, const std::string & s2_
) :
bus_stop(lat_, long_), street1(s1_), street2(s2_)
{}
virtual std::string description() const
{
return street1 \+ " and " \+ street2;
}
};
int main() {
// 创建并打开一个输出用的字符存档
std::ofstream ofs("bus_corner");
// 创建类实例
const gps_position latitude(1, 2, 3.3f);
const gps_position longitude(4, 5, 6.6f);
bus_stop_corner stop_corner(latitude, longitude, "corn1", "corn2");
// 保存数据到存档
{
boost::archive::text_oarchive oa(ofs);
// 将类实例写出到存档
oa << stop_corner;
// 在调用析构函数时将关闭存档和流
}
// ... 晚些时候,将类实例恢复到原来的状态
bus_stop_corner new_stop_corner;
{
// 创建并打开一个输入用的存档
std::ifstream ifs("bus_corner", std::ios::binary);
boost::archive::text_iarchive ia(ifs);
// 从存档中读取类的状态
ia >> new_stop_corner;
// 在调用析构函数时将关闭存档和流
}
return 0;
}
可以尝试调试程序,这里为了简化代码,我没有将信息输出了。
至此,我们已经有了序列化需要的基本功能了,至于其他更多复杂的结构不过就是此种方式的组合而已。另外,此种使用方式已经超过了我公司的序列化类-_-!那个类实现是非常简单的,但是功能似乎也是如此的弱,但是这里的超过不过是通过&符号的重载来简化Serialize函数的调用而已,还算不上什么质的飞跃。下面看质的飞跃所在。
write by九天雁翎(JTianLing) – www.jtianling.com
阅读全文....
序列化支持(2)—Boost的序列化库
write by九天雁翎(JTianLing) – www.jtianling.com**
讨论新闻组及文件
这里找到了一些序列化的库,MFC的序列化库不能在Linux下用,剩下可以尝试的还有s11n,CommonC++,和boost的序列化库了。。。特别要提到的就是boost的库。。。。一个序列化的库也写了230多K,真是服了。。。。基本上我在公司写的序列化类也就一个文件一个类,也就1K,包括了所有的基本结构。。。很显然,boost的野心是很大的。。。出于对准标准库的尊敬。。。自然优先boost。特别要提到的是,Boost有所有我需要的东西-_-!并且我发现Boost的ASIO库就是我以前计划完成的目标。。。。。以后有特别有文章详细提到。。。
这里感谢为Boost库进行中文文档性工作的哥们,实在是感谢,虽然我常常以学英文为由去参看原文文档,甚至还阅读过基本原著,但是在文中引用的时候插入一篇的英文似乎是不太合适的,并且当我需要很快知道答案的时候,中文能够让我更快的处理,感谢你们。
以下引号中内容摘自Boost中文文档1.37.0版本:
这里有个对序列化比较正统的解释
“这里,我们用术语 “serialization序列化“ 来表示将任意一组C++数据结构解构为一串字节的、可逆的过程。这样的系统可用于在另一个程序上下文中重新构建一个等价的结构。根据不同的上下文,它可以用来实现对象持久化、远程参数传递或其它功能。在本系统中,我们使用术语 “archive存档“ 来指代这个字节流的特定表现。它可以是一个二进制数据文件、文本文件、XML或其它由本库的用户所创建的东西。”
注意,序列化的作用如我之前所述有:实现对象持久化、远程参数传递
我们对于这个系统的目标是:
- 代码的可移植性 - 只依赖于 ANSI C++ 所提供的功能。
- 代码的经济性 - 使用C++的一些特性,如 RTTI, 模板, 和多重继承等等,以使得代码更短也更易于使用。
- 各个类定义版本的无关性。即当一个类的定义更改时,旧文件仍可导入到新版本的类中。
- 深的指针保存和恢复。即指针的保存与恢复分别保存和恢复所指的数据。
- 对共享数据指针的正确恢复。
- STL容器及其它常用模板的序列化。
- 数据的可移植性 - 在一个平台上创建的字节流可以在另一平台上读出。
- 类的序列化与存档格式的正交性。即任何文件格式都可用于保存任意一组C++数据结构的序列化信息而无需调整。
- 非介入性。可以对不作更改的类进行序列化。即不要求进行序列化的类派生自某个特定基类或者实现特定的成员函数。这一点对于要将序列化应用于某些我们不能或不愿修改的类库中的类来说是十分必要的。
- archive 的接口必须足够简单,以易于创建一种新的存档类型。
- archive 的接口又必须足够丰富,才可以创建出象XML这样风格的存档。
野心够大,其描述的第1,7点很符合我的需要,第6点是属于额外的好处。
我们公司的序列化方式是,对于已实现序列化的结构使用其结构的Serialize方法,对于基本结构使用序列化类的Serialize,因为序列化类已经重载了基本结构的的Serialize实现。我曾经对比过这种方法和MFC的序列化方式,还有stream的使用方式,我感觉假如都使用stream那样重载«,»操作符的方式是最能节省击键数的,并且也足够的形象,并且个人认为,在某种程序上来说,stringstream就可以做一个简单的Archive实现来用。但是这里有一个比较不方便的就是,都需要先判断是存储还是读取,然后分别调用操作符,这里就相当于多了一倍的工作量,而公司的方式(统一到Serialize函数),虽然多了函数名的输入,但是对于输入还是输出可以不关心,这是其很大的优势。当时一下子还分不清孰优孰劣,但是出于亲近C++ 流实现的方式。碰到Boost的重载&操作符方式一下子傻了。。。。呵呵,听了太多的教条,太多的教导告诉我们不要重载操作符进行莫名奇妙的操作,除了真正用到其操作符原始涵义的时候,我还真从来没有去重载过他们,这里算是见识到了,虽然是重载了与操作符进行序列化运算,有点扭曲,但是,一切是简单就好,下面的示例你可以看到,这样兼有我上面所述的两种方案的优点。
回想起工作当中用了多少CTRL-C,CTRL-V(yy,p)去实现该死的一行又一行的序列化啊,在我们公司吓死人的物品扩展属性中,有60个以上的字段。。。-_-!也记不清多少次添加成员变量却忘写序列化函数导致的bug,虽然这样的bug都都容易找,但是都出在与同事联合调试的时候,还是浪费了很多时间。Boost的这种方案仅仅也是简化了实现,并没有办法根除这一点,还是必须说明。虽然其提供了不介入的方式来完成序列化,但是本质上序列化函数还是需要自己来写,以后要是有种方式能够为用户新添的任何结构或类进行序列化,而不需要用户进行额外的工作时,那才是完美的序列化方案。。。。毕竟,越少的工作,出错的机会也就越少。
现在想起来,重复工作最多的几个地方在哪?其一,结构的拷贝构造函数和=操作符的重载,为了安全,公司都是一个字段一个字段的写,累死人,也容易错。其二,就是一个字段一个字段的写序列化了。其三,目前还没有解决办法,数据库的操作,无论是MySQL的C API还是ODBC,都是有多少个字段(呵呵,多少个“?”)就得bind多少个结构的数据。。。。也是累死人。。。想想我的两个记日志工作就心寒(其一就是日志服务器,另外还有监控中心对整个服务器运行情况的日志记录),那个重复工作的啊。。。。还好有vim:)以前还特意讲过怎么用vim来简化我的工作,没有vim我都吐血了。最好用的就是q的记录功能和ctrl+a的联合使用了(可惜Viemu不支持ctrl+a)
本来来说,Boost的文档属于开源库中最最详细的一列了,基本上跟着文档走就都能学会了,但是对于初学者来说可能有的地方太过简略,当然,对于熟悉boost的人来说那叫主题吐出,一针见血。我这里主要摘文档中的例子来讲讲,偶尔发表一下自己的见解,有的地方也跟进实现去看看。毕竟原有的例子仅仅是很简单的。这里自然还是推荐任何学习者都像我一样,调试其中的每一个例子,而不仅仅是看看而已。
write by九天雁翎(JTianLing) – www.jtianling.com
阅读全文....
write by 九天雁翎 (JTianLing) – www.jtianling.com
讨论新闻组及文件
没有工作前,不知道序列化的作用。。。。虽然那时候学习《Programming with MFC》的时候,也知道CArchive,CObject等东西,但是没有太意识到其作用。但是,如前所述,我工作的第一件事情,就是了解公司的序列化类。从当时的一无所知,到现在也慢慢理解了一些序列化的作用了。说起来,自从工作以来因为当时做文件系统后后来的程序补丁,我应经在公司的序列化类以外额外实现了两个独立的序列化类了,分别是文件序列化和内存序列化。
这里也顺便讲讲序列化吧,虽然不算太难,也不算太复杂,但是这样越是基础的东西越是支持着程序:)没有还真不行。
序列化的作用包括,实现操作系统,硬件平台无关的数据保存和传输。从网络传输角度来讲解决诸如大头,小头等问题。目前我自己的感受还有,对于保存数据和读取数据使用同一套接口,简化了数据结构的管理,方便使用。虽然,序列化的作用一般是用来做永久保存的,在《深入浅出MFC》中,侯捷就将其中的序列化技术讲解称为“VC++六大关键技术”,事实上,序列化的作用不仅仅如此,比如我以前不就写过一个内存序列化类吧:)公司还有一个网络序列化类,即便在MFC中,也有可以序列化的CMemFile,事实上,序列化还是一种二进制的交流方式。:)
最典型的网络程序,就更好理解了,网络上交流的其实都是二进制的数据,从一端到另外一端,大家要有统一的格式支持才能和谐-_-!不仅仅是字符串的编码需要一致,字节的顺序需要一致,每个位置是什么值自然也需要一一指定好,一般而言,对于网络程序,在我们公司,对于序列化成一个包,我们叫打包,从一个网络包反序列化(似乎也有人叫串行化),我们叫解包。
这种二进制交流的方式不仅限于网络程序,任何程序间的交流都有可能用到,我碰到另外一个典型情况是一个Python脚本希望将一些数据经过一段C++程序,然后再放到另外一段Python脚本中去,这时当然可以通过Python的C API将所有的数据都一个一个转成C变量保存下来然后传出去,由C++程序运行时保存,然后再通过Python的C API一一转成Python的变量,这样做有很多不好的地方,其一是Python脚本间传递的数据对于C++程序来说不透明,任何改变都需要C++程序进行相应的更改,另外是每个变量都通过Python的C API去转换效率不高。其实这里就可以利用序列化成二进制数据来传输,其上两个缺点都会消失。顺便提一下,在Python中可以通过struct库的pack,unpack函数进行简单数据的序列化,通过marshal库来进行复杂对象的序列化(可是这个库好像已经不被推荐,但是我当时在实际工作中使用的就是这个),有更先进的pickle(有更快的C语言版本cPickle),shelve可用。从这点来看,又会感叹Python的库实在是太多,往往不是没有你想要的库,而是由于库太多,你不知道哪个好用-_-!
write by 九天雁翎 (JTianLing) – www.jtianling.com
阅读全文....
UCS-2与UTF8之间的选择(5)--断然决定UTF-8
write by九天雁翎(JTianLing) – www.jtianling.com**
讨论新闻组及文件
想来想去,在Unicode这种问题上纠缠过多似乎意义不大,就如我以前说的,我认为将来很自然的不会再需要考虑这个问题,因为,未来总会统一到UTF-32上去,那时候我现在考虑的这些东西都是完全没有意义-_-!在一个不久的将来就会完全没有意义的事情上考虑来考虑去似乎太没有抓住主要问题……
最后我的决定是使用UTF-8,这是个背Windows的选择,但是是个亲开源的选择,亲近世界的选择……即便到了现在,世界上还是有很多程序员不使用Unicode,即便是在Windows下编程。
举个让人信服的例子就是breakpad(google-_breakpad_ - Google Code),这个程序对错误的处理技术可谓手段用尽,为了在程序出错下不破坏现场的手段现在还感叹精巧,不能说开发人员没有水平,但是,在Unicode模式下编译就会有严重的问题(我去年刚开始的第一个工作就是这个,现在不知道修复了没有)。只能感叹英语的强势,无论是在文化领域还是在编程领域都是一样。。。。。对于更多的开源项目,可以想象情况怎么样。。。。。
另外,即便是一般的开源项目考虑了国际化,选择了Unicode来进行开发,一般而言,就目前的情况来看,似乎也是UTF-8居多,因为以前谈到的那么多的UTF-8的优点,其中最最符合欧美人习惯的一点就是完全兼容ASCII。。。。作为以自己语言开发的项目,移植到UTF-8一般而言比移植到UCS-2要更加方便,再加上开源世界的主导GNU/Linux的核心Linux的核心(好像有点绕)就是UTF-8的,所以,开源软件一般使用UTF-8也就很好理解了。就个人的开发学习而言,肯定会用到很多开源软件,或者参考,或者直接复用,当我的开发也是以UTF-8为基础的话,可以省事很多。举个简单的例子就是,CEGUI就是UTF-8的,这点我甚至看到Windows下的开发人员骂过,但是,这就是世界的现实。另外,就我使用的经验而言,MySQL也是对UTF-8的支持更加好,虽然其支持UCS-2编码的字符串,但是在命令行控制时无法正确显示,MySQL的著名前端工具,PhpAdmin也是对UTF-8支持良好,一样也无法显示UCS-2编码的字符串。(这些都是基于以前工作中的经验,现在不一定准确)作为个人开发。。。。我好像最可能使用的数据库也就是MySQL了。。。SQL Server我用过,的确是对UCS-2支持的更好,但是即便实在要用,还是可以现转成UCS-2的。Oracle没有用过,我不做评论。
另外,本人在公司常常是在VS2005下做Windows的开发,虽然也有Linux的工作,但是一般都仅仅是将Windows下开发好的服务器移植到Linux下的工作,其实对Linux下的学习不够深入,对Linux下的服务器开发学习也不够深入,而且以前说过,公司为了更加满足Windows下的客户端的要求,所以公司都是使用UCS-2作为编码,即便是Linux下的服务器都是强制使用了2字节的wchar_t来适应这种选择,也逼迫公司需要重编译所有其想要在Linux下使用的库。。。因为也需要这些库使用2字节的wchar_t,其实代价也挺大的。。。。而且作为学习,要是老是和工作中做一样的事情又怎么能更多的学到新东西呢,又何谓工作外的学习。。。。。再加上目前个人对Linux有很大的兴趣,希望多学点东西,这也是原因之一,虽然我的目标是可移植,但是感觉以后可能还是以在Linux下跑服务器为主,那么简化Linux下的开发,并优化速度,也算是理由之一吧。
再说,习惯了VS的我一直希望能将自己搬迁到Eclipse这样的多功能开放平台上,(虽然VS好像也可以作为开放式平台,但是相对来说插件的选择和质量实在和Eclipse不是一个量级的),虽然我目前没有学习JAVA,但是Eclipse也是我感觉非常合适的开发平台,不仅仅当我想使用C时,使用C++时,当我想使用Python,Lua时,这一样也是个很好的平台,而且,最重要的是,Linux下也能使用,在Linux下还能用其来开发bash脚本:)这一点是VS怎么也不可能达到的。。。。(我不期望MS将来开发Linux下的VS),另外,Eclipse对UTF-8的支持很好,而且也有vi的插件可用(Eclipse下的vi插件没有VS下的这么强大,支持的功能比较弱。。。这点比较遗憾。。。。),何况,假如哪天我希望学习JAVA。。。那么连IDE学习的时间都省了。。。。对于开发工具。。。实在可以有很多话说,越是学的语言更多,越是觉得为每一种语言去学习一个开发平台是不值得的,假如有一个比较统一的平台,那样可以节省很多时间,目前来说,最最能够胜任这个目标的就是Eclipse了,感谢IBM。。。。。目前我的暂时替代品就是VIM…文本编辑之王,写任何程序都感觉非常爽,就是配置起来有点麻烦,除了编辑以外的事情处理起来效率稍微低了点,调试更是弱项。。。。虽然其一向贯彻着一个程序只做好一件事情的原则,让其编辑能力无可匹敌(真的好用),但是当我需要更多丰富功能的时候。。。。实在有点麻烦。。。呵呵,我前段时间学习的时候,基本都是用vim编辑,用gdb/pydb/bashdb调试(没有找到好用的LUA调试工具,这也是我最近很少用LUA的原因,甚至Eclipse的LUA开发插件都不支持调试-_-!)对了,还有汇编,VS2005即便加上VA,汇编程序的开发也没有任何帮助。。。。这在我前段时间学习反汇编和内嵌汇编的时候感受很深,希望Eclipse不也要这样。。。。呵呵,也许额外还要多说一下Eclipse的是,多多使用GCC对于简化移植会有更多帮助,唯一的麻烦可能就是很多人可能会排斥不能用VS编译的程序-_-!这就是MS垄断的影响力所在。
似乎好像谈多了Linux就自然会讲到很多MS的坏话-_-!这几乎成了Linux社团的特点。。。。其实我并不讨厌Windows,用某人的话来说,即便某天Windows不再垄断,仅仅占有30%的市场份额,为Windows做开发的程序员仍然有广阔的空间。。。。何况现在呢-_-!我仅仅是刚开始学编程,希望多接触到一些思想,另外为了学习编程,需要看更多好的优秀的程序,而那些开源的优秀的程序往往是运行在Linux下的,Windows下的优秀程序虽然很多,但是苦于人家不让我看源码啊。。。。-_-!
唯一还需要看的问题是Python的支持,要是Python仅仅支持UCS-2,那么我还是会改变我的决定。可惜:)Python的默认Unicode实现就是UTF-8编码的,更为强大的是,Python的正则表达式库还对其UTF-8有直接支持,太强大了,这一直被视为UTF-8编码的弱项,那么,还缺少什么?万事俱备,连东风都不欠了。
对了,作为我关注的语言之一,LUA直到5.1版本都没有对Unicode有直接的支持。。。这点比较遗憾。至于Bash…我要他支持Unicode干什么-_-!
当仅在windows来进行编码转换时,就可以利用Windows的编码转换API,WideCharToMultiByte和MultiByteToWideChar函数对,还算是比较好用,起码比Unicode组织提供的好用的多。其实还有C 运行库mbtowc, wctomb可以用,这两个函数在Linux下也有实现。那么,以后就不在这个问题上再进行更多的纠缠了,UTF-8吧:)
write by九天雁翎(JTianLing) – www.jtianling.com
阅读全文....
UCS-2与UTF8之间的选择(4)–linux中各编码字符串的C/C++输出支持及方式比较
write by九天雁翎(JTianLing) – www.jtianling.com**
讨论新闻组及文件
继续研究UTF8和UCS-2的选择,这里继续使用上一次提到的函数。
鉴于大家不一定能找到下载的地址,而源文件是允许自由散发的,我将代码打包,提供给大家下载,下载地址还是在讨论新闻组及文件 中,名字为unicodeorg.rar,
昨天看了下Windows下的方法,这次研究Linux下的:
1 #include <stdio.h>
2 #include <locale.h>
3 #include <stdlib.h>
4 #include "ConvertUTF.h"
5
6 **int** main(**int** argc, **char** * argv[])
7 {
8
9 ConversionResult result = sourceIllegal;
10 UTF16 utf16_buf[3] = {0};
11 utf16_buf[0] = 0x4e2d;
12 utf16_buf[1] = 0x6587;
13 utf16_buf[2] = 0;
14 UTF16 *utf16Start = utf16_buf;
15 UTF8 utf8_buf[12] = {0};
16 UTF8* utf8Start = utf8_buf;
17
18 // If you want to test next line, you can't get anything but ???
19 // wprintf("%s/n", utf16_buf);
20
21 result = ConvertUTF16toUTF8((**const** UTF16 **) &utf16Start, &(utf16_buf[3]), &utf8Start, &(utf8_buf[12]), strictConversion);
22 **switch** (result) {
23 **default** : fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
24 **case** conversionOK: **break** ;
25 **case** sourceExhausted: printf("sourceExhausted/t"); exit(0);
26 **case** targetExhausted: printf("targetExhausted/t"); exit(0);
27 **case** sourceIllegal: printf("sourceIllegal/t"); exit(0);
28 }
29
30 **int** i = 0;
31 **for**(; i < 12; ++i)
32 {
33 printf("%x ", utf8_buf[i]);
34 }
35
36 printf("/n");
37
38 printf("%s/n", (**char** *)utf8_buf);
39 bzero(utf16_buf, **sizeof**(utf16_buf));
40
41 UTF8* utf8End = utf8Start;
42 utf8Start = utf8_buf;
43 utf16Start = utf16_buf;
44
45 result = ConvertUTF8toUTF16((**const** UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[3]), strictConversion);
46 **switch** (result) {
47 **default** : fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
48 **case** conversionOK: **break** ;
49 **case** sourceExhausted: printf("sourceExhausted/t"); exit(0);
50 **case** targetExhausted: printf("targetExhausted/t"); exit(0);
51 **case** sourceIllegal: printf("sourceIllegal/t"); exit(0);
52 }
53
54 // If you want to test next line, you can't get anything
55 wprintf("%s/n", utf16_buf);
56
57 **return** 0;
58 }
59
运行结果:
e4 b8 ad e6 96 87 0 0 0 0 0 0
中文
这里和Windows中不同的就是输出UTF-8的字符串在Linux下甚至不需要通过setlocale设置环境变量,这样对于C++的输出估计还是有好处的:),起码不会去影响到C++的正常输出。但是,对于宽字节的输出没有办法成功,就算你像在Windows中设置locale也没有用,道理也很简单,因为Linux下的locale我就是设置成UTF-8的-_-!
1 #include <stdio.h>
2 #include <locale.h>
3 #include <stdlib.h>
4 #include <iostream>
5 #include "ConvertUTF.h"
6 **using** **namespace** std;
7
8 **int** main(**int** argc, **char** * argv[])
9 {
10 ConversionResult result = sourceIllegal;
11 UTF16 utf16_buf[3] = {0};
12 utf16_buf[0] = 0x4e2d;
13 utf16_buf[1] = 0x6587;
14 utf16_buf[2] = 0;
15 UTF16 *utf16Start = utf16_buf;
16 UTF8 utf8_buf[12] = {0};
17 UTF8* utf8Start = utf8_buf;
18
19 result = ConvertUTF16toUTF8((**const** UTF16 **) &utf16Start, &(utf16_buf[3]), &utf8Start, &(utf8_buf[12]), strictConversion);
20 **switch** (result) {
21 **default** : fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
22 **case** conversionOK: **break** ;
23 **case** sourceExhausted: printf("sourceExhausted/t"); exit(0);
24 **case** targetExhausted: printf("targetExhausted/t"); exit(0);
25 **case** sourceIllegal: printf("sourceIllegal/t"); exit(0);
26 }
27
28 **int** i = 0;
29 **for**(; i < 12; ++i)
30 {
31 printf("%x ", utf8_buf[i]);
32 }
33
34 printf("/n");
35
36 cout << (**char** *)utf8_buf <<endl;
37 bzero(utf16_buf, **sizeof**(utf16_buf));
38
39 UTF8* utf8End = utf8Start;
40 utf8Start = utf8_buf;
41 utf16Start = utf16_buf;
42
43 result = ConvertUTF8toUTF16((**const** UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[3]), strictConversion);
44 **switch** (result) {
45 **default** : fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
46 **case** conversionOK: **break** ;
47 **case** sourceExhausted: printf("sourceExhausted/t"); exit(0);
48 **case** targetExhausted: printf("targetExhausted/t"); exit(0);
49 **case** sourceIllegal: printf("sourceIllegal/t"); exit(0);
50 }
51
52 **return** 0;
53 }
54
直接可以获得
e4 b8 ad e6 96 87 0 0 0 0 0 0
中文
的输出,也不需要调用C++的locale(imbue)函数去改变流的状态,甚至可以说,使用了UTF-8不仅仅是对ASCII完全的兼容了,对于新添加的字符也是可以不改变任何代码就做到兼容的,这点给我印象深刻。难怪UTF-8虽然是变长的编码方式,但是还是获得了这么广范围的应用。
这样的情况下,在Linux下同时在C 和 C++中使用UTF-8输出中文也不会有任何冲突,这个优势比在Windows下大多了。
另外,有个很重要的问题需要提及的就是,目前我使用的Ubuntu8.04桌面版,不支持全Unicode的编码,似乎中文是没有什么问题,但是以前那几个太玄经的字符是输出不了的,可能其Unicode的支持只到到BMP这一层,这点比Windows稍逊色。以前测试过,的确是输出不了,但是源代码已经被覆盖了,大家可以自己去确定一下:)
write by九天雁翎(JTianLing) – www.jtianling.com
阅读全文....
UCS-2与UTF8之间的选择(3)--windows与linux中各编码字符串的C/C++输出支持及方式比较
write by九天雁翎(JTianLing) – www.jtianling.com**
讨论新闻组及文件
继续研究UTF8和UCS-2的选择,这里继续使用上一次提到的函数。
鉴于大家不一定能找到下载的地址,而源文件是允许自由散发的,我将代码打包,提供给大家下载,下载地址还是在讨论新闻组及文件 中,名字为unicodeorg.rar,
另外,在wiki上看到了一个对UTF8的评价:
优点及缺点
关于字符串长度的一个注解:
总体来说,在Unicode字符串中不可能由码点数量决定显示它所需要的长度,或者显示字符串之后在文本缓冲区中光标应该放置的位置;组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。
所以尽管在UTF-8字符串中字元数量与码点数量的关系比UTF-32更为复杂,在实际中很少会遇到有不同的情形。
- 总体
- 优点
- UTF-8是ASCII的一个超集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或很少修改就能与UTF-8一起使用。
- 使用标准的面向字节的排序例程对UTF-8排序将产生与基于Unicode代码点排序相同的结果。(尽管这只有有限的有用性,因为在任何特定语言或文化下都不太可能有仍可接受的文字排列顺序。)
- UTF-8和UTF-16都是可扩展标记语言文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。[2]
- 任何面向字节的字符串搜索算法都可以用于UTF-8的数据(只要输入仅由完整的UTF-8字符组成)。但是,对于包含字符记数的正则表达式或其它结构必须小心。
- UTF-8字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低,并随字符串长度增 长而减小。举例说,字元值C0,C1,F5至FF从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值(可以查看W3 FAQ: Multilingual Forms上的验证UTF-8字符串的正则表达式)。
- 缺点
- 一份写得很差(并且与当前标准的版本不兼容)的UTF-8解析器可能会接受一些不同的伪UTF-8表示并将它们转换到相同的Unicode输出上。这为设计用于处理八位表示的校验例程提供了一种遗漏信息的方式。
--《UTF-8》
其中还有:
UTF-8编码的缺点
不利于正则表达式检索
正则表达式可以进行很多英文高级的模糊检索。例如,[a-h]表示a到h间所有字母。
同样GBK编码的中文也可以这样利用正则表达式,比如在只知道一个字的读音而不知道怎么写的情况下,也可用正则表达式检索,因为GBK编码是按读音排序的。只是UTF-8不是按读音排序的,所以会对正则表达式检索造成不利影响。但是这种使用方式并未考虑中文中的破音字,因此影响不大。Unicode是按部首排序的,因此在只知道一个字的部首而不知道如何发音的情况下,UTF-8 可用正则表达式检索而GBK不行。
其他
与其他 Unicode 编码相比,特别是UTF-16,在 UTF-8 中 ASCII 字元占用的空间只有一半,可是在一些字元的 UTF-8 编码占用的空间就要多出,特别是中文、日文和韩文(CJK)这样的象形文字,所以具体因素因文档而异,但不论哪种情况,差别都不可能很明显。
呵呵,自从可以访问WiKi以后,基本成了一些名字解释等信息的重要来源了。。。。希望以后可以一直访问到。(个人想法)
另外,对于服务器来讲,调试信息的主要出口在两个地方,一个是记录日志文件,一个就是控制台输出了,按我们总监的话来说@#¥@#……¥#算了,不说了,反正总监的大意就是服务器就是看着一屏屏的文字,没有写客户端代码那样有成就感,能够即时的显示出来,但是,别小看这些文字,很多bug调试的时候不一定能出来,偶尔出现一次,基本得靠日志分析,而控制台的输出主要目的可能就是即时的对服务器运行状况有所了解(其实真正后期运行个人感觉并不是太主要),主要的还是以文件形式记录的日志,而且控制台的输出对于效率的影响过大。(其实所有的I/O操作都是相对来说很耗时间的)
正在世界地图服务器,这里讲一个插曲,我们公司的服务器日志目前全部是英文…………….呵呵,原因是Linux下的中文输出有问题-_-!当然有问题,公司为了保证游戏客户端的效率,所有的字符处理全部是以UCS-2编码的两字节wchar_t来表示,不经过任何转换能显示出来才怪了-_-!。。。。。
首先是控制台的输出,Windows下:
一般来说,用VS2005中用wchar_t来表示UTF16中文就比较合适了,因为目前VS2005中wchar_t默认就是两字节的,下面是演示过程的源代码:
// Unicode.cpp : 定义控制台应用程序的入口点。
//
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <locale.h>
#include "ConvertUTF.h"
int _tmain(int argc, _TCHAR* argv[])
{
setlocale(LC_ALL, "");
wprintf(L"windows控制台在C语言中输出UCS-2宽字节中文\-----begin-----/n");
ConversionResult result = sourceIllegal;
wchar_t lwcBuf[3] = L"中文";
UTF16 utf16_buf[3] = {0};
utf16_buf[0] = 0x4e2d;
utf16_buf[1] = 0x6587;
utf16_buf[2] = 0;
UTF16 *utf16Start = utf16_buf;
UTF8 utf8_buf[12] = {0};
UTF8* utf8Start = utf8_buf;
wprintf(L"%s/n", (wchar_t*)utf16_buf);
wprintf(L"%s/n", lwcBuf);
result = ConvertUTF16toUTF8((const UTF16 **) &utf16Start, &(utf16_buf[3]), &utf8Start, &(utf8_buf[12]), strictConversion);
switch (result) {
default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
case conversionOK: break;
case sourceExhausted: printf("sourceExhausted/t"); exit(0);
case targetExhausted: printf("targetExhausted/t"); exit(0);
case sourceIllegal: printf("sourceIllegal/t"); exit(0);
}
printf("is leagal UTF8: %d/n", isLegalUTF8Sequence(utf8_buf, utf8Start));
printf("%s/n", (char*)utf8_buf);
// 清空缓存,以确定以后的值的确是转换得来
ZeroMemory(utf16_buf, sizeof(utf16_buf));
// 由于转换中利用了这两个start,所以需要重新为start定位,并且保存住End值
UTF8* utf8End = utf8Start;
utf8Start = utf8_buf;
utf16Start = utf16_buf;
result = ConvertUTF8toUTF16((const UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[3]), strictConversion);
switch (result) {
default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
case conversionOK: break;
case sourceExhausted: printf("sourceExhausted/t"); exit(0);
case targetExhausted: printf("targetExhausted/t"); exit(0);
case sourceIllegal: printf("sourceIllegal/t"); exit(0);
}
wprintf(L"%s/n", (wchar_t*)utf16_buf);
wprintf(L"windows控制台在C语言中输出UCS-2宽字节中文\-----end-----/n");
return 0;
}
运行结果:
windows控制台在C语言中输出UCS-2宽字节中文\-----begin-----
中文
中文
is leagal UTF8: 1
涓枃
中文
windows控制台在C语言中输出UCS-2宽字节中文\-----end-----
还是利用了上一篇提到的函数,需要注意的是在C语言中不使用setlocale函数是没有办法正确输出utf16中文的,(因为默认可能是使用了windows以前的codepage那一套系统)设置后一切非常正常。无论是用short标志的中文,或者是直接输入的中文,控制台都能正确输出,但是utf8显然没有得到MS的支持,一样的,在VS2005中,UTF8字符不能正常的通过鼠标停留显示出来。。。。
以上是C语言的方案,C++的输出方式如下:(从原来的代码改过来的,有点乱,但是仅仅作为测试,没有考虑那么多了)
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <locale.h>
#include <iostream>
#include "ConvertUTF.h"
using namespace std;
void TestInCPP()
{
// 未为wcout流设置locale前的效果,cout能够输出中文是因为Windows以前的多字节技术
cout <<"未为wcout流设置locale前的效果: /"";
wcout <<L"中文";
cout <<"/"" <<endl;
// 可以看到什么效果都没有,还会将wcout设置错误标志位,不清楚此标志位将什么输出都没有
wcout.clear();
wcout.imbue(locale(""));
wcout <<L"windows控制台在C++语言中输出UCS-2宽字节中文\-----begin-----/n" <<endl;
ConversionResult result = sourceIllegal;
wchar_t lwcBuf[3] = L"中文";
UTF16 utf16_buf[3] = {0};
utf16_buf[0] = 0x4e2d;
utf16_buf[1] = 0x6587;
utf16_buf[2] = 0;
UTF16 *utf16Start = utf16_buf;
UTF8 utf8_buf[12] = {0};
UTF8* utf8Start = utf8_buf;
wcout<<L"utf16_buf:" <<(wchar_t*)utf16_buf <<L" lwcBuf:" <<lwcBuf <<endl;
result = ConvertUTF16toUTF8((const UTF16 **) &utf16Start, &(utf16_buf[3]), &utf8Start, &(utf8_buf[12]), strictConversion);
switch (result) {
default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
case conversionOK: break;
case sourceExhausted: printf("sourceExhausted/t"); exit(0);
case targetExhausted: printf("targetExhausted/t"); exit(0);
case sourceIllegal: printf("sourceIllegal/t"); exit(0);
}
wcout <<L"Is leagal UTF8:" <<isLegalUTF8Sequence(utf8_buf, utf8Start) <<endl;
cout.imbue(locale(""));
cout <<(char*)utf8_buf <<endl;
// 清空缓存,以确定以后的值的确是转换得来
ZeroMemory(utf16_buf, sizeof(utf16_buf));
// 由于转换中利用了这两个start,所以需要重新为start定位,并且保存住End值
UTF8* utf8End = utf8Start;
utf8Start = utf8_buf;
utf16Start = utf16_buf;
result = ConvertUTF8toUTF16((const UTF8 **) &utf8Start, utf8End, &utf16Start, &(utf16_buf[3]), strictConversion);
switch (result) {
default: fprintf(stderr, "Test02B fatal error: result %d for input %08x/n", result, utf16_buf[0]); exit(1);
case conversionOK: break;
case sourceExhausted: printf("sourceExhausted/t"); exit(0);
case targetExhausted: printf("targetExhausted/t"); exit(0);
case sourceIllegal: printf("sourceIllegal/t"); exit(0);
}
wcout<<L"utf16_buf:" <<(wchar_t*)utf16_buf <<endl;
wcout <<L"windows控制台在C++语言中输出UCS-2宽字节中文\-----end-----/n" <<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
TestInCPP();
return 0;
}
可以看到,C++的使用方法更加有效一些,因为仅仅影响到某个特定的输出流,而不是对所有的东西都一次性更改,这点与C++中面向对象的思想相符合,C语言那种方式太霸道了一点(符合C语言全局设定的风格)。但是作为操作系统的特性,C++也没有办法超越,去将UTF-8编码的字符串输出。
所以,最后的结论是,要在windows的控制台输出UTF-8编码的字符串似乎唯一的办法就是先转换成UCS-2然后再输出了。-_-!
这里要提及的就是同时使用两种机制会有冲突,记得当时看到有文章说,C与C++的这两种机制最好不要混用-_-!
write by九天雁翎(JTianLing) – www.jtianling.com
阅读全文....