代码之家  ›  专栏  ›  技术社区  ›  Steve Hanov

对于C中的数字,是否应该始终使用“int”,即使它们不是负数?

  •  44
  • Steve Hanov  · 技术社区  · 14 年前

    我总是使用 无符号整型 对于永远不应该为负的值。但是今天我 在我的代码中注意到这种情况:

    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 而不是 无符号整型 对于数字,即使他们 不能是否定的吗?

    16 回复  |  直到 14 年前
        1
  •  25
  •   Jonathan Leffler    8 年前

    我应该一直…

    “我应该一直…”的答案几乎肯定是“不”,有很多因素决定您是否应该使用数据类型-一致性很重要。

    但是,这是一个非常主观的问题,很容易把没有签名的人搞砸:

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

    导致无限循环。

    这就是为什么有些风格指南包括 Google's C++ Style Guide 劝阻 unsigned 数据类型。

    在我个人的观点中,我没有遇到许多由这些未签名数据类型的问题引起的错误。我会说,使用断言检查代码并明智地使用它们(在执行算术时更少)。

        2
  •  89
  •   BlueRaja - Danny Pflughoeft    14 年前

    有一件事没被提到 交换有符号/无符号的数字可能导致安全错误 . 这是一个 大的 问题,因为标准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 已签名,但在传递到时转换为未签名 memcpy . 因此,将长度设置为 SHRT_MIN 将验证 <= 512 测试,但原因 曼皮西 要将超过512个字节复制到缓冲区,这允许攻击者覆盖堆栈上的函数返回地址,并(经过一点工作)接管您的计算机!

    你可能天真地说, “很明显,长度需要 西泽特 或检查为 >= 0 “我永远不会犯这个错误。” . 但是,我保证,如果你写过任何不平凡的东西,你也写过。作者也是如此 Windows , Linux , BSD , Solaris , Firefox , OpenSSL , Safari , MS Paint , Internet Explorer , Google Picasa , Opera , Flash , Open Office , Subversion , Apache , Python , PHP , Pidgin , Gimp ,… 不停地 …-这些都是 明亮的 人谁 工作 知道安全。

    简而言之, 总是使用 西泽特 尺寸。

    人, programming is hard .

        3
  •  11
  •   Nietzche-jou    14 年前

    在某些情况下,应使用无符号整数类型:

    • 您需要将数据视为纯二进制表示。
    • 您需要使用无符号数得到的模算术的语义。
    • 您必须与使用无符号类型(例如,接受/返回的标准库例程)的代码进行接口 size_t 价值观。

    但是对于一般的算术,当你说某个“不能是负数”时,并不一定意味着你应该使用无符号类型。因为你 可以 把一个负值放在一个无符号中,就是当你把它取出来的时候,它会变成一个非常大的值。因此,如果您的意思是禁止使用负值,例如对于基本平方根函数,那么您要声明函数的前提条件,并且应该断言。你不能断言什么是不可能的,是;你需要一种方法来保持带外值,这样你就可以测试它们(这是后面的逻辑类型) getchar() 返回一个 int 而不是 char )

    此外,选择有符号与无符号也会对性能产生实际影响。请看下面的(人为)代码:

    #include <stdbool.h>
    
    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) 很可能比 a 如果 是非常大的,因此必须有这个案例的代码。另一方面,有符号算术溢出是未定义的,因此GCC将继续进行并假定有符号算术 溢出,等等 (a + 69) 不能 永远比 . 因此,不加区别地选择无符号类型会不必要地影响性能。

        4
  •  10
  •   5ound    14 年前

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

    无符号整数类型是理想的 用于将存储视为 数组。使用无符号而不是 int再获得一个位来表示 正整数几乎不是 好主意。试图确保 有些值是正的,通过声明 无符号变量通常为 被隐式转换所击败 规则。

        5
  •  9
  •   6502    14 年前

    答案是肯定的。C和C++的“无符号”int类型不是一个“总是正整数”,无论该类型的名称是什么样子。如果您尝试读取类型为“非负”的话,C/C++无符号int的行为就没有意义了。例如:

    • 两个无符号的差是一个无符号数(如果将其读作“两个非负数之间的差是非负的”,则没有意义)
    • 一个int和一个无符号int的加法是无符号的。
    • 存在从int到unsigned int的隐式转换(如果将unsigned读取为“非负”,则 相反的 有意义的转换)
    • 如果声明一个函数在有人传递负in t时接受一个无符号参数,那么只需将其隐式转换为一个巨大的正值;换句话说,使用无符号参数类型既不帮助您在编译时也不帮助您在运行时查找错误。

    实际上,无符号数在某些情况下非常有用,因为它们是环“整数模n”的元素,n是2的幂。当您想使用模n运算或作为位掩码时,无符号整数很有用;它们作为数量不有用。

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

    顺便说一下,我认为那是个错误…如果32K不够,那么很快64K也不够;我认为仅仅因为一个额外的位而滥用模整数的代价太高了。当然,如果有一个合适的非负型存在或被定义的话,这样做是合理的。但无符号语义将其用作非负语义是错误的。

    有时你会发现谁说无符号是好的,因为它“文档”你只需要非负值…然而,这些文档对于那些实际上不知道如何为C或C++进行无符号工作的人来说是有价值的。对于我来说,看到一个用于非负值的无符号类型仅仅意味着编写代码的人不理解该部分的语言。

    如果你真的明白 想要 无符号整数的“包装”行为是正确的选择(例如,我处理字节时几乎总是使用“unsigned char”);如果不使用包装行为(这种行为对您来说是个问题,就像在显示的差异的情况下一样),那么这是无符号整数体育是一个糟糕的选择,你应该坚持简单的整数。

    这是否意味着C++? std::vector<>::size() 返回类型是错误的选择?对。。。这是个错误。但是,如果你这么说的话,准备好被那些不理解“未签名”的名字只是一个名字的人称作坏名字…它计算的是行为,这是一种“modulo-n”行为(没有人会认为容器大小的“modulo-n”类型是明智的选择)。

        6
  •  7
  •   Aconcagua    6 年前

    我似乎不同意这里的大多数人,但我发现 unsigned 类型非常有用,但在 未经加工的 历史形态。

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

    不要使用 int , long , 未签名的 , char 如果你不能的话。它们属于历史书。(有时必须返回错误、位字段,例如)

    回到你的例子:

    bitsAvailable – mandatoryDataSize >= optionalDataSize

    可以很容易地重写为

    bitsAvailable >= optionalDataSize + mandatoryDataSize

    这并不能避免潜在的溢出问题( assert 是你的朋友),但我想这会让你更接近你想测试什么的想法。

        7
  •  6
  •   Stephen Canon    14 年前
    if (bitsAvailable >= optionalDataSize + mandatoryDataSize) {
        // Optional data fits, so add it to the header.
    }
    

    没有bug,只要mandatorydatasize+optionaldatasize不能溢出无符号整数类型——这些变量的命名让我相信情况可能是这样的。

        8
  •  6
  •   Pavel Minaev    14 年前

    在可移植代码中不能完全避免无符号类型,因为标准库中的许多typedef都是无符号的(最明显的是 size_t ,许多函数返回这些函数(例如 std::vector<>::size() )

    也就是说,出于您所描述的原因,我通常更喜欢尽可能使用签名类型。这不仅仅是你提出的情况-在混合有符号/无符号算术的情况下,有符号参数被悄悄地提升为无符号。

        9
  •  3
  •   Brian    14 年前

    从埃里克·利珀茨的一篇博客文章的评论中(见 here ):

    杰弗里·L·惠特利奇

    我曾经开发过一个系统, 负值作为 参数,所以不是验证 参数值为 非阴性,我想是 只使用uint的好主意。我 很快发现每当我 把这些值用于任何事情(比如 调用bcl方法),它们是 转换为有符号整数。这个 意味着我必须确认 值没有超过带符号的 顶端的整数范围,所以我 一无所获。而且,每次 代码被调用 使用中(通常从BCL收到 函数)必须转换为 尤因茨。没过多久我 把所有的uint都改回int 并接受了所有不必要的铸造 出来。我仍然需要验证 数字不是负数,而是代码 更干净!

    利珀特

    我自己说得再好不过了。 你几乎不需要 uint,它们不符合cls。 代表一个小的 整数带有“int”,即使存在 其中的值是否超出 范围。一个好的经验法则:只使用 “uint”表示您所在的位置 与非托管代码互操作 这需要uint,或者 所讨论的整数显然用作 一组位,不是数字。总是 尽量避免在公共接口中使用。 -埃里克

        10
  •  2
  •   Michael Burr    14 年前

    在什么情况下 (bitsAvailable – mandatoryDataSize) 当类型无符号且 bitsAvailable < mandatoryDataSize 这是有时使用签名类型的原因,即使数据不应为负数。

    我认为没有硬性和快速性的规则——我通常“默认”为对没有理由为负的数据使用无符号类型,但是您必须确保算术包装不会暴露错误。

    同样,如果使用带符号的类型,有时还必须考虑溢出:

    MAX_INT + 1
    

    关键是,在为这些类型的错误执行算术运算时必须小心。

        11
  •  2
  •   user393170    14 年前

    不,您应该使用适合您的应用程序的类型。没有黄金法则。例如,在小型微控制器上,如果可能的话,使用8或16位变量(通常是本机数据路径大小),速度更快,内存效率更高,但这是一种非常特殊的情况。我还建议尽可能使用stdint.h。如果您使用的是Visual Studio,则可以找到BSD许可的版本。

        12
  •  1
  •   Remy Lebeau    14 年前

    如果存在溢出的可能性,则在计算期间将值分配给下一个最高的数据类型,即:

    void CreateRequestHeader( unsigned int bitsAvailable, unsigned int mandatoryDataSize, unsigned int optionalDataSize ) 
    { 
        signed __int64 available = bitsAvailable;
        signed __int64 mandatory = mandatoryDataSize;
        signed __int64 optional = optionalDataSize;
    
        if ( (mandatory + optional) <= available ) { 
            // Optional data fits, so add it to the header. 
        } 
    } 
    

    否则,只需单独检查值而不是计算:

    void CreateRequestHeader( unsigned int bitsAvailable, unsigned int mandatoryDataSize, unsigned int optionalDataSize ) 
    { 
        if ( bitsAvailable < mandatoryDataSize ) { 
            return;
        } 
        bitsAvailable -= mandatoryDataSize;
    
        if ( bitsAvailable < optionalDataSize ) { 
            return;
        } 
        bitsAvailable -= optionalDataSize;
    
        // Optional data fits, so add it to the header. 
    } 
    
        13
  •  0
  •   Timo Geusch    14 年前

    您需要查看对变量执行的操作的结果,以检查是否可以溢出/溢出-在您的情况下,结果可能是负数。在这种情况下,最好使用有符号的等价物。

        14
  •  0
  •   InsertNickHere    14 年前

    我不知道它在C中是否可行,但在这个例子中,我将x-y的值转换为in t。

        15
  •  0
  •   nmichaels    14 年前

    如果你的号码 应该 永远不要小于零,但有机会是<0,无论如何,使用有符号整数,并撒断言或其他运行时检查。如果您实际使用的是32位(或64位或16位,具体取决于您的目标体系结构)值,其中最重要的位表示除“-”之外的其他值,则只应使用无符号变量来保存这些值。如果一个数字应该始终为正,而不是为零,则更容易检测整数溢出,因此如果不需要该位,请使用带符号的位。

        16
  •  0
  •   John    14 年前

    假设你需要从1数到50000。您可以使用一个双字节无符号整数来实现这一点,但不能使用双字节有符号整数(如果空间非常重要)。