教训都是血染的

五年前吐槽过自己买的杂七杂八的数码玩意儿,有不少血的教训。时光如水岁月如梭,后来我又买了不少杂七杂八的数码玩意儿,又有了不少血的教训……由于流血过多所以健忘的厉害,也想不大起来究竟是什么教训僚。估计以后会继续流血,继续吐槽……

这其实和我热爱拆卸和以伪电工自居有很大关系。我从小就喜欢折腾电子的东西,曾经订阅少年科学画报并邮购了太阳能充电器进行DIY作业,最终因为把电路图上的导线交点全部连通了导致成品完全不工作。这个事情一直到初中物理课上我才明白,原来交点上用力画个实心点才是连通的啊……

然而失败从来没有阻止我探索的好奇心,真正阻止我探索的是我的钱包。虽然有些东西实在想拆开来看看,但是在它未损坏之前贸然拆开,还是不能接受的。于是和其他人不同,我内心是期望他们损坏的,当然不是大毛病,拆开就可以修好的小毛病最赞了。还有保修贴什么的,真是让人纠结啊,不要阻止我拆卸的欲望嘛!不仅仅是拆卸,我对电烙铁也有一种执念,觉得它是最神奇的工具。大概刚上初中我就有了自己的烙铁,只是在学会阅读电路图之前,烙铁大部分是用来焊接那些该死的线路交叉点。我用烙铁最成功的经历是把GBA盗版卡改造成烧录卡,不过我花了100元买的4张卡只有一半改造成功,并且日后统统坏掉了。可是,这难道不就是DIY的核心精神——花钱又费力么!

记得我刚上大学的时候,别人带着大包小包的衣服食物书来到校园,而我的包包里装着电烙铁和万用表。这是怎样一种超前的态度啊!我甚至觉得所有的老师都将拜倒在我的电烙铁之下。遗憾的是高等学府生涯摧毁了我对电子世界进一步探索的欲望,至今让我仍停留在手持电烙铁焊接导线的阶段。本来我会成为用烙铁制作CPU的人,现在却只能变成用小键盘编写操作系统的人。我曾经不止一次的想过,假如我始终保持着对电子设备钻研的兴趣,在不远的将来,我一定会成为一名伟大的……电工……

无论如何,俱往矣,数杯具人物,还看今朝。我其实是想说……

我的kindle貌似被我拆坏了啊啊啊啊啊啊啊啊!

我勒个去kindle的屏幕哪里有双面胶粘了,铁壳子上层是可以取下来的啊啊啊啊啊啊!

网上的拆机教程能不能不要写的那么二啊啊啊啊啊啊!

(被拖走)

补丁也有补出问题的时候

上一篇提到的member-function pointer的问题,后来发现并不是因为不同的编译环境造成指针大小不一样,而是因为不同的环境下结构体的对齐规则出了问题。

出问题的是vc6.0 sp5,在这个环境下,如果定义unknow type的member-function pointer,例如这样:

class Unkonw; //forward declaration

typedef void(Unkonw::* MFP)(int param);

struct Callback {

char* szName;

MFP fp; }

即使编译选项里面没有设定其他的对其规则,使用了默认的8字节对齐,这个sizeof(Callback)也是32,而不是正常的24。这个问题在原始的vc6.0里面并没有出现,打了sp5补丁才会出现。如果继续升级到sp6就修复这个问题了。所以说,真正邪恶的不是member-function pointer,而是出错的编译器才对-_-b

查问题的时候,有种很简单的输出sizeof结果的方法,可以做到编译期查看对象大小,方法是:

template<int s> class TestTSize; 

然后在需要的地方写TestSize<sizeof(xxx)>()即可在编译出错的信息里面看到求值以后的sizeof。这个技巧貌似在《Modern C++ Design》里面提到过。嗯嗯,很有用的说。

邪恶的member-function pointer

谁都用过指针。

更别说我和你了。

但是当member-function pointer出现的时候,

你还是要跪倒!

嗯……今天耗了些精力d掉一个老bug。症状是这样的,从某人处拷贝来的static lib链接到应用程序之后,lib里面的函数尝试执行应用程序传过去的callback,结果没反应。

传入的callback是一个包含了某member-function pointer(MFP)的结构体,大致定义如下:

typedef void (Object::*FunctionPtr)(void);

struct Callback {
char * szName;
FunctionPtr fp;
}

库函数大致为这个样子:

void foo(Callback* ptr, Object* pObj) {

if(pObj && ptr->fp)
(pObj->*(ptr->fp))(); //怎么这么难看,我写对了伐……

}

开始以为是什么参数错误,后来惊奇的发现在本地重新编译一次static lib,再链接就一切正常僚。既然这样肯定是编译出来的asm有问题,略去痛苦的过程,直接进入主题,一路跟过去发现了不同。在错误的lib中,ptr->fp的地址是ptr+0x8h,而正确的lib里面是ptr+0x10h。于是去看了一下我本地编译出来的sizeof(Callback),猜猜是多少?

答案是32。

为啥这么大啊,于是又求了下sizeof(fp)。猜猜看这次又是多少?

答案是16。

本来以为fp至多再存个虚表,更何况我这个是non-virtual的,不要虚表,结果没想到搞出这么大一坨。这样看来,因为要对齐所以整个Callback结构体被搞成了32字节,这个char*可真是浪费呢。由此推出错误的lib里面是把fp当做8byte大小来储存的,所以ptr->fp就是ptr+0x8h。(好吧,这一段的分析在事后证明是错的,fp总是16byte,出错是因为错误的对齐规则,前者错误的使用了#pragma pack(16)的规则,邪恶的vc6.0 sp5……)

比较巧合的是,由于传入的Callback*来自一个储存在mem pool里的数据,而这个mem pool是由自己的内存分配器来管理的,一开始会把pool里的数据全部写0,所以错误的指针恰好指向了一段为0的内存,阴差阳错的通过了if判断,所以没有报access violation。

最重要的是为啥会有这么大的fp?放狗查到一篇文章,《Member Function Pointers and the Fastest Possible C++ Delegates》,原来这么复杂。其实我以前还用过这个fastdelegate,但是完全没注意到文章中有个描述不同编译器下的member-function pointer大小的表格。真是热闹啊,从4到20无奇不有……回头看看我的环境,应该是由于MSVC的编译选项不同导致了大小不同。具体是被当做那种情况处理了还得再仔细研究下……

裸用member-function pointer真是罪孽啊,还是封一层比较好。比如boost::bind那种东西……

嗯,总之是爽了。

算你狠

第一次突然掉电,我忍了。

第二次突然掉电,我在Buzz上吐了个槽。

第三次突然掉电……好吧,我败了……我去睡觉还不行么。

莫非是因为大家都开空调了?还好第二次掉点之后我就把硬盘电源拔了,要不然可能会悲剧。哼哼哼哼。

记录一个FPU的猥琐Bug

貌似n多人遇到过,知道问题的原因之后用合适的关键字可以搜出很多帖子。核心问题归结为:float单精度浮点数有效位数不足,而FPU控制寄存器CTRL的bit8和bit9被置为0时,使得FPU被强制工作在单精度(24bit)模式,导致结果出错。

起因是一个朴素的string转浮点数函数,由于输入数据的范围被设定在0~1,000,000且包含两位小数,所以最初的版本使用float储存结果就会导致精度不足。发现问题之后,第一反应就是采用double来储存,理论上不会出错了。结果神奇的事情发生了,改成double之后精度还是不足。

接下来省略掉各种无用处的猜想,再去掉其他的逻辑,这个函数其实就这么一行:

double foo( int input ) { return (double)input / 100; }

简化的症状就是,我调用foo(100,000,000)期待返回1,000,000.01,实际返回的是1,000,000.00

当我尝试在程序最开始调用这个函数,例如放到main的最前面,结果是正确的。如果初始化完其他模块之后,结果就不对了。这样重现n次之后,脑子顿时就不转了,居然存在同一个函数同样的输入但输出不同!我确定这个函数木有inline,就算有啥优化,跑起来的汇编指令也完全一样的说。所以开始考虑是不是thread context有什么不同导致……其实这时候如果对FPU的工作机制比较熟,肯定马上就可以想到。可惜我早就把这种东西还给课本了,只好傻乎乎的肉眼看寄存器有什么问题。

最初想复杂了,脑子一抽想到了以前看过云风一篇帖子,模糊记得是什么东西导致浮点运算出错。搜出来一看,原来人家说的是编译器的问题,比我这个严重的多。不过因为有这么个模糊的印象,又通读了一遍这个帖子,接下来就放在研究FPU那坨诡异的寄存器上面了。

对比发现那条浮点除法指令FDIVR调用过后,结果正确的那次STAT寄存器有所变化,bit8置为了1。google之了解到bit8是如果为1,一切正常,如果为0,代表运算精度不足,FPU对结果做了roundup,大概就是这个意思吧。看来确实是精度问题,但double肯定是完全能存下我要的结果的说……疑问没有消除,继续肉眼看半天,又发现二者的CTRL寄存器也有所不同:正确的那次是0x27F,错误的那次是0x07F。马上google之,果然查到CTRL的bit8和bit9是用来控制运算精度的,(CTRL&0x300)之后,默认状态下应该为0x200,表示双精度(53bit),如果是0x000则表示单精度(24bit)。

好吧,如此看来,第一次调用FPU的时候,CTRL还是用双精度,第二次就单精度了,怪不得出错。问题是这个东东怎么会改变呢?继续google,换了几次关键字终于找到,居然是D3D的CreateDevice干的。DX的文档《Top Issues for Windows Titles》一节中有说,如果没有设置D3DCREATE_FPU_PRESERVE,则FPU被强制设成单精度。更加诡异的是这个东西的影响是per-thread,其他的thread如果没有碰过FPU的CTRL,那么还是默认的精度。为何D3D要做这种处理?彰显它的浮点运算能力?我不是很理解。再次回到程序中验证,的确是图像模块的Init被调用之后,CTRL就奇迹般的从0x27F变成了0x07F了。

解决方案就不说 了,总之问题原因探究到此为止。果然,就如同江湖上传闻的那样,使用浮点数存在各种诡异陷阱。这次悲剧的debug之后,最令人欣慰的一点是让我对float这个该死的单精度浮点数更加敏感……虽说丫是32bit的,其实也就7位有效数字,唉……

那帮人

翻出仙四看credit,然后开古剑看credit。

听说当年仙四的预算很少,开发过程应该不会很享受吧。也许曾经怀着出个“大作”的梦,结果折腾了半天连公司都散了,应该会有很多苦闷吧。

但还是那帮人,就这么折腾了2年多,又弄了个古剑。

本来对古剑各种失望,各种不满,看着两份几乎一样的credit,突然就不想喷了。

喵了个咪的,只有爱果然是不行的啊。

月志加一

基本上一个月没更新了。其实就是一个月没更新了。但那又怎么样嘛~╮(╯_╰)╭

入了好几本《独唱团》,到手了就散出去。估计有些人想看但是懒得买吧,那我就主动传播一下~

最近天气热了,各种效率变低,决定减少刷reader和twitter的频率。看了几本推理小说和科幻之后,对自己脑中yy的那个设定又燃起来了,有机会一定要完善一下。

晚上看到CD同学对《Dragon Age》乐此不疲的样子也捡起来玩了玩,为了加快进度载了一份某处制作的汉化包,结果有些失望。开场那么简单的句子,就翻译错了,是缺少爱的人制作的么……

“But those, who once called us heroes, have forgotten”

给译成了“那些被我们叫做英雄的人已经忘记”,但这句是“把”的意思吧……

“Maker help us all”

译成“造物主帮助我们很多”,应该是恳求的语气说“造物主请帮助我们大家”的意思吧……

哎呀呀,结果还是只能卸载掉这个民间汉化包么=_=b

槽不可以不吐

自从我看了某些类似圣经一样的鼓吹人与人之间应当充满爱的书之后,吐槽就变得越来越少了。但这并不代表我的吐槽功能有所减弱,因为除了在blog上吐槽,我还有很多隐晦的方式可以吐槽。

但我确实懒得吐槽了,吐来吐去效果一般啊,没有人因为被我吐槽然后就悲愤而死,基本上大部分在我眼里是sb的sb依旧sb,有些sb变得不那么sb也并不是sb自己的原因,而是因为我调整了我的坐标系。

喂,是我自己在变呐,而sb们并没有变。这和我想的不一样啊。

唉……

We Are One

有没有想过这样一种场景。

有些人的想法几乎一摸一样,我想到的你也曾经想到。由这件事情出发,我认为会这样,你也认为会这样。假如你得到一份前人留下的手稿,手稿中说你是他的转世,然后他列举了各种你曾经遇到的事情,发现你们几乎会在同样的场景下做同样的事情,然后你是否会相信你就是他的延续呢?

现在可以做的更加高科技一些,比如前人留下一个网站,你问的各种问题电脑都有解答。因为即使物理上你们没有基因关系,但是精神上趋于一致,这完全可以看做是一种延续。

接下来,他可以告诉你,如果我是你,你应该去做如下行为。如此反复的心理暗示会不会让这些行为真的被你所施行?

这是一种超越时间和地域的心理控制手段。很想拿这个桥段构思一个故事……嗯嗯。