为什么在C ++中更喜欢无符号签名?[关闭]


71

我想更好地了解为什么选择intunsigned

就个人而言,除非有正当理由,否则我从不喜欢带符号的值。例如,数组中的项数,字符串的长度或存储块的大小等,因此这些东西通常不可能为负数。这样的值没有可能的含义。为什么int在所有此类情况下都令人误解,还是选择它呢?

我问这个,因为这两个Bjarne的Stroustrup的和钱德勒卡鲁斯给了建议,喜欢intunsigned 这里(大约12:30' )

我可以看到使用intovershortlong-的参数int是目标机器体系结构的“最自然的”数据宽度。

但是无人签名总是让我很烦。在典型的现代CPU架构上,带符号的值是否真的更快?是什么使它们更好?


13
为了使那些现在不能观看1小时视频的读者受益:Stroustrup和Carruth对于他们为什么喜欢签名有何看法?
us2012年

12
我更喜欢int而不是unsigned因为:1.它更短(我是认真的!),2.它更通用,更直观(即,我希望能够假设它1 - 2为-1而不是一些晦涩的巨大数字),3.什么?如果我想通过返回超出范围的值来表示错误?

6
11:08:“有没有简单的指导,可以被赋予”
Robᵩ

3
@john好的,但是他们至少需要使int溢出定义为行为,并使二进制补码为强制性。否则,对于现在int无法解决unsigned ints的许多合理任务,将是根本无法使用的。
Christian Rau

2
@john:在大多数情况下优先int选择unsigned是一回事;unsigned从语言中删除更为严厉。
基思·汤普森

Answers:


33

让我解释一下该视频,就像专家们简洁地说的那样。

安德烈·亚历山德列斯库(Andrei Alexandrescu)

  • 没有简单的准则。
  • 在系统编程中,我们需要不同大小和符号的整数。
  • 许多转换和不可思议的规则控制着算术运算(如auto),因此我们需要小心。

钱德勒·卡鲁斯Chandler Carruth)

  • 以下是一些简单的准则:
    1. 除非需要二进制补码算术或位模式,否则请使用带符号整数
    2. 使用足够的最小整数。
    3. 否则,int如果您认为自己可以计算项目,请使用;如果64位整数甚至超过您想要的数量,请使用64位整数。
  • 不用担心,并在需要其他类型或尺寸时使用工具告诉您。

Bjarne Stroustrup

  • 使用int直到你有一个理由不这么做。
  • 无符号仅用于位模式。
  • 切勿混用已签名和未签名

除了对签名规则保持警惕外,我的一句话使专家无法理解:

使用适当的类型,如果不知道,请使用int直到知道为止。


2
我觉得这个答案很有趣;但是,您能否详细说一下“int如果您认为可以算数的话就使用”?特别是,当我们必须与size_t变量进行比较时,这是否与“从不混合有符号与无符号”规则冲突?
Alberto Moriconi

2
他只是在我的OP中引用了视频中发言人的回答。他们的确再次回来讨论这个话题,包括Herb Sutter说在size_t的情况下,标准库“错了……对此感到抱歉”。
Mordachai 2013年

1
@Alberto关于“int如果您认为可以计数的话使用”,我们实际上将其与(signed)之类的类型进行了对比,该类型long不会挑战“绝不使用无符号签名”规则。
Prashant Kumar

40

根据评论中的要求:我宁愿int而不是unsigned因为...

  1. 它更短(我是认真的!)

  2. 它更通用,更直观(即,我希望能够假设它1 - 2为-1而不是一些晦涩的巨大数字)

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

当然有一些反论点,但这是我喜欢将整数声明为int而不是的主要原因unsigned。当然,这并不总是正确的,在其他情况下,这unsigned仅仅是执行任务的更好工具,我只是在回答“为什么有人会更喜欢默认为签名”这一问题。


8
我认为很难说需要更短的时间(我很认真)。
ChiefTwoPencils 2013年

2
@BobbyDigital确实如此。一般而言,应该较少担心“效率”,而应该更多地考虑正确性,可读性和样式。

7
@ H2CO3:如果的正范围int足以满足您的需求,则它们UINT_MAX是一个很好的超出范围的值,用于指示错误情况。实际上,-1可以将其用于代码中,因为它UINT_MAX在转换为时求值为unsigned
AnT

5
键入“ unsigned”并不完全是腕管诱导;)
Mordachai 2013年

1
@Mordachai不是关于可写性,而是关于可读性。

20

几个原因:

  1. 算术运算unsigned始终会产生无符号数,这在减去可能合理导致负数的整数数量时可能会出现问题,请考虑减去货币数量以产生余额,或者减去数组索引以产生元素之间的距离。如果操作数是无符号的,则会得到一个完美定义的结果,但几乎可以肯定是毫无意义的结果,并且result < 0比较将始终为false(幸运的是,现代编译器会警告您)。

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


2
#2曾经咬过我一次。啊!

26
signed1 - signed2也不安全,因为如果溢出,您将得到未定义的行为。
Ben Voigt

2
我认为这些是真正的原因。
MirroredFate 2013年

6
除非signed1和/或signed2为“大”(超过最大可表示值的一半),否则签名的案例不会溢出。相比之下,从无符号值中减去任何东西都会导致其自动换行。
超级猫

2
@Mordachai:没有创建此异常的原因很多。这将阻止编译器进行许多有用的优化。有关讨论,请参见blog.regehr.org/archives/213
Rob Napier 2013年

19

没有理由喜欢signedunsigned从单纯的社会学的,放在一边,即一些人认为,平均程序员不胜任和/或细心足够的条件来写正确的代码unsigned类型。这通常是各种“发言人”所使用的主要理由,无论这些发言人可能有多受尊重。

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

还要注意,在C和C ++语言的其他部分(如指针算术和迭代器算术)中,有符号和无符号语义之间的根本区别总是存在(表面上不同)。这意味着在一般情况下,程序员实际上并没有避免处理特定于未签名语义及其带来的“问题”的问题的选择。也就是说,无论您是否想要,即使您坚决避免使用unsigned整数,也必须学会使用在其左端突然终止并在此处终止(不在距离的某处)的范围工作。

而且,您可能已经知道,标准库的许多部分已经unsigned非常依赖整数类型。强迫将有符号算术混用,而不是学习使用无符号算术,只会导致灾难性的错误代码。

在某些情况下,我想到的唯一真正的理由signed是,在混合整数/浮点代码中signed,FPU指令集通常直接支持整数格式,而unsigned根本不支持格式,这使得编译器可以为浮点值和unsigned值之间的转换。在此类代码中,性能signed可能会更好。

但是同时纯整数代码unsigned类型的性能可能比signed类型。例如,整数除法经常需要附加的纠正代码,以满足语言规范的要求。仅在负数操作数的情况下才需要进行校正,因此,在没有真正使用负数操作数的情况下,这会浪费CPU周期。

在我的实践中,我竭尽unsigned所能地坚持下去,并且signed仅在确实需要时才使用。


5
我不同意。这与能力无关,而与普通能力有关。(例如何时使用类vs结构)何时有很多称职的程序员可以完美地告诉您何时可以使用无符号或有符号的值,但是出于这些“社会学”的原因,仍然要使用有符号的值。(我认为甚至缩进都用于此目的-是的,目的是使代码更易于阅读,但这也是重点int)。
Luchian Grigore

我倾向于同意注释,因为无论何时变量的值都将被无符号化时都使用unsigned,就像在一个循环中,它只是一个正值,for (unsigned int i=0; i < 5; ++i)我觉得这给了它一点额外的类型说明符,但是我也看到了您的观点仅仅拥有int本身就使代码更加简洁。
bjackfly

6
@迈克尔:不是。这只是“正确”的那些伪造智慧之一。例如,就像人们用来证明“ Yoda比较”语法的代码一样。例如,他们说应该改写,3 == x或者x == 3避免不小心用赋值代替==。但实际上,这是一个永远不会发生的假问题。使用普通语法的人x == 3根本不会犯这个错误。与相同unsigned。有能力的开发人员永远不会编写像这样的代码i - 3 < 0,而自然的方式i < 3就是“与代码无关”。
AnT

1
@AnT:减去并与零比较可能不是特别有用,但是与另一个数相减并比较可能是有用的。我认为(uint32_t)(x-y) < z这是一种检查是否y在一定距离之内x但不低于此距离的合理方法,尽管如果可以用惯用语言编写而不必指定特定类型会更好(尽管0u+x-y < z在所有情况下都应该工作,x并且y相同的无符号类型,无论它大于还是小于int,我都不认为这0u+是公认的惯用语
。– supercat

1
But in reality it is a fake problem that never happens 好吧,我想这对我或同事所经历的十几个时间以及调试所花费的时间就不算了。
吉恩-迈克尔Celerier

9

C和从其派生的许多语言中的整数类型有两种通用用法:表示数字或表示抽象代数环的成员。对于那些不熟悉抽象代数的人,环的主要概念是,将环的两个项目相加,相减或相乘应产生该环的另一个项目-不应崩溃或在环之外产生值。在32位计算机上,将无符号0x12345678添加到无符号0xFFFFFFFF不会“溢出”-它只会产生结果0x12345677,该结果是为整数环mod 2 ^ 32定义的(因为将0x12345678添加到0xFFFFFFFF的算术结果) ,即0x112345677,等同于0x12345677 mod 2 ^ 32)。

从概念上讲,这两个目的(表示数字或表示等于2的整数环的成员)都可以通过有符号和无符号类型来实现,并且两种用法的许多操作相同,但是存在一些区别。除其他事项外,不应期望将两个数字相加会产生除正确的算术和之外的任何东西。尽管是否应要求一种语言来生成保证不会发生的代码(例如,将抛出异常)是有争议的,但有人可能会争辩说,对于使用整数类型表示数字的代码,这种行为会更可取产生算术错误值,并且不应禁止编译器采用这种方式。

C标准的实现者决定使用带符号的整数类型来表示数字,而使用无符号的类型来表示与2模n一致的整数代数环的成员。相比之下,Java使用带符号的整数表示此类环的成员(尽管在某些情况下对它们的解释不同;例如,大小不同的带符号类型之间的转换行为与无符号的类型不同)并且Java既没有无符号的整数,也没有任何整数。在所有非异常情况下均表现为数字的基本整数类型。

如果一种语言同时提供了数字和代数环数字的带符号和无符号表示形式,则使用无符号数字表示始终为正的数量可能是有意义的。但是,如果唯一的无符号类型表示代数环的成员,而唯一的数字类型表示有符号的环,则即使值始终为正,也应使用旨在表示数字的类型来表示。

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


2
细化:字段允许除可加性标识外的其他任何内容。如果你拥有的是+-*,代数结构是一个的

@ChrisWhite:谢谢。上面已更正。自从我学习抽象代数已经很久了。我最初说的是“组”,但是组不支持乘法。
超级猫

@Chris:但是无符号整数类型除可加性之外,确实有除数-它只是基于带有舍入的自然算法,而不是模块化等价类。
Ben Voigt 2013年

@BenVoigt当然可以。但是,“除法”不是乘法的逆运算,因此不会使集合成为一个字段。但这都是语义,我想我们都知道我们在说什么:)

@Chris:您能想象如果C ++实际上在其原始类型之一上使用Galois Field划分会引起混乱吗?
Ben Voigt 2013年

8

在现代架构上,速度是相同的。问题unsigned int在于它有时会产生意外行为。这可能会产生其他情况下不会出现的错误。

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

就返回-1的错误值而言,现代的思想是抛出异常比测试返回值更好。

的确,如果您正确地保护自己的代码,就不会有此问题,并且如果在所有地方都认真地使用unsigned,那么您会没事的(前提是您只加而不减,并且永远都不会接近MAX_INT)。我到处都使用unsigned int。但是这需要很多纪律。对于很多程序,您可以通过使用获得帮助,int并将时间花在其他错误上。


13
“问题unsigned int在于它有时可能(在溢出的情况下)产生意外行为。” 问题signed int在于它有时(在发生溢出的情况下)会产生不确定的行为。有了这些选择,unsigned看起来就不错了;)
Ben Voigt

2
(当然,对于完全不同的值会发生溢出,因此对于有符号类型而言,溢出很少是一个问题)
Ben Voigt

1
@BenVoigt另外,“意外”仅在不知道隐式转换规则的情况下才是意外的(这就是我所说的“反直观”)。幸运的是,无符号溢出是由C和C ++标准精确定义的100%(据我所知)。

1
@ H2CO3:超出范围的移位操作数除外,这是我所知道的无符号类型的UB的唯一示例。
Ben Voigt

@BenVoigt啊,是的,很好。

7

要回答一个实际的问题:对于很多事情,这并不重要。int使用第二个操作数大于第一个操作数进行减法处理可能会更容易一些,但仍会得到“预期”结果。

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

  1. 增加数字的长度(带符号的正负号或无符号的零)-两者都需要花费相同的精力。
  2. 比较-一个带符号的数字,如果一个数字是否为负,则处理器必须考虑到这一点。但是同样,与带符号或无符号数字进行比较的速度相同-只是使用不同的指令代码说“设置最高位的数字小于未设置最高位的数字”(本质上)。[通常,使用比较结果的操作几乎总是不同的-最常见的情况是有条件的跳转或分支指令-但无论哪种方式,都是一样的,只是输入被认为是稍有不同的东西]。
  3. 乘和除。显然,如果是有符号乘法,则需要进行结果的符号转换;如果设置了其中一个输入的最高位,则无符号的不应该更改结果的符号。同样,所做的努力是(与我们关心的一样)相同的。

(我认为还有另外一种或两种情况,但结果是相同的-不管是带符号的还是无符号的,这都没有关系,两种操作的执行力都相同)。


正确,高度相关...。没有回答问题。仍然有用,所以+1
Ben Voigt

7
  1. int默认使用:与其他语言搭配使用效果更好

    • 最常见的域用法是常规算法,而不是模块化算法
    • int main() {} // see an unsigned?
    • auto i = 0; // i is of type int
  2. unsigned用于模算术和位扭曲(尤其是移位)

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

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

    • 当前的体系结构数据流很好地适应了32位非指针数据(但请注意@BenVoigt所发表的有关较小类型缓存效果的评论)
    • charshort节省空间但遭受整体促销
    • 您真的要指望全部int64_t吗?

1
最佳时间性能通常取决于您可以在缓存中容纳多少数据...然后小型类型就可以轻松击败32位。
Ben Voigt 2013年

“移位符号类型是未定义的行为” -不,不是,但是可以
Christian Rau

@ChristianRau感谢您指出这一点,并进行了更新。我不想完整引用5.8 / 2,但这太简单了。
TemplateRex

@TemplateRex不幸的是,它仍然不一定是未定义的行为,对于左移是未定义的,对于右移是实现的。如果您不想引用该标准,最简单的方法可能只是说它可能是不确定的行为。不幸的是,做出准确的陈述有责任做到正确。:-)
Christian Rau

2
您已经给出了一组指导原则,但解释很少。for特别是您的回旋循环需要一些“ splainin”。(我什至会说这是一个糟糕的指导方针–使用for (auto i = 0u; i < v.size(); ++i)代替!–甚至更好for (auto i : indices(x))。)
Konrad Rudolph 2013年

3

int类型比该类型更类似于数学整数的行为unsigned

unsigned仅仅因为某种情况不需要表示负值就偏爱该类型是幼稚的。

问题是 unsigned类型在零附近具有不连续的行为。任何试图计算较小的负值的操作都会产生较大的正值。(更糟糕的是,这是实现定义的。)

诸如此类的代数关系a < b意味着a - b < 0即使对于像a = 3和这样的小值,它们也会在无符号域中破坏b = 4

for (i = max - 1; i >= 0; i--)如果i将其设为无符号,则降序循环无法终止。

无符号的怪癖可能会导致一个问题,该问题将影响代码,无论该代码是否预期仅代表正数。

无符号类型的优点在于,对于有符号类型,在位级别上未可移植地定义的某些操作就是这种方式。无符号类型缺少符号位,因此通过符号位进行移位和屏蔽不成问题。无符号类型适用于位掩码以及适用于以独立于平台的方式实现精确算术的代码。即使在非二进制补码机器上,无符号的出现也会模拟二进制补码的语义。编写多精度(bignum)库实际上需要将无符号类型的数组(而不是带符号的类型)用于表示。

无符号类型也适用于数字的行为类似于标识符而不是算术类型的情况。例如,IPv4地址可以用32位无符号类型表示。您不会将IPv4地址加在一起。


您肯定知道模数算术是完全数学的,对吗?
GManNickG 2013年

@GManNickG这就是为什么我说“数学整数”而不是“数学”。在许多常见情况下,模块化算法是不合适的。
卡兹(Kaz)2013年

3
虽然for (i = max - 1; i >= 0; i--)不会终止,但是请注意,for (i = max - 1; i != -1; i--)它将按预期工作(并且与类型的签名无关)。
AnT

1
@Kaz:您可能是指自然数
Ben Voigt

@BenVoigt为什么我要调用自然数{1,2,3,...}; 它们在这里几乎不相关,并且作为一种类型,它们具有缺点,例如在减法下不封闭,在这方面,它们比模块化的一致性差。
卡兹(Kaz)2013年

2

int首选,因为它是最常用的。unsigned通常与位操作相关联。每当我看到时unsigned,我都认为它是用于旋转的。

如果需要更大的范围,请使用64位整数。

如果您要遍历使用索引的内容,则类型通常具有size_type,并且不必关心它是带符号的还是无符号的。

速度不是问题。


2
@ ott--我不关注。“设置标志”是什么意思?您是说要为未签名少设置一点吗?就像...您只写31位?
Luchian Grigore

2
@ott:有很多(可能是多数)永不消极的价值观。因此不需要您的标志并进行设置。
Ben Voigt

2
@ ott--您有参考吗?我仍然看不到如何使用无符号的方式节省设置的标志或设置的位置。
Luchian Grigore 2013年

2
@ ott--:在大多数现代处理器上,关于有符号和无符号加法的指令不是差不多吗?此外,CPU的速度不是由它需要做多少事情决定的,而是由延迟(周期数)和时钟(因此实际上是关键路径的长度)决定的(省略了诸如OOO执行或超标量架构之类的细节)。因此,只要不增加关键路径,它就不会对速度产生任何影响,并且功耗可以忽略不计。
Maciej Piechotka 2013年

1
@ott:如果您谈论的是由ALU设置的CPU标志,则应该知道在许多体系结构上都为有符号和无符号设置了这些标志。CPU没有太多的数据类型概念。
Ben Voigt

2

对我来说,除了包含在32位体系结构上的有符号和无符号整数集中的0 .. + 2,147,483,647范围内的所有整数之外,我有更大的可能性需要使用-1(或更小),而不是需要使用+2,147,483,648(或更大)。


2

我能想到的一个很好的理由是在检测到溢出的情况下。

对于用例,例如数组中的项目计数,字符串的长度或内存块的大小,您可能会溢出一个无符号的int,即使您查看该变量也可能不会注意到差异。如果它是带符号的int,则该变量将小于零,并且显然是错误的。

您只需要检查一下变量是否为零即可。这样,您就不必像无符号int一样在每次算术运算之后检查溢出。


+1表示“在出现问题时更明显”
Cogwheel

1
我来自汇编程序背景-溢出总是被编码在CPU状态标志中。只需访问此信息,而不是需要从您的范围中切出一点以注意到此类信息,那将是很好的,不是吗?
Mordachai 2013年

从技术上讲,C ++不必在cpu上运行,更不用说带有提供此类信息的标志的了。溢出是未定义的行为,因此您“应该”确保一开始就不会发生。但是,是的,这会很好:P
Cogwheel 2013年

“如果它是一个有符号的int,则该变量将小于零且显然是错误的”-此溢出属性在Java和C#等语言中为true,但在C / C ++中不保证。在C和C ++中,溢出带符号的int是未定义的行为-因此您的程序可以显示正值或执行完全意外的操作。注意不要将溢出的int用作检查健全性的一种方法。
纳希纪(Nayuki)'16

1

在执行简单的算术运算时,它会产生意外的结果:

unsigned int i;
i = 1 - 2;
//i is now 4294967295 on a 64bit machine

进行简单比较时会产生意外结果:

unsigned int j = 1;
std::cout << (j>-1) << std::endl;
//output 0 as false but 1 is greater than -1

这是因为在执行上述操作时,有符号的int会转换为无符号的,并且会溢出并变为非常大的数字。


2
然而,与标准完美定义的任何其他规则相比,这些“故障”已不再多。我认为带符号溢出的未定义行为更多是“故障”。确实,未签名的行为可能有点违反直觉,但是“故障”在这里绝对是错误的词。
Christian Rau

@ChristianRau改写
texasbruce 2013年

1
有趣。我发现您的例子是完全明智的,也是可以预期的。在处理不能为负的事物时,无符号(环形)算术对我而言似乎比整数更明智。如果这确实是使用unsigned的可怕部分,那么我很满意该建议仅是通用的,并且主要基于约定而不是任何严重的担忧(当问题域不需要负值时)。
Mordachai 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.