邓际锋ID:soloist
83578次访问,排名1119好友1人,关注者2
soloist的文章
原创 39 篇
翻译 0 篇
转载 0 篇
评论 189 篇
soloist的公告
欢迎吹毛求疵,感谢您对任何错误的指正,包括技术的、语法的、用词的、标点的、典故的、引用资料的……
最近评论
qingbai:lua绝对是个好东西。但国内除了java就是.net,其他东西没法活。因为程序员得工作,得吃饭。国内有哪家公司用lua?唉没办法呀。国外是一片繁荣,“百家争鸣”,国内是“青一色”的java和.net!无奈!
zhangyilan:尽管没有在实际代码的编写中碰到这个问题,不过也先学习一下,免得出现问题了搞出清楚情况。
ddrmsdos:这篇文章写的太好了,分析的非常仔细,以前常常碰到这类问题,终于解了我多年的心头之患......
ollydbg23:楼主的这篇文章写的非常好啊!
我看了以后,还是挺有收获感的,多谢多谢!
我也是对汇编,c++的比较感兴趣,有空可以交流一下!
w2001:写得很好
文章分类
收藏
    相册
    好博链接
    C++罗浮宫
    cpper
    fixopen
    fmddlmyy
    neoragex2002
    whinah
    云风
    梦想风暴
    沉思者
    许式伟
    负暄琐话
    辣子鸡丁
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 C标准库函数abs的一个错误收藏

    新一篇: MFC中菜单项的动态添加、删除与响应 | 旧一篇: 模板参数推导

        VC.NET 7.1 提供的取绝对值函数(abs.c中)如下:

       
    int __cdecl abs (
            int number
            )
        {
            return( number>=0 ? number : -number );
        }

       此函数实现得非常简单,乍看之下无任何毛病,可是仔细琢磨琢磨,就发现了一个大漏洞。举个极端的例子,当函数参数为INT_MIN(32位的int此值为0x80000000,即–2147483648)时,对它取负会产生上溢,因为32位的int能表示的最大正数是INT_MAX(0x7fffffff,即2147483647)。实际上,对INT_MIN取负等于什么也没有干(0x80000000按照补码取负规则还是0x80000000)。因为函数声明的返回值也是int,所以大多数时候程序员会用某个int变量来接收这个返回值,但是如上所述的极端情况则几乎百分百地导致程序混乱。

        简单的解决办法就是总是用unsigned int类型的变量来接收返回值。因为32位的unsigned int的值域范围是0到4294967295(0xffffffff),2147483648落在这个范围内,所以用unsigned int变量可以正确地表达INT_MIN的绝对值。如果觉得这种做法容易忘记,还可以自己写一个abs,只需在上述函数体的前面加一句assert(number > INT_MIN)就行了,或者将返回值改成unsigned int类型。

        为什么在标准库中会出现这么隐蔽的漏洞呢?因为计算机的存储空间有限,所以C中的每种类型所占字节数也须是有限大小,这样程序语言中的类型真正能表示的值域就是对应数学类型值域的有限子集了。这就是现实与理想的差距。不仅如此,因为有符号类型比无符号类型多拨出一个二进制位来表示符号信息,则它们能表示的值域又有所差异。但是程序员写程序却往往有意或无意地忽略这种差别,不愿面对现实(正如有的程序员调用函数从不检查返回值)。他们使用程序中的类型就象在使用理想的数学上的类型一样。欧洲阿里亚纳火箭爆炸就是此种错误给我们的最惨痛教训。

        既然如此,每一个C程序员都有责任牢记这么一条规则:“经常反问:这个变量或表达式会上溢或下溢吗?”(《编程精粹-Microsoft编写优质无错C程序秘诀》P80,Steve Maguire 著)。

        补充(于2005年3月22日): ANSI C标准规定了每种整数类型的最小值域(但没有规定它们必须采用哪种编码方案),并要求所有的C语言实现都要在limits.h头文件中通过诸如INT_MIN、INT_MAX这样的宏来指定该实现中整型数据的实际值域,而且这些实际的值域一定不能比标准规定的最小值域还要小(也即要求每种实现在limits.h中定义的宏的绝对值不小于C标准规定的同名宏的绝对值,并且正负号要保持一致)。标准定义的INT_MIN是-(2^15 - 1),INT_MAX是(2^15 - 1),换句话说,标准保证了int型数据至少能表示-(2^15 - 1)到(2^15 - 1)这样一个对称区间内的所有整数,因此程序员可以放心大胆地用int变量存储以上范围内的任何整数。但与此同时请特别注意,用int变量表达超出上述范围的数将是一件没有法律(标准即是程序员的法律)保障的事情,所以不应该想当然地认为-32768(即-2^15)一定可以用int型来表达,尽管它确实位于用二次补码记法(twos-complement notation)进行编码的16位整数的值域内。熟悉以上背景后再回顾abs函数的问题就会发现,实际上该函数对于标准规定范最小值域内的所有整数都能正常工作,而上面提到的引起错误的输入数据已经不在此范围内了,所以此错误不应由abs而应由函数调用者负责。由此可见,为了安全以及可移植性,将表达式欲表示的值严格限制在标准指定的最小值域内是一个良好的编程习惯。

    发表于 @ 2004年10月26日 18:00:00|评论(loading...)|编辑

    新一篇: MFC中菜单项的动态添加、删除与响应 | 旧一篇: 模板参数推导

    评论

    #周星星 发表于2004-10-27 08:53:00  IP: 218.2.111.*
    Very good,佩服!
    #soloist 发表于2004-11-22 11:42:00  IP: 211.144.99.*
    这种溢出确实是由补码表示法的固有缺陷引起的。这不是C的错。但是作为C语言重要组成部分的标准库函数却没有考虑这种情况,这就是失职了。
    标准库函数的设计原则是尽量高效?我没有听说过,也不赞成。不是每个程序员都会意识到这种风险,从而写出如你所述的安全代码的。库函数应该在程序员对其内部实现一无所知的情况下能有效地阻止对它的错误使用。当然,C要解决的是现实世界的问题,库函数的设计也有其历史原因,没有必要把责任全部推在它们身上,我只是想提醒大家有这样一个潜在的风险存在。毕竟,没有安全性的高效是毫无意义的。
    我比较倾向用断言assert(number > INT_MIN),因为这样就相当于和调用者订立了一个契约(contract),abs要求调用者承担使参数大于INT_MAX的义务,同时保证在这种条件下返回值一定是参数的绝对值。可以发现契约实际上精确地描述了函数的语义,使得潜在的风险明朗化,从而令调用者采取措施预防它。
    #lover_p 发表于2004-11-22 11:00:00  IP: 218.247.132.*
    这个似乎不是什么错误。这种溢出时必然会发生的,而且使用unsigned int似乎不是那么合适(没有符号呀)。
    这样的函数关键看我们怎么用,正确的用法是:
    a = abs(x);
    if(a >= 0) {
    没有溢出,可以使用
    }
    else {
    发生了溢出,处理错误
    }
    也就是在调用abs函数后判断一下。
    标准库的设计原则是:尽量地高效。因此没有必要在库中对这若干万分之一可能性的缺陷作出处理。相反,如果你的程序有可能遇到这种危险,并且这个缺陷有可能危害到程序的用户时,才要你自己作出处理。否则的话,越快越好。
    类似的函数实际上有很多,包括臭名昭著的gets。
    想学好C语言就要学会自己做很多事。
    #cecil 发表于2004-12-04 22:28:00  IP: 219.140.144.*
    跟intel8086 中的neg指令是一样的.
    #清风雨 发表于2005-06-24 10:54:00  IP: 61.186.252.*
    经常反问:这个变量或表达式会上溢或下溢吗?—— 这句话好!

    assert俺太喜欢他了;就这个函数,unsigned俺喜欢,毕竟返回值是非负的。
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © soloist