为什么更喜欢在C + +无符号签名?
我想更好地理解为什么selectint
unsigned
?
就我个人而言,除非有合理的理由,否则我从来不喜欢签名的值。 例如数组中项目的数量,string的长度,内存块的大小等,所以这些事情往往不可能是负面的。 这样的价值没有可能的意义。 在所有这些情况下,为什么更喜欢int
?
我问这个,因为Bjarne Stroustrup和Chandler Carruth都给出了build议,希望int
在这里没有 unsigned
(约12:30) 。
我可以看到在short
或long
使用int
的参数 – int
是目标机器体系结构的“最自然的”数据宽度。
但签署无签名总是恼火我。 在典型的现代CPU架构上,签名值是否真的更快? 是什么让他们更好?
让我们用video简要地解释一下这个video。
Andrei Alexandrescu :
- 没有简单的指导方针。
- 在系统编程中,我们需要不同大小和符号的整数。
- 许多转换和奥术规则pipe理算术(如
auto
),所以我们需要小心。Chandler Carruth :
- 这里有一些简单的指导:
- 除非需要二进制补码algorithm或位模式,否则使用带符号整数
- 使用最小的整数就足够了。
- 否则,如果您认为您可以计算这些项目,请使用
int
如果计数结果超过您的值,则使用64位整数。- 不用担心,并使用工具告诉你什么时候需要不同的types或大小。
Bjarne Stroustrup :
- 使用
int
直到你有一个理由不。- 只使用无符号位模式。
- 切勿混合签名和未签名
撇开签名规则的谨慎,我的一句话从专家身上拿走:
使用适当的types,当你不知道的时候,使用
int
直到你知道。
根据要求在评论:我喜欢int
而不是unsigned
因为…
-
它更短(我是认真的!)
-
它更通用,更直观(即我能够假设
1 - 2
是-1而不是一些模糊的巨大数字) -
如果我想通过返回超出范围的值来指示错误,该怎么办?
当然有反驳的意见,但这些是我喜欢把我的整数而不是unsigned
整数的主要原因。 当然,这并非总是如此,在其他情况下, unsigned
只是一个更好的工具,我只是回答“为什么会有人更喜欢默认签名”的问题。
几个原因:
-
unsigned
算术总是产生无符号的,这可能是一个问题时减去整数量,可以导致一个负面的结果 – 认为减去货币数量产生平衡,或数组索引产生元素之间的距离。 如果操作数是无符号的,那么你得到了一个完全定义的,但几乎肯定无意义的结果,并且result < 0
比较总是假的(现代编译器幸好会提醒你)。 -
unsigned
具有污染算术的混乱与有符号整数的讨厌的属性。 所以,如果你添加一个signed和unsigned,并询问结果是否大于零,你可以被咬住,特别是当无符号整型被隐藏在一个typedef
后面的时候。
除了纯粹的社会学的理由之外,没有理由更喜欢signed
不是unsigned
,也就是说有些人认为普通程序员没有足够的能力和/或足够的注意力去用unsigned
types来编写正确的代码。 这通常是各种“发言人”使用的主要推理,无论这些发言者是多么受人尊敬。
实际上,主pipe程序员很快就开发和/或学习了一套基本的编程习惯用法和技巧,使他们能够根据无符号整型来编写正确的代码。
还要注意,在C和C ++语言的其他部分,像指针运算和迭代器运算一样,有符号和无符号语义之间的根本区别总是存在(表面上不同的forms)。 这意味着在一般情况下,程序员实际上没有select避免处理特定于未签名语义的问题以及它带来的“问题”。 也就是说,无论你是否想要,即使你坚决避免unsigned
整数,你也必须学会使用左端突然终止的范围,并在这里终止(不在远处)。
另外,正如您可能知道的那样,标准库的很多部分已经非常依赖unsigned
整数types。 强制签名算术混合,而不是学习使用未签名的algorithm,只会导致糟糕的代码糟糕的。
在想到的一些上下文中,喜欢signed
的唯一真正原因是混合整数/浮点代码有signed
整数格式通常由FPU指令集直接支持, unsigned
格式根本不被支持,使得编译器生成浮点值和unsigned
值之间转换的额外代码。 在这样的代码signed
types可能会performance更好。
但是在纯粹的整数代码中, unsigned
types可能比有signed
types更好。 例如,整数除法通常需要额外的校正码才能满足语言规范的要求。 只有在操作数为负的情况下才需要进行修正,否则在不使用负操作数的情况下会浪费CPU周期。
在我的实践中,我尽可能坚持不unsigned
,只有在真的必须signed
时才使用signed
。
现代架构的速度是一样的。 unsigned int
的问题是有时会产生意外的行为。 这可能会产生不会出现的错误。
通常当你从一个值中减去1时,该值变小。 现在,对于有signed
和unsigned int
variables,将有一个时间,减去1创build一个很大的值。 unsigned int
和int
的关键区别在于,使用unsigned int
,产生矛盾结果的值是一个常用值— 0 —而带符号的数字安全地远离正常的操作。
至于返回一个错误值—现代思维是抛出一个exception比testing返回值更好。
确实,如果你正确地捍卫你的代码,你就不会有这个问题,如果你在任何地方都使用无符号的信号,那么你会好起来的(假设你只是增加,而不是减去,并且你永远不会接近MAX_INT)。 我使用unsigned int无处不在。 但这需要很多纪律。 对于很多程序,你可以通过使用int
来获得,并把时间花在其他bug上。
C中的整型和源于它的许多语言有两个一般的用例:代表数字,或代表抽象代数环的成员。 对于那些不熟悉抽象代数的人来说,环之后的主要概念是加,减或乘以一个环的两个项应该产生该环的另一个项 – 它不应该在环之外崩溃或产生一个值。 在32位机器上,将无符号0x12345678添加到无符号0xFFFFFFFF不会“溢出” – 它只会产生结果0x12345677,它是为整型mod 2 ^ 32的整数环定义的(因为将0x12345678加到0xFFFFFFFF即0x112345677,与0x12345677 mod 2 ^ 32一致)。
从概念上讲,这两种用途(表示数字,或表示整数环一致的环的成员)可以由有符号和无符号两种types来服务,并且许多操作对于这两种使用情况是相同的,但是有一些差异。 除了其他的事情之外,试图增加两个数字不应该被期望产生除了正确的算术和以外的任何东西。 虽然为了确保不会产生必要的代码(例如,会抛出一个exception),应该要求使用一种语言是有争议的,但人们可以争辩说, 对于使用整数types来表示数字的代码,这样的行为将是可取的产生一个算术不正确的值,编译器不应该被禁止这样做。
C标准的实现者决定使用带符号整数types来表示数字和无符号types,以表示与mod 2 ^ n一致的整数代数环的成员。 相比之下,Java使用有符号整数来表示这样的环的成员(尽pipe在某些情况下它们的解释是不同的;例如,不同大小的签名types之间的转换,与未经签名的types之间的行为不同),Java既没有无符号整数也没有在所有非例外情况下performance为数字的原始整数types。
如果一种语言为数字和代数环号码提供了有符号和无符号表示的select,使用无符号数字来表示总是正数的数量可能是有意义的。 但是,如果唯一的无符号types代表代数环的成员,并且唯一代表数字的types是带符号的types,那么即使值总是正数,也应该使用用于表示数字的types来表示。
顺便说一下,(uint32_t)-1是0xFFFFFFFF的原因在于,将有符号值转换为无符号等价于添加无符号零,并将整数添加到无符号值被定义为将其大小加到或减去无符号值,根据代数环的规则规定如果X = YZ,则X是该环的唯一成员,例如X + Z = Y。 在无符号math运算中,0xFFFFFFFF是唯一一个加到无符号1时的数字,产生无符号零。
回答实际问题:对于广大的事物来说,这并不重要。 int
可以更容易处理像第二个操作数大于第一个操作数的减法,而您仍然可以得到“预期”结果。
在99.9%的情况下绝对没有速度差异,因为对于有符号和无符号数的唯一指令是:
- 使数字变长(用符号填充符号或用无符号数字填充0) – 两者都需要同样的努力。
- 比较 – 一个有符号的数字,处理器必须考虑到,如果任何一个数字是否定的。 但同样的速度与有符号或无符号的数字进行比较也是一样的 – 只是使用不同的指令代码来表示“设置了最高位的数字小于最高位未设置的数字”(本质上)。 [迂回地说,几乎总是使用不同的比较结果的操作 – 最常见的情况是条件跳转或分支指令 – 但无论哪种方式,都是一样的,只是input意味着稍微不同的东西]。
- 相乘和分割。 显然,如果是有符号乘法,则需要对结果进行符号转换,如果其中一个input的最高位被设置,则无符号不应该改变结果的符号。 再次,努力(尽可能接近我们所关心的)是相同的。
(我认为有一两个其他的情况,但结果是一样的 – 如果它是有符号的或者没有符号,那么执行这个操作的努力是一样的。
-
在默认情况下使用
int
:与其他语言一起使用更好- 最常见的领域用法是正则算术,而不是模块化算术
-
int main() {} // see an unsigned?
-
auto i = 0; // i is of type int
-
只能使用
unsigned
的模运算和比特旋转 (特别是移位)- 与常规算术有不同的语义,确保它是你想要的
- 位移符号types是微妙的(见@ChristianRau的评论)
- 如果在32位机器上需要> 2Gb的vector,请升级您的操作系统/硬件
-
切勿混合有符号和无符号算术
- 规则是复杂和令人惊讶的(任何一个可以转换到另一个,取决于相对的types大小)
- 打开
-Wconversion -Wsign-conversion -Wsign-promo
(gcc比Clang好) - 标准库得到了错误的
std::size_t
(从GN13video引用) - 如果可以的话,使用范围,
-
for(auto i = 0; i < static_cast<int>(v.size()); ++i)
如果必须的话
-
除非你真的需要,否则不要使用短或大的types
- 当前体系结构的数据stream可以很好地适应32位非指针数据(但请注意@BenVoigt对于较小types的caching效应的评论)
-
char
和short
节省空间,但遭受积分促销 - 你真的要数到所有
int64_t
?
int
是首选,因为它是最常用的。 unsigned
通常与位操作相关联。 每当我看到一个unsigned
,我认为它用于位twiddling。
如果您需要更大的范围,请使用64位整数。
如果你正在使用索引迭代东西,types通常有size_type
,你不应该在乎它是有符号还是无符号的。
速度不是问题。
int
types与int
types更类似于math整数的行为。
仅仅因为情况不需要负值就可以代表unsigned
types,这是天真的。
问题是unsigned
types在0旁边有一个不连续的行为。 任何试图计算一个小的负值的操作,反而会产生一个很大的正值。 (更糟:一个是实现定义的。)
代数关系如a < b
意味着a - b < 0
在无符号域中被破坏,即使对于a = 3
和b = 4
这样a = 3
小值也是如此。
像for (i = max - 1; i >= 0; i--)
这样的下降循环for (i = max - 1; i >= 0; i--)
如果i
是无符号的,就不能终止。
无符号怪癖可能会导致一个问题,这将影响代码,无论代码是否只表示正数量。
无符号types的好处在于,在签名types的位级上不可移植地定义的某些操作对于无符号types是这样的。 无符号types没有符号位,所以通过符号位移位和掩码不是问题。 无符号types对于位掩码是有利的,对于以独立于平台的方式实现精确算术的代码。 即使在非二进制补码机器上,未经签名的操作也将模拟二进制补码语义。 编写一个多精度(bignum)库实际上需要使用无符号types的数组来表示,而不是签名types。
无符号types也适用于数字类似于标识符而不是算术types的情况。 例如,IPv4地址可以用32位无符号types表示。 您不会将IPv4地址添加在一起。
对于我来说,除了包含在32位体系结构中有符号和无符号整数集合中的0 .. + 2,147,483,647范围内的所有整数之外,我需要使用-1(或更小)的概率比需要使用+2,147,483,648(或更大)。
我能想到的一个很好的理由是在检测到溢出的情况下。
对于数组中的项目数,string长度或内存块大小等用例,您可以溢出一个unsigned int,即使查看variables,您也可能不会注意到有什么区别。 如果它是一个有符号的整数,variables将会小于零,显然是错误的。
当你想使用它时,你可以简单地检查variables是否为零。 这样,每次算术运算后,您都不必检查溢出,就像未经签名的整数一样。
在进行简单的算术运算时会产生意外的结果:
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
这是因为在执行上面的操作时,已签名的整数被转换为无符号的,并且溢出并且变成了一个非常大的数字。