为神马不要在代码里写中文(二)

在很多个明天之后,我想起来要更新blog了。上次话说到一半,这次回来把结果补上,说的是字符串”a好人”在string literal中的故事。这个字符串有1个洋文字母和2个中文,测试的环境是win7中文版,非unicode的代码页设置的是中文简体,应该是gbk的编码。为了让结果更加整齐一些,我先把出现的结果列一下。win7 x64是little endian的,我直接打印的字符串各个字节,所以,对应的字符编码就自己调顺序吧:

// (1) "a好人"用UTF-8编码为:

61 | e5 a5 bd | e4 ba ba | 00

//(2) "a好人"用GBK编码为:

61 | ba c3 | c8 cb | 00

//(3) "a好人"用UTF-16 LE编码为:

61 00 | 7d 59 | ba 4e | 00 00 

我用’|’分割了一下结果,实际打印结果里是木有那个’|’的。不过还没完,因为编译器不一定能识别出正确的编码,所以会出现一些诡异的结果。编码(1)所列的utf-8字符如果强制被当做gbk来理解,对应的字符串是“a濂戒汉”,也就是1个英文3个汉字。以此字符串为基准我也列一些结果,待会儿填到表格中。

//(4) "a濂戒汉"用GBK编码和(1)结果相同:

61 | e5 a5 bd | e4 ba ba | 00

//(5) "a濂戒汉"用UTF-16 LE编码结果为:

61 00 | c2 6f | 12 62 | 49 6c  | 00 00

再次说明,“a濂戒汉”是utf-8的“a好人”被错误的以GBK编码方式解释而得到的错误字串,本身并没有写在源码中。在我测试的时候,utf-8的源代码文件中字符串就是以(1)那种编码储存的,gbk的源代码文件中字符串就是以(2)那种编码储存的。

下面贴一下结果,输入的源代码文件以gbk,utf-8,和utf-8 with BOM(byte order mark)三种格式储存,分别在不同的编译器下运行上一篇中提到的代码

源文件编码 \ 编译器 gcc4 vc6 vc2003.net vc2005 vc2008 vc2010
UTF-8(char) (1) (1) (1) (1) (1) (1)
GBK(char) (2)* (2) (2) (2) (2) (2)
UTF-8 BOM(char) N/A N/A (1) (2) (2) (2)
UTF-8(wchar_t) (3) (5) (5) (5) (5) (5)
GBK(wchar_t) (3)* (3) (3) (3) (3) (3)
UTF-8 BOM(wchar_t) N/A N/A (3) (3) (3) (3)

*gcc4使用GBK编码的源文件时,加了编译参数-finput-charset=gbk。

这个表格给出的结果很有趣。首先说说char string literal,几乎所有的编译器都得到了和源文件编码一致的结果……但这里面隐含着一个巧合,那就是对utf-8的处理。对于gcc来说,utf-8是默认的编码格式,但对于微软的编译器来说,当前codepage,在我这里也就是gbk,才是默认的编码格式。所以,UTF-8(char)的结果在所有编译器中都一致不过是巧合而已,只要看看下一行UTF-8 BOM(char)的结果就明白了。

vc的编译器据说是自动识别文件编码的,但对于90%都是英文字符的源代码,utf-8和gbk几乎无法自动区分开来,所以加了BOM在文件头才可以让vc识别出真正的编码。不过加了BOM的源代码gcc是不认识的,而vc自己也直到vc2003才开始支持。加上BOM之后,vc才可以真正理解utf-8格式的源代码,这时vc2003和gcc依旧保持一致,仍把char string literal直接编译成utf-8,但是!!但是!!坑爹的事情从vc2005开始出现,此版本之后vc会强制对char string literal进行转码,它会强制转换成当前的codepage,也就是gbk!

可以这么理解,对于char string literal,自vc2005开始,无论源文件是什么格式,它统统会转码到当前的codepage。而gcc,vc6和vc2003则是和源码文件中的编码保持一致——虽然实际上vc2003和vc6并没有真的理解不带BOM的utf-8文件,它们其实是当gbk的编码来理解的,往后看wchar_t的转码结果就能明白。

上面啰嗦了半天,结论就是,假如你在程序中写了中文,例如这样:

const char* p = "五道杠"; 

vc2005之后编译的结果将和当前codepage挂钩……而且将和gcc或vc2003的编译结果不一致……想搞跨平台?还是洗洗睡吧。

再来看wchar_t string literal,如果理解了char身上发生的事情,对照来看表格中wchar_t的结果倒是相当一致,大家都对源文件中的字串进行了转码(注意我没有提供任何UTF16格式的源文件),最终结果都是UTF16-LE编码。遗憾的是,在没有BOM的情况下,vc的编译器不能正确识别utf-8,所以它把“a好人”当成了“a濂戒汉”,由这4个字转换成UTF16-LE之后就变出了结果(5)

说到这里,可以看出wchar_t string literal也是个坑,更不要提ndk中的gcc是以4字节为1个wchar_t的长度,或许是按UTF32来进行编码的?以后有机会再试验吧。

最后总结一下,如果有潜在的跨平台需求,那是无论如何不能在代码中写ASCII以外的字符的。除非显式的使用0x1234, “\u1234″这种方式。另外,使用平台无关的字符串处理函数库代替wchar_t系的标准库函数也很有必要,例如ICU神马的,星际2里面就用的是它。对于vc这一系的编译器,其string literal的编译结果不仅和编译器版本相关,而且和编译器运行环境的codepage相关,着实是在给广大程序员添堵……

好长~以上= =b

为神马不要在代码里写中文(一)

c++中的字符串是个很麻烦的东西,有了宽字符之后变得更加麻烦,据说c++0x要引入raw string,就是类似这种

const char* str = R"噼里

回个车

啪啦";

的奔放写法,所以未来估计会更加的乱。最近看了一些关于字符串编码的东西,才明白双引号之间的这些玩意儿还是有很多说道的,接下来详细记录一下,要是说错了,那就日后发现再改= =b

双引号搞出来的这种字符串称为string literal,相当于一个const char*或者const wchar_t*。vc的编译器(估计大部分都有)有个优化,叫string pool,是说编译的时候所有相同的字符串会被合并,只保留一份在内存中,因此相同的string literal会返回相同的指针。

但是只要有字符就会牵扯到字符编码,因为程序只存取byte,写在引号之间的字符对应哪些byte呢?另外,代码文件本身就是一个文本文件,也存在编码的问题,这二者之间的关系又是如何呢?请不要走开,我们广告之后再进行讨论……

跨平台移植刻不容缓……程序中惊现神秘字串……究竟是程序员脑残还是编译器脑残……答案即将揭晓,请看走进科学之《不要在代码中写中文》……

嗯嗯,广告完毕,答案是~~~~~~这得看用的什么编译器了╮(╯_╰)╭

我写了段程序,用于打印string literal的各个byte:

#include <iostream>
using namespace std;

void outbyte(const void* buf, int len)
{
	unsigned char* p = (unsigned char*)buf;
	cout<<"Len:"<<len<<endl;
	while(len--)
	{
		cout<<hex<<(int)(*p++)<<" ";
	}
	cout<<endl;
}

int main(int argc, char** argv)
{
	const char* p = "a好人";
	outbyte(p, sizeof("a好人"));

	const wchar_t* pw = L"a好人";
	outbyte(pw, sizeof(L"a好人"));

	return 0;
}

代码分别保存成不同的编码格式,有utf-8, 以BOM(byte order mark)开头的utf-8,还有gbk。然后用gcc4,vc6,vc2003,vc2005以及vc2008编译并运行,得到了很有趣(蛋疼?)的结果。

嗯嗯,结果和分析就明天再贴了= =b

Xoom体验

从永高那边借到Xoom一台,在五一假期体验了一把,趁着还没忘记记录几笔。硬件不提了,只谈软件。我先说优点吧,优点就是……挺新奇的……ORZ

这个Honeycomb的UI flow有很大不同,界面上增加了各种特效,android默认的几个应用都有为平板设计的新UI。但我感觉都是花哨的东西,没有什么实际的亮点。

说到亮点,好吧,桌面上的小部件确实丰富了许多。不过我还是觉得需要手动把应用拖放到桌面上这个设定很鸡肋,删除倒是终于可以在my apps里面进行了,不用费劲的跑到setting中搞来搞去。

还有个亮点就是对于sdcard目录的操作,在接驳电脑之后平板和电脑可以同时访问,不必像原来那样还得先mount然后再unmount神马的。

其他的亮点么……嗯……

我还是吐槽吧,实在忍不住了。首先说说流畅度的问题,Xoom的桌面流畅度确实不错,但仅限于“横向正放”的情况。竖起来,或者反过来横放都会导致桌面滑动变得有点卡,不是卡到受不了,但是绝对可以察觉。这真是个坑爹的Bug,这么高的配置了连个桌面都做不好。

第二是桌面下边任务栏不可隐藏,“Home,Back”两个键就放在这里。多了一个用来显示最近开过的应用缩略图的键,Menu键只有在需要的时候才会显示,个人猜测是app注册了option menu项目才会显示。感觉这一条就能坑死一茬app,原来不是说“Menu,Search”是必备的按键么?

Back键放在屏幕最下方实在很难按到,很多应用不提供Back按钮只好用这个键,平板太大所以手指移动距离变得非常长。这一点iPad不错,逼迫应用自己搞一个什么back按钮到屏幕上。而android 3.0居然不思悔改还沿用了手机的设定,唉……

Market上没有专门对应平板的分类,下载应用的时候只能赌。要是自适应屏幕的还好,不能自适应的就显示在桌面中间, 也没有类似iPad放大的功能,很多手机应用就悲剧了。不过还好也有不少应用写出来就是自适应屏幕的,不必像app store里面那样搞个HD版出来骗钱。

浏览器很不好用,因为本身就16:9很扁了,任务栏又白白占了一行,这个浏览器的Tab又占去一行,地址栏再占去一行。好在地址栏在拖动的时候可以隐藏掉,但Tab是隐藏不了的,很是浪费空间。并且Tab的关闭按钮和”+”离得太近,有时候想新增Tab结果把旧的关闭了……至于浏览器突然自动关闭的事情就更不用说了……

Xoom最坑爹的地方就是电源接口居然不是USB,你妹啊又用你自己的接口,moto啊我本想说这一切都是软件的问题但这个接口绝对是moto你自己搞的啊!!

我本来是对Xoom和Android 3.0抱有很大期待的,若不是它太贵我就第一时间入手了。现在看来还是不入为好,至少得等下一个版本出来之后再考虑。感觉Android每一个整数版本都很悲剧,1.0,2.0,3.0都是过渡的,不出意外的话,3.1很快就会出现,我就继续期待吧……

城续缘(五)

事情发生的时候,并没有那么神秘。只不过是指头敲击在键盘上而已,却裂开了,出现了奇怪的符号。没有其他可能性,必然是这个结果,也只好接受了。就这样一点一点,逐渐变的密集起来。但总觉得不够,所以不停的延续着,直到诞生了最古老的传说。那个故事是这样的,如果看到了A,那么下一个是B。结局不会有人猜到,因此无法写出来,好吧。

蛋疼的NDK

最近在ndk下面编译某个lib,可惜这个lib用到了神棍的boost中的regex,恰好regex不仅仅是头文件是需要编译的,而且还是很旧的boost版本。于是乎折腾了半天boost在ndk下面的编译。主要是和jam这个令人发狂的语言打交道(有时间我一定专门出来吐槽),最后即将崩溃的时候,灵光乍现搞定了这坨玩意儿,编译出了静态库。

本以为接下来天下太平,link完毕之后又出现运行时错误。折腾了两个晚上,第一晚搞明白了ndk中的g++是把wchar_t当做4字节处理的,第二晚搞明白了这样做的原因:

google你是在坑爹啊!!!!尼玛wchar_t相关的函数根本没实现啊!!!!运行时各种诡异啊!!!!你们这帮家伙在想神马啊!!!!

参考一下两个链接中的内容就知道我有多想抽他们了:

http://code.google.com/p/android/issues/detail?id=4780

http://www.crystax.net/trac/ticket/25#

不让用就搞得明显点嘛,编译通过之后搞各种trick是怎么回事啊,ndk二等公民的身份彰显无疑……这下彻底傻x了,等吧。

尝试Git中

今儿终于成功把一个svn的项目迁移到了git上。之所以用git不用svn是因为这个项目文件太琐碎,托管项目的服务器走svn实在太慢了。昨天恰好发现它已经提供了git支持,于是乎就决定尝试一下。话说加了新feature为什么没有给我邮件通知啊,这家做SCM的太低调了。当然,我最终没用hg的原因也很简单,因为没有hg嘛……= =b

由于我用svn用的比较随意,大部分时间是当一个文件管理工具来用的,所以转移到git的时候被branch这个东西搞了半天,怎么也想不通要如何映射到git上。仔细琢磨了一下,发现git和svn在概念上是有本质上的区别的,很多文章用类比的方法来讲svn迁移到git是不合适的。譬如svn是可以单独checkout某个目录,而git如果想clone就必须全部搞下来。例如我总觉得svn的repo建起来比较麻烦,所以经常在trunk下同时搞着几个独立的project,这时候就很难直接把svn整个映射到git中。

而且git的强项不单单是分布式管理,还有强大的branch,所以按照svn的思路来用git也纯属蛋疼,没什么实质意义。因此我考虑比较合适的映射方式不是用git-svn把branch, trunk神马的直接搞成git的branch和master,而是需要先分析一下svn中的项目构成,逐个目录的建立git repo。例如原先我可能在trunk中放了好几个project,foo1和foo2,那么我觉得比较合适的方法是各搞一个git repo来管理。

全部搞定之后,托管服务器的SSH Public Key一直不生效,搞了大半天。最后终于成功push,平均速度达到80k/s,相当给力。

Android版SDL编译

NDK刚刚发布的时候,我想试试看怎么用于是去找了个开源的游戏,发现这个叫做《Alien Blaster》的游戏源码中带着移植好的SDL。最近搜了一下,这个作者貌似专心去搞SDL的Android移植了,具体可以看http://libsdl-android.sourceforge.net/。众所周知SDL是个适用范围挺广的跨平台图像库,所以这个东东一旦被移植到Android上,很快就可以出现一大堆其他移植作品。

于是从github上得到最新的代码,我开始尝试在cygwin下编译它。相比起早先的那个alienblaster项目,这个sdl-android的编译还挺诡异的,主要是作者自己在makefile外面包装了一堆自动化的东西,再加上linux和cygwin不完全一样,折腾了半天才搞定。稍后再吐槽……

先得确定NDK的版本,按照他目前的说明得用r4b,而SDK版本需要2.2,不过编译出来的程序可以跑在1.6上。在这里我不得不吐槽这帮搞android的家伙,头三个NDK版本都做的无比奇怪,想编译还得把自己的项目放在NDK的app目录下才行。到了r4终于不用把工程目录诺来挪去了,结果SDK的tools目录内容又起了大变化。还真是热爱折腾啊……

在确定NDK工作正常之后,进入sdl-android里面那个commandergenius目录,需要编译的脚本都已经放在里面了,可惜在Cygwin下需要修改才可使用。首先是他在代码文件中用了linux下的符号链接,目前发现的就是sdl-1.2里面很多文件都是链接到1.3里面的,需要修改。我是选择把1.3里对应的文件直接拷贝到1.2的目录里,省得麻烦。

在project/jni里面是各种lib的代码,比如我们最关心的SDL,还有其他一些开源的库,project/jni/application里面是各种独立的应用。按照readme里面的介绍,想编译哪个应用,就通过ln命令,把project/jni/application/下的源码目录做符号链接到project/jni/application/src,然后进行编译。这样makefile始终是编译project/jni/application/src下的东西。例如按照readme里面说的:

rm project/jni/application/src

ln -s ballfield project/jni/application/src

然后就是比较奇怪的地方了,这个作者喜欢在编译期决定各种配置,执行ChangeAppSettings.sh -a会生成一个配置文件,然后根据配置文件又修改了project/java下的java代码模板拷贝到project/src中。主要是配置项目有将近20个,执行脚本的时候会不停的询问各种问题,搞得无比烦躁。其实都是可以在运行时修改的东西没必要在编译期出来烦人的。后来发现每个appliction下的目录中都有一个现成的AndroidAppSettings.cfg,可以把这个直接拿来覆盖掉commandergenius/的那个,这样每次提问开始的时候直接回车就可以使用默认配置。

这步执行完毕之后再执行android update project -p project得到build.xml用于编译apk。至此就完成了一大半了。

接下来修改build.sh,把自己的NDK路径配置好。值得注意的是NDKBUILDPATH里面如果有空格记得加上引号,例如我是这样的:

NDKBUILDPATH=$NDK4:$PATH

#.....各种省略

cd project && env PATH="$NDKBUILDPATH" nice -n19 ndk-build -j4 V=1 && \

#.....各种省略

咱对linux各种不熟,env这一行卡了好久,感谢贾生同学指点迷津。嗯嗯,最后调用sh build.sh就可以开始编译了。基本上是执行了ndk-build之后再使用ant来编译java代码,大概都觉得eclipse太重口味了吧……build.sh里面包含了对adb install的调用,所以记得把SDK的platform-tools目录也加到PATH中。手动进入project目录编译lib,然后用eclipse编译安装也一样。

总结一下,我个人觉得这个SDL的移植版还是比较有用的,可惜java端的包装就有点粗糙了(或者说繁缛)。所以自己想用的话,还是得做一些额外的工作才行,除非接受作者的设定,例如从AndroidData或者设定的url下载数据自动解压安装屏幕上有奇怪的软件虚拟键盘还有要填写十几个问题的config问卷什么的= =b

强烈的失忆中

整理一个旧坑的时候,怎么也想不起来代码是基于哪个版本搞出来的。各种回忆加分析最后才理清了头绪,其实也就七八个月没有碰嘛,怎么就忘得干干净净了呢。

然后吐槽一下svn的诡异的扫描文件方式……假如我单独check-out目录/branches/foo到某处,比如c:\foo,然后check-out整个/branches到某处,比如c:\branches。当我用c:\foo替换掉c:\branches\foo之后,对foo的更改必须在foo目录下做commit才能感应的到。如果是在c:\branches下做commit,则TortoiseSVN表示神马更改都没有发现。

也就是说,branches下面的foo必须是它自身check-out的时候搞出来的才算数……唉……就因为这个问题,漏提交了几个文件,直到今天搞大清理才被我发现。

怒吼之后

其实SC2已经1个多月未上线了,MHP3准备减速,毕竟已经220个小时了,剩下个岚龙慢慢搞死就行。为了武器而刷素材什么的,我肯定会上瘾的,需要节制一下。

所以说,该干嘛了?