为什么喜欢在C++中签名无符号?

Mor*_*hai 68 c++ optimization

我想更好地了解为什么选择int结束unsigned

就个人而言,除非有正当理由,否则我从未喜欢过签名的价值观.例如,数组中的项目数,或字符串的长度,或内存块的大小等,因此这些事情通常不可能是负面的.这样的价值没有任何意义.为什么喜欢int在所有这些情况下误导?

我问这个问题是因为Bjarne Stroustrup和Chandler Carruth都给出了建议,而int不是unsigned 在这里(约12:30').

我可以看到使用intover shortlong- 的参数int是目标机器架构的"最自然"的数据宽度.

但签署无条件总是让我生气.在典型的现代CPU架构上,签名值是否真的更快?是什么让他们更好?

小智 39

根据评论中的要求:我更喜欢int而不是unsigned因为......

  1. 它更短(我很认真!)

  2. 它更通用,更直观(即我喜欢能够假设它1 - 2是-1而不是一些不起眼的巨大数字)

  3. 如果我想通过返回超出范围的值来发出错误信号怎么办?

当然有反驳论据,但这些是我喜欢将整数声明为int代替的主要原因unsigned.当然,这并非总是如此,在其他情况下,a unsigned只是一个更好的工具,我只是回答"为什么有人会更喜欢违约签署"的问题.

  • 我认为说它更短的需要(我很认真)很难过. (8认同)
  • @ H2CO3:如果`int`的正范围足以满足您的需要,它们'UINT_MAX`是一个非常好的超出范围的值,用于指示错误条件.实际上,为了这个目的,可以在代码中使用`-1`,因为它在转换为`unsigned`时会计算为'UINT_MAX`. (7认同)
  • 输入'unsigned'并不完全是腕管诱导;) (5认同)
  • @BobbyDigital确实.应该更加担心"效率",更多的是关于正确性,可读性和风格. (2认同)

Pra*_*mar 32

让我解释一下视频,正如专家们简洁地说的那样.

Andrei Alexandrescu:

  • 没有简单的准则.
  • 在系统编程中,我们需要不同大小和符号的整数.
  • 许多转换和神秘的规则控制着算术(比如for auto),所以我们需要小心.

钱德勒卡罗斯:

  • 这是一些简单的指导方针:
    1. 除非需要二进制补码算术或位模式,否则使用有符号整数
    2. 使用足够的最小整数.
    3. 否则,int如果您认为可以计算项目,则使用64位整数,如果它甚至超过您想要的数量.
  • 不用担心并使用工具告诉您何时需要不同的类型或尺寸.

Bjarne Stroustrup:

  • 使用int直到你有理由不这样做.
  • 仅对位模式使用无符号.
  • 永远不要混合签名和未签名

关于签署规则的谨慎,我的一句话从专家那里拿走:

使用适当的类型,当您不知道时,使用int直到你知道.

  • 我觉得这个答案很有意思; 但是,如果您认为可以计算项目,您能否详细说明"使用`int`"?特别是,当我们必须与`size_t`变量进行比较时,这不会与"从不与无符号混合签署"的规则发生冲突吗? (2认同)
  • 他刚刚在我的OP视频中引用了发言者给出的答案.他们确实第二次回到这个话题,包括Herb Sutter说,在size_t的情况下,标准库"弄错了......对不起." (2认同)

use*_*342 19

几个原因:

  1. 算术on unsigned总是产生无符号,这可能是一个问题,当减去可以合理地导致负结果的整数量时 - 想想减去货币数量以产生平衡,或者数组索引来产生元素之间的距离.如果操作数是无符号的,那么你得到一个完美定义但几乎肯定没有意义的结果,并且result < 0比较总是错误的(幸运的是现代编译器会警告你).

  2. unsigned具有污染算术的令人讨厌的特性,它与有符号整数混合在一起.因此,如果添加有符号和无符号并询问结果是否大于零,则可能会被咬,特别是当无符号整数类型隐藏在a后面时typedef.

  • `signed1 - signed2`也不安全,因为如果它溢出你会得到未定义的行为. (25认同)
  • 除非`signed1`和/或`signed2`为"large"(超过最大可表示值的一半),否则签名的大小写不会溢出.相比之下,从无符号值中减去*anything*会导致它换行. (6认同)
  • #2咬了我一次.Aaaargh! (2认同)
  • 我认为这些是真正的原因. (2认同)
  • @Mordachai:有很多理由不创建这个例外.它会阻止编译器进行许多有用的优化.有关讨论,请参见http://blog.regehr.org/archives/213. (2认同)
  • @RobNapier`for(unsigned i = count; i--;)// what`最短的循环结构,并且对于无符号的人非常满意:-) (2认同)

AnT*_*AnT 19

没有理由喜欢signedunsigned从单纯的社会学的,放在一边,即一些人认为,平均程序员不胜任和/或足够细心来讲编写正确的代码unsigned类型.这通常是各种"发言者"使用的主要推理,无论这些发言者如何受到尊重.

实际上,有能力的程序员可以快速开发和/或学习基本的编程习惯和技能,使他们能够根据无符号整数类型编写适当的代码.

还要注意,在C和C++语言的其他部分中,有符号和无符号语义之间的基本区别总是存在(表面上不同的形式),如指针算法和迭代算术.这意味着在一般情况下,程序员实际上没有选择避免处理特定于无符号语义的问题以及它带来的"问题".也就是说,无论你是否想要它,你必须学会​​使用在它们的左端突然终止并在此处终止的范围(不在远处的某个地方),即使你坚决避免unsigned整数.

另外,正如您可能知道的那样,标准库的许多部分已经非常依赖于unsigned整数类型.强制签名算法进入混合,而不是学习使用无符号算法,只会导致灾难性的错误代码.

在某些上下文中首选的唯一真正理由signed是,在混合整数/浮点代码中,signed整数格式通常由FPU指令集直接支持,而unsigned格式根本不受支持,使编译器生成额外的代码浮点值和unsigned值之间的转换.在这样的代码signed类型中可能表现更好.

但与此同时,纯粹的整数代码unsigned类型可能比signed类型更好.例如,整数除法通常需要额外的校正代码以满足语言规范的要求.仅在负操作数的情况下才需要校正,因此在没有真正使用负操作数的情况下浪费CPU周期.

在我的实践中,我竭尽全力坚持到unsigned任何可能的地方,并且signed只有在我必须的时候才能使用.

  • @Michael:不是.这是那些"听起来不错"的虚假智慧之一.就像一个人们用来证明"尤达比较"语法的那种语言一样.例如,他们说应该写'3 == x`而不是'x == 3`以避免意外使用赋值而不是`==`.但实际上这是一个永远不会发生的假问题.使用普通语法`x == 3`的人根本就不会犯这个错误.`unsigned`也是一样的.一个称职的开发人员永远不会编写像`i - 3 <0`的代码,当表达它的自然方式是`i <3`并且它是"符号独立"时. (6认同)
  • 我不同意.这不是关于能力,而是关于什么是共同的.(比如何时使用class vs struct)有很多有能力的程序员可以完美地告诉你他们什么时候可以使用未签名或签名的值,但是出于这些"社会学"原因仍然使用签名.(我认为甚至缩进也用于此目的 - 是的,目的是使代码更容易阅读,但这也是'int`的要点). (5认同)
  • “但实际上,这是一个永远不会发生的假问题”,好吧,我想我或同事身上发生过十几次这种情况,并且在调试中花费了数小时,这根本不算数。 (2认同)

sup*_*cat 9

C中的整数类型和从中派生的许多语言都有两个一般用例:表示数字,或代表抽象代数环的成员.对于那些不熟悉抽象代数的人来说,一个环背后的主要概念是,加上,减去或乘以一个环的两个项应该产生该环的另一个项 - 它不应该崩溃或在环外产生一个值.在32位机器上,将无符号0x12345678添加到无符号0xFFFFFFFF不会"溢出" - 它只是产生结果0x12345677,这是为整数环同义的mod 2 ^ 32定义的(因为将0x12345678添加到0xFFFFFFFF的算术结果) ,即0x112345677,与0x12345677 mod 2 ^ 32一致.

从概念上讲,两个目的(代表数字,或表示整数环的成员一致的mod 2 ^ n)可以由有符号和无符号类型提供服务,并且许多操作对于两种用例都是相同的,但是存在一些差异.除此之外,除了正确的算术总和之外,不应该尝试添加两个数字.虽然是否需要一种语言来生成必要的代码以保证它不会(例如,将抛出异常),但是有人可能会争辩说,对于使用整数类型来表示数字的代码,这样的行为会更好产生一个算术上不正确的值,编译器不应该被禁止这样做.

C标准的实现者决定使用有符号整数类型来表示数字和无符号类型,以表示整数的代数环的成员全等mod 2 ^ n.相比之下,Java使用有符号整数来表示这些环的成员(尽管它们在某些上下文中的解释不同;例如,不同大小的有符号类型之间的转换,与无符号整数之间的行为不同),Java既没有无符号整数也没有任何整数在所有非例外情况下表现为数字的原始整数类型.

如果一种语言为数字和代数环数提供了有符号和无符号表示的选择,则使用无符号数表示始终为正的数量可能是有意义的.但是,如果只有无符号类型表示代数环的成员,并且表示数字的唯一类型是带符号的类型,那么即使值始终为正,也应使用设计用于表示数字的类型来表示.

顺便提一下,(uint32_t)-1为0xFFFFFFFF的原因在于将有符号值转换为无符号的等效于添加无符号零的事实,并且将无符号值的整数添加到无符号值中定义为向/从中添加或减去其大小.根据代数环的规则的无符号值,其指定如果X = YZ,则X是该环的唯一成员,例如X + Z = Y. 在无符号数学中,0xFFFFFFFF是唯一的数字,当加到无符号1时,产生无符号零.

  • 挑剔:字段允许除除附加标识之外的任何内容.如果只有`+`,`-`和`*`,那么代数结构就是*环*. (2认同)

vy3*_*y32 8

现代架构的速度是相同的.问题unsigned int在于它有时会产生意外行为.这可能会产生不会出现的错误.

通常,当您从值中减去1时,该值会变小.现在,使用两个signedunsigned int变量,将有一个时间减去1创建一个更大的值.之间的主要区别unsigned intint是与unsigned int产生自相矛盾的结果值是一个常用的值--- 0 ---而与签约数为安全远离正常运营.

至于为错误值返回-1 ---现代思维是抛出异常比测试返回值更好.

确实,如果你正确地捍卫你的代码,你将不会遇到这个问题,如果你在任何地方使用unsigned,你都可以(如果你只是添加,从不减去,并且你永远不会接近MAX_INT).我到处都使用unsigned int.但它需要很多纪律.对于很多程序,您可以使用int并花时间处理其他错误.

  • "`unsigned int`的问题在于它有时(在溢出的情况下)会产生意外行为." 而且`signed int`的问题在于它有时(在溢出的情况下)会产生未定义的行为.鉴于这些选择,`unsigned`看起来很不错;) (13认同)
  • (当然,对于完全不同的值会发生溢出,因此对于签名类型,溢出很少会出现问题) (2认同)

Mat*_*son 7

回答实际问题:对于大量的事情,这并不重要.int可以更轻松地处理诸如减法之类的事情,第二个操作数大于第一个操作数,你仍然得到"预期"的结果.

99.9%的情况绝对没有速度差异,因为有符号和无符号数字的唯一指令是:

  1. 使数字更长(填写有符号的符号或无符号的零) - 两者都需要同样的努力.
  2. 比较 - 一个有符号的数字,如果任何一个数字是负数,处理器必须考虑.但同样,与有符号或无符号数字进行比较的速度相同 - 它只是使用不同的指令代码来说"具有最高位设置的数字小于最高位未设置的数字"(基本上).[讽刺的是,它几乎总是使用不同的比较结果的操作 - 最常见的情况是条件跳转或分支指令 - 但无论哪种方式,它都是相同的努力,只是输入被认为意味着略有不同的东西].
  3. 乘以除.显然,如果结果是有符号乘法,则需要对结果进行符号转换,如果设置了其中一个输入的最高位,则无符号不应改变结果的符号.而且,努力(尽可能接近我们)是相同的.

(我认为还有一两个其他情况,但结果是一样的 - 无论是签名还是未签名都无关紧要,执行操作的努力对两者都是一样的).


Tem*_*Rex 6

  1. int默认使用:它与其他语言相比更好

    • 最常见的域使用是常规算术,而不是模运算
    • int main() {} // see an unsigned?
    • auto i = 0; // i is of type int
  2. unsigned用于模运算和bit-twiddling(特别是移位)

    • 与常规算术有不同的语义,确保它是你想要的
    • 位移符号类型很微妙(参见@ChristianRau的评论)
    • 如果您需要在32位计算机上使用> 2Gb向量,请升级您的操作系统/硬件
  3. 切勿混合有符号和无符号算术

    • 对此的规则是复杂和令人惊讶的(根据相对类型大小,任何一个都可以转换为另一个)
    • 打开-Wconversion -Wsign-conversion -Wsign-promo(gcc比Clang好)
    • 标准库弄错了std::size_t(引自GN13视频)
    • 如果可以的话,使用range-for,
    • for(auto i = 0; i < static_cast<int>(v.size()); ++i) 如果你必须
  4. 除非您确实需要,否则请勿使用短型或大型

    • 当前的体系结构数据流非常适合32位非指针数据(但请注意@BenVoigt关于较小类型的缓存效果的注释)
    • charshort节省空间,但遭受整体促销
    • 你真的会指望一切int64_t吗?

  • 你已经给出了一套指导方针,但很少有解释.你的复杂的'for`循环特别需要一些'splainin'.(我会说它是一个*坏*准则 - 使用`for(auto i = 0u; i <v.size(); ++ i)`而不是! - 或者,甚至更好,[`for( auto i:indices(x))`](https://github.com/klmr/cpp11-range).) (2认同)