当前位置:  开发笔记 > 编程语言 > 正文

你是否总是对C中的数字使用'int',即使它们是非负数?

如何解决《你是否总是对C中的数字使用'int',即使它们是非负数?》经验,为你挑选了8个好方法。

我总是使用unsigned int来表示永远不应该是负数的值.但是今天我在代码中发现了这种情况:

void CreateRequestHeader( unsigned bitsAvailable, unsigned mandatoryDataSize, 
    unsigned optionalDataSize )
{
    If ( bitsAvailable – mandatoryDataSize >= optionalDataSize ) {
        // Optional data fits, so add it to the header.
    }

    // BUG! The above includes the optional part even if
    // mandatoryDataSize > bitsAvailable.
}

我应该开始使用int而不是unsigned int来表示数字,即使它们不能为负数吗?



1> BlueRaja - D..:

有一点没有提到,交换有符号/无符号数字可能会导致安全漏洞.这是一个很大的问题,因为标准C库中的许多函数都采用/返回无符号数字(fread,memcpy,malloc等都采用size_t参数)

例如,采取以下无害的例子(来自真实代码):

//Copy a user-defined structure into a buffer and process it
char* processNext(char* data, short length)
{
    char buffer[512];
    if (length <= 512) {
        memcpy(buffer, data, length);
        process(buffer);
        return data + length;
    } else {
        return -1;
    }
}

看起来无害,对吧?问题是length已签名,但在传递给时会转换为unsigned memcpy.因此设置长度SHRT_MIN将验证<= 512测试,但导致memcpy复制超过512个字节到缓冲区 - 这允许攻击者覆盖堆栈上的函数返回地址和(经过一些工作)接管您的计算机!

你可能天真地说,"很明显,长度需要被size_t检查或检查>= 0,我永远不会犯这个错误".除此之外,我保证,如果你曾经写过任何非平凡的东西,你就有.因此,有作者的Windows,Linux的,BSD,的Solaris,火狐,OpenSSL的,Safari浏览器,MS画图,互联网浏览器,谷歌的Picasa,歌剧,闪存,开放式办公,颠覆,阿帕奇,Python的,PHP,洋泾浜,瘸子,... 在和和 ...... -这些都是光明的人,他们的工作是知道的安全性.

简而言之,始终size_t用于尺寸.

男人,编程很难.


@ dan04:不,**当你应该使用无符号整数时,根本原因是使用有符号的整数**像`size_t`*(或者更确切地说,它是有符号/无符号数之间的隐式转换)*.当然,忘记检查边界也是一个问题.我已经改变了这个例子,使其更加清晰 - 谢谢.
不,**忘记边界检查**会导致安全漏洞.如果你在另一个方向上弄错了,`unsigned`对你不会有帮助,你的函数很乐意写'myArray [0xFFFFFFFF]`.
我仍然不明白为什么省略边界检查`length`的下限不是这里的根本问题.当然,你可以使用像`size_t`这样的无符号类型,但是你甚至无法检查下限是否为非负数.由于隐式转换规则,这只会​​导致*不同的*错误.这有什么改进?
@ dan04:不,它不会写。如果使用了unsigned int length = 0xFFFFFFFF,则if(length <= 512)的值为false。

2> Stephen..:

我应该总是......

"我应该总是......"的答案几乎肯定是"不",有很多因素决定了你是否应该使用数据类型 - 一致性很重要.

但是,这是一个非常主观的问题,它很容易搞乱无符号:

for (unsigned int i = 10; i >= 0; i--);

导致无限循环.

这就是为什么包括谷歌的C++风格指南在内的一些风格指南不鼓励unsigned数据类型.

在我个人看来,我没有遇到由无符号数据类型的这些问题引起的许多错误 - 我会说使用断言检查你的代码并明智地使用它们(当你执行算术时更少).


未检测到的下溢和溢出是基本的C系列陷阱 - 使用有符号和无符号更​​改错误情况,但不会删除任何错误.当然,在零附近有一个错误案例可能是*特别*坏事,但正如你所说,这取决于你正在做什么.在上面的循环中,您可以检查`!=〜0`作为结束条件 - 它是一个有用的无符号无效/结束值.这是一个轻微的欺骗(`0`是int,所以'~0`是`-1`)但是在理智的机器上隐式转换才起作用,并且在视觉上它比没有签名的`-1`更不可思议.
@Thomas:谢谢,反馈,但我不完全确定我同意.c(和c ++)提供了`signed`和`unsigned`类型之间的隐式转换,这可以产生静默和令人惊讶的结果.两者之间没有太多的语法约束可以触发编译失败(除非你传递额外的编译器警告标志)."unsigned"类型的好处主要是语义,除非您专门使用无符号类型来避免操作符号位(例如在位掩码中).

3> Nietzche-jou..:

您应该使用无符号整数类型的一些情况是:

您需要将数据视为纯二进制表示.

你需要使用无符号数的模运算的语义.

您必须与使用无符号类型的代码(例如,接受/返回size_t值的标准库例程)进行交互.

但对于一般算术,当你说某些东西"不能为负"时,这并不一定意味着你应该使用无符号类型.因为你可以在无符号中加一个负值,所以当你去掉它时它会变成一个非常大的值.所以,如果你的意思是禁止使用负值,比如基本的平方根函数,那么你就是在说明函数的前提条件,你应该断言.你不能断言不可能的是; 你需要一种方法来支持带外值,因此您可以测试他们(这是同一种逻辑背后的getchar()返回一个int而不是char.)

此外,签名与未签名的选择也会对性能产生实际影响.看看下面的(人为的)代码:

#include 

bool foo_i(int a) {
    return (a + 69) > a;
}

bool foo_u(unsigned int a)
{
    return (a + 69u) > a;
}

foo除了参数类型之外,两者都是相同的.但是,在编译时c99 -fomit-frame-pointer -O2 -S,您会得到:

        .file   "try.c"
        .text
        .p2align 4,,15
.globl foo_i
        .type   foo_i, @function
foo_i:
        movl    $1, %eax
        ret
        .size   foo_i, .-foo_i
        .p2align 4,,15
.globl foo_u
        .type   foo_u, @function
foo_u:
        movl    4(%esp), %eax
        leal    69(%eax), %edx
        cmpl    %eax, %edx
        seta    %al
        ret
        .size   foo_u, .-foo_u
        .ident  "GCC: (Debian 4.4.4-7) 4.4.4"
        .section        .note.GNU-stack,"",@progbits

你可以看到它foo_i()比效率更高foo_u().这是因为无标符算术溢出由标准定义为"环绕",因此(a + 69u)很可能小于aif a非常大,因此必须有这种情况的代码.另一方面,有符号算术溢出是未定义的,因此GCC将继续并假设有符号算术不会溢出,因此(a + 69) 不能小于a.因此,不加选择地选择无符号类型会不必要地影响性能.



4> 5ound..:

C++的创建者Bjarne Stroustrup在他的"C++编程语言"一书中警告过使用无符号类型:

无符号整数类型非常适合将存储视为位数组的用途.使用无符号而不是int来再获得一位来表示正整数几乎不是一个好主意.通过声明无符号变量来确保某些值为正的尝试通常会被隐式转换规则所取代.



5> 6502..:

答案是肯定的.无论类型的名称是什么,C和C++的"unsigned"int类型不是"始终为正整数".如果你试图将类型读作"非负",那么C/C++无符号整数的行为是没有意义的......例如:

两个无符号的差异是一个无符号数(如果你把它读成"两个非负数之间的差异是非负的",则没有意义)

添加int和unsigned int是无符号的

有一个从int到unsigned int的隐式转换(如果你将unsigned作为"非负"读取,那么它是相反的转换,这是有意义的)

如果你声明一个函数接受一个无符号参数,当有人传递一个负数int时,你只需将其隐式转换为一个巨大的正值; 换句话说,使用unsigned参数类型无法帮助您在编译时和运行时都找不到错误.

实际上,无符号数对于某些情况非常有用,因为它们是环"整数 - 模N"的元素,其中N是2的幂.当您想要使用模数运算或位掩码时,无符号整数非常有用; 它们不适用于数量.

不幸的是,在C和C++中,无符号也被用来表示非负数量,当能够使用32k或64k的小整数被认为是一个很大的区别时,能够使用全部16位.我把它基本上归类为历史事故......你不应该试着读它里面的逻辑,因为没有逻辑.

顺便说一句,在我看来这是一个错误...如果32k还不够,那么很快64k也不够; 因为在我看来一个额外的位,滥用模数整数是一个太高的代价.当然,如果存在或定义了适当的非负类型,那将是合理的......但是无符号语义对于将其用作非负数而言是错误的.

有时您可能会发现谁说无符号是好的,因为它"文档"您只需要非负值...但是该文档仅适用于那些实际上并不知道无符号适用于C或C++的人.对于我来说,看到用于非负值的无符号类型只是意味着编写代码的人不理解该部分的语言.

如果你真的理解并想要无符号整数的"包装"行为,那么它们就是正确的选择(例如,当我处理字节时,我几乎总是使用"unsigned char"); 如果你不打算使用包装行为(并且这种行为对你来说只是一个问题,就像你所显示的差异一样)那么这是一个明确的指示,即无符号类型是一个糟糕的选择而你应该坚持平原.

这是否意味着C++ std::vector<>::size()返回类型是一个糟糕的选择?是的......这是一个错误.但是,如果你这样说准备好被称为坏名字的人不明白"无符号"名称只是一个名字......重要的是行为,这是一种"模数"行为(并且没有人们会认为容器大小的"modulo-n"类型是明智的选择.


-1.呃,我的意思是+4294967295 :)`unsigned`的语义是不合逻辑的.

6> Jens Gustedt..:

我似乎与这里的大多数人不一致,但我发现unsigned类型非常有用,但不是原始的历史形式.

如果您因此坚持类型为您表示的语义,则应该没有问题:对文件偏移使用size_t(无符号)数组索引,数据偏移等off_t(签名).使用ptrdiff_t(签名)指针的差异.使用uint8_t的小型无符号整数,并int8_t为签署的.并且您避免了至少80%的可移植性问题.

不要使用int,long,unsigned,char如果你不得.它们属于历史书籍.(有时你必须,错误返回,比特字段,例如)

并回到你的例子:

bitsAvailable – mandatoryDataSize >= optionalDataSize

可以很容易地重写为

bitsAvailable >= optionalDataSize + mandatoryDataSize

这不能避免潜在溢出的问题(assert是你的朋友),但我认为你会更接近你想要测试的想法.



7> Stephen Cano..:
if (bitsAvailable >= optionalDataSize + mandatoryDataSize) {
    // Optional data fits, so add it to the header.
}

没有错误,只要mandatoryDataSize + optionalDataSize不能溢出无符号整数类型 - 这些变量的命名使我相信这可能就是这种情况.



8> Pavel Minaev..:

您无法完全避免可移植代码中的无符号类型,因为标准库中的许多typedef是无符号的(最值得注意的size_t),并且许多函数返回那些(例如std::vector<>::size()).

也就是说,由于您概述的原因,我通常更愿意尽可能坚持签名类型.这不仅仅是你提出的情况 - 在混合签名/无符号算术的情况下,signed参数被悄悄地提升为unsigned.

推荐阅读
凹凸曼00威威_694
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有