2006年九月月 发布的文章

'Manna' – An extremely attractive fiction

Our British English teacher Alex recommended a book called ‘Manna’ to us for its simple grammar and vocabulary. After reading it, we all agreed on that it was an extremely attractive fiction.

The author of ‘Manna’ is Marshall Brain, who is a writer, a well-known national speaker and a consultant in U.S. In his fiction, he tells us his point of view on robots’ coming out in near future. It seems to be a science fiction, maybe a political fiction, who knows, because almost any book could be associated with politics.

The following is the link of this fiction:
Manna by Marshall Brain

If you have your own options after reading the book, I am looking forward to talking with you about that.

小心'溢出'陷阱

这几天以前曾经做过的一个项目上线测试了,果不其然,没有经过’战争洗礼’的产品就是靠不住,这不出了若干问题。害得我逃了半天课远程支持。

其中的一个问题很值得思考。其所在的模块并非是一个核心功能模块,而是一个提高系统Availability的一个功能模块,主要功能就是监视磁盘占用率。我们通过配置给出允许使用的磁盘空间大小(以M Byte为单位),以及两个阈值,即当占用率达到多少的时候,Do A;达到多少的时候Do B。

我们假设用变量quota代表配置中读取的配额数值,而total代表实际检测到的占用数值,一般关于文件大小的系统调用都是用byte作为单位的,也就是说我们需要做一个转换,假设换算后的变量为quota1。由于最初我们没有考虑周全的原因,我们使用unsigned int作为quota、quota1和total的存储类型。结果在家里没有做过认真的测试,导致一到现场就’露馅’了。这个问题反应到家里后,一个同事发现了这一问题,并作了修改,经过简单的测试,好像表面上问题消失了。再一次提交到现场后,问题依旧。

由于那位同事还有其他工作,我只能逃课改问题,经过一段时间的代码Review终于发现了些许’蛛丝马迹’,简单表述一下,原来这里的代码是这样的:

计算total;
quota1 = quota * 1024 * 1024;
拿total和quota1之比与配额阈值作比较;

注意这里的total和quota1是unsigned long long,也就是64位的,而quota是unsigned int,即32位的。首先quota肯定不会出现溢出的可能,因为检查配置发现这个数不大。那么为什么从日志观察,quota1有问题呢?

比如我们的quota配置为1004800,那么在换算后正确的数值应该是053609164800,而日志中打印出来的结果却是1342177280。基本上可以肯定问题出在quota1 = quota * 1024 * 1024;这个转换式上。

我们大概可以用下面的程序来模拟一下这个问题:
int main() {
        long m = 1004800;
        unsigned long long n;
        n = m * 1024 * 1024;
        printf("%llu\n", n);
}

由于n = m * 1024 * 1024这个计算式的工作流程是这样的,先将m * 1024 * 1024的结果保存在一个临时变量中,然后再将这个临时变量值赋给n,这里是在Solaris9下利用GDB反汇编的结果:

(gdb) disas main
Dump of assembler code for function main:
0x0001066c <main+0>:    save  %sp, -128, %sp
0×00010670 <main+4>:    sethi  %hi(0xf5400), %o0
0×00010674 <main+8>:    or  %o0, 0×100, %o0     ! 0xf5500
0×00010678 <main+12>:   st  %o0, [ %fp + -20 ]
0x0001067c <main+16>:   ld  [ %fp + -20 ], %o0
0×00010680 <main+20>:   sll  %o0, 0×14, %o0
0×00010684 <main+24>:   st  %o0, [ %fp + -28 ]
0×00010688 <main+28>:   sra  %o0, 0x1f, %o0
0x0001068c <main+32>:   st  %o0, [ %fp + -32 ]
0×00010690 <main+36>:   sethi  %hi(0×10400), %o0
0×00010694 <main+40>:   or  %o0, 0×358, %o0     ! 0×10758 <_lib_version+8>
0×00010698 <main+44>:   ld  [ %fp + -32 ], %o1
0x0001069c <main+48>:   ld  [ %fp + -28 ], %o2
0x000106a0 <main+52>:   call  0×20800 <printf>
0x000106a4 <main+56>:   nop
0x000106a8 <main+60>:   mov  %o0, %i0
0x000106ac <main+64>:   nop
0x000106b0 <main+68>:   ret
0x000106b4 <main+72>:   restore

%o0 = 0xf5500 = 1004800
store %o0 -> fp + -20
大概看一下:
0×00010670 <main+4>:    sethi  %hi(0xf5400), %o0
0×00010674 <main+8>:    or  %o0, 0×100, %o0     ! 0xf5500
0×00010678 <main+12>:   st  %o0, [ %fp + -20 ]
这三句实际上是在栈上分配一个变量m,并赋值为1004800,这里编译器利用sethi  %hi(0xf5400), %o0和or  %o0, 0×100, %o0两句在寄存器%o0中构造出1004800(即0xf5500),然后将寄存器的值通过st指令写入到%fp – 20的位置。即m占据着从%fp – 17到%fp – 20这四个字节。

再往下
sll  %o0, 0×14, %o0,
st  %o0, [ %fp + -28 ]
这里是编译器做的优化,它没有乘以两次1024,而是直接乘以1024*1024的结果,也就是2^20,即将%o0逻辑左移20位,即逻辑左移0×14,我们知道逻辑左移即把操作数看成无符号数。对寄存器操作数进行移位,不管左右移,空出的位均补0,我们可以来手工逻辑左移一次,目前%o0中存储的是无符号数0xf5500, 即 0000 0000 0000 1111 0101 0101 0000 0000(B),我们逻辑左移20位后为0101 0000 0000 0000 0000 0000 0000 0000(B), 即0×50000000,即1342177280。之后利用st指令将改寄存器的值存入到%fp – 28开始的8个字节当中(即从%fp – 21到%fp – 28)。这样我们读出来的n值也就是1342177280了。

如何修正呢?看下面的例子:
int main() {
        long m = 1004800;
        unsigned long long n = m;

        n *= 1024 * 1024;
        printf("%llu\n", n);
}

(gdb) disas main
Dump of assembler code for function main:
0x0001066c <main+0>:    save  %sp, -128, %sp
0×00010670 <main+4>:    sethi  %hi(0xf5400), %o0
0×00010674 <main+8>:    or  %o0, 0×100, %o0     ! 0xf5500
0×00010678 <main+12>:   st  %o0, [ %fp + -20 ]
0x0001067c <main+16>:   ld  [ %fp + -20 ], %o0
0×00010680 <main+20>:   st  %o0, [ %fp + -28 ]
0×00010684 <main+24>:   sra  %o0, 0x1f, %o0
0×00010688 <main+28>:   st  %o0, [ %fp + -32 ]
0x0001068c <main+32>:   ldd  [ %fp + -32 ], %o0
0×00010690 <main+36>:   mov  %o0, %o2
0×00010694 <main+40>:   mov  %o1, %o3
0×00010698 <main+44>:   srl  %o3, 0xc, %o5
0x0001069c <main+48>:   sll  %o2, 0×14, %o4
0x000106a0 <main+52>:   or  %o5, %o4, %o0
0x000106a4 <main+56>:   sll  %o3, 0×14, %o1
0x000106a8 <main+60>:   std  %o0, [ %fp + -32 ]
0x000106ac <main+64>:   sethi  %hi(0×10400), %o0
0x000106b0 <main+68>:   or  %o0, 0×378, %o0     ! 0×10778 <_lib_version+8>
0x000106b4 <main+72>:   ld  [ %fp + -32 ], %o1
0x000106b8 <main+76>:   ld  [ %fp + -28 ], %o2
0x000106bc <main+80>:   call  0×20820 <printf>
0x000106c0 <main+84>:   nop
0x000106c4 <main+88>:   mov  %o0, %i0
0x000106c8 <main+92>:   nop
0x000106cc <main+96>:   ret
0x000106d0 <main+100>:  restore

和上面的汇编差不多少,主要的差别就是再st  %o0, [ %fp + -28 ]后,所有的操作均针对8位的m了,而且寄存器也不仅仅一个%o0参与(位数不够了),这句之后都是关于8字节的运算了。也就不存在溢出了。毕竟汇编细节看起来还是很费劲的,大家能明白其中的意思即可。

其实简单来看我们可以这么来理解:
n = m * 1024 * 1024;
n *= 1024 * 1024;

前一个式子可以看成 m’ = m * 1024 * 1024; n = m’;这样我们可以简单的认为m’这个中间变量和m存储空间一致。
而n *= 1024 * 1024 <=> n *= 1048576 <=> n = n * 1048576,都是在n的基础上操作,不会出现溢出问题。

溢出问题一般都很隐蔽,很难轻易发现,大家要格外注意。

最初接触计算机的两三事

刚刚看了云风的’编程的门槛‘一文,和他相比我接触计算机的时间则显得少得可怜。

记得第一次接触计算机是在初中二年级,一张软盘启动计算机的年代,WPS、五笔字型盛行的年代,那时我的理解计算机除了能打字之外,唯一能干的就是玩游戏-超级玛丽-一张软盘足以。那时渐渐的失去了对计算机的兴趣,五笔字型输入法也没学会,除了最简单的一级简码:)。

进入高中后,也有计算机课,老师用一个学期的时间讲BASIC语言,不知道是老师水平的问题,还是自己足够笨,一个学期下来,自己对计算机仍旧停留在游戏和打字,这回游戏又多了一种足球,一种射门游戏。另外就是见到了windows3.1,那时我认为它就是一种在DOS下启动的程序,不过是图形界面罢了,可以利用上一种新型的设备-鼠标了,但是这个程序很不稳定,总是玩玩就’不理你了’,除了按下电源开关,你别无选择。

到了高三的时候,网络开始盛行,网吧如’雨后春笋’般从大街小巷的各个角落冒出,很多同学晚上都出去’包宿’,我从未去过,我也不知道他们出去做什么,后来听说有网吧里有’红警’、’三角洲’,可以在’聊天室’里和你不认识的人特别是mm聊天,还有一种聊天工具叫OICQ很是火爆。就这样直到大一我GF让我去网吧到聊天室和她聊天,我才真正开始接触网络。记得第一次去网吧,老板让我选一台机器就可以上网了。我坐在机器前按照GF的指示,在IE地址栏中输入聊天室地址,然后我居然不知道下一步该做什么?无可奈何之下只好问网吧老板,老板用惊异的眼神儿看着我,很不情愿的说了一声:按一下回车就行了。至今这件事情还是让我牢记于心的。

自从有了这次经历,我开始学习上网和利用网上资源了,并且了解到在学校里面有更便宜的实验室可以上网,0.5元一分钟,很便宜吧,而且实验室有软驱,可以拷数据。再后来,也开始和寝室哥们一起出去’包宿’,记得大学第一学期的那个元旦之夜,我们就是在网吧过的,上网上累了,就玩玩游戏,玩累了,就趴在电脑桌上睡上一觉。

大一下学期,我们寝室终于决定一起’投资’买一台电脑了,我们的第一台电脑共用了近7000块。从此算是顺利过上有电脑的日子了。

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!




这里是Tony Bai的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:


如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:


以太币:


如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:



本站Powered by Digital Ocean VPS。

选择Digital Ocean VPS主机,即可获得10美元现金充值,可免费使用两个月哟!

著名主机提供商Linode 10$优惠码:linode10,在这里注册即可免费获得。

阿里云推荐码:1WFZ0V立享9折!

View Tony Bai's profile on LinkedIn


文章

评论

  • 正在加载...

分类

标签

归档











更多