建议关于无符号整数(江南Style版)

23
视频“江南Style”(我相信你听过)在YouTube上的观看次数刚刚超过了20亿。事实上,Google表示他们从未预料到一个视频的观看次数会超过32位整数...这暗示着Google在其视图计数器中使用了int而不是unsigned。我认为他们必须稍微重新编写代码来容纳更大的观看量。查看他们的样式指南:https://google-styleguide.googlecode.com/svn/trunk/cppguide.html#Integer_Types。他们建议“不要使用无符号整数类型”,并给出一个很好的原因:unsigned可能存在漏洞。这是一个好的理由,但可以防范。我的问题是:一般情况下使用unsigned int是不良编码习惯吗?

8
这个规则太愚蠢了:“不要使用无符号类型”。我可以想到无数种情况需要使用无符号类型。 - Cory Kramer
5
我完全不同意那个指南。他们的示例问题在于,在i是“无符号整数”时,他们使用i >= 0作为条件 - 那太愚蠢了。当然,这个条件总是成立的。实际上,在这种情况下,“无符号整数”已经完成了它的工作!它永远不会是负数! - Joseph Mansfield
1
我也不同意那个规则,也许是因为担心混合使用有符号和无符号类型时可能会出现一些边角情况,就像在我的答案中所述。但是老实说,使用正确的警告标志和静态分析应该可以捕捉到这些问题。 - Shafik Yaghmour
他们并不完全禁止无符号类型。 "如果合适的话,您可以使用标准类型,如**size_t**和ptrdiff_t。" - Anton Savin
2
另一方面,无符号类型不太可能格式化您的硬盘。 - MSalters
显示剩余5条评论
5个回答

13

谷歌规则在专业圈中被广泛接受。问题在于,无符号整数类型有点失效,并且在用于数字值时具有意外和不自然的行为;它们不能很好地作为基数类型。例如,数组索引可能永远不会是负数,但是编写 abs(i1 - i2) 来查找两个索引之间的距离是完全有道理的。如果使用无符号类型,则此方法将无法工作。

通常情况下,谷歌样式指南中的这个特定规则更或多或少对应于语言设计人员的意图。如果您看到的不是 int,则可以假定有一个特殊原因。如果是由于范围,则将使用 longlong long,甚至是 int_least64_t。通常使用无符号类型表示您正在处理位,而不是变量的数值,或者(至少在使用 unsigned char 的情况下)表示您正在处理原始内存。

关于使用 unsigned 的“自说明性”:这并不能保持,因为变量几乎总是不能(或不应该)采用许多正值,包括许多正值。 C++ 没有子范围类型,并且 unsigned 的定义方式意味着它实际上无法用作子范围类型。


3
在这种情况下,我认为“广泛”是非常主观的。或者说在我的职业生涯中我可能很幸运。就我个人而言,我认为带模算术的无符号数在数学上比它们有符号的对应物更有意义。顺便说一下,我认为abs(i1-i2)没有意义,但我喜欢clang对它的警告。 - Cubbi
2
你的例子有缺陷。假设你包含了整数版本的<cstdlib>而不是浮点数版本的<cmath>,那么std::abs(i1 - i2)甚至无法编译通过。在gcc中,我得到了一个“error: call of overloaded ‘abs(unsigned int)’ is ambiguous”的错误。 - b4hand
2
为什么要猜测C++的设计者对于“unsigned”的意图呢?Stroustrup与洛克希德·马丁公司合作制定了JTSF指南。在这些指南中,他特别提到了必须使用“unsigned”并禁止对其进行算术运算。 - b4hand
2
当整数(在这种情况下为无符号整数)太大而无法表示为(无符号)整数时,它开始“环绕”(所谓的“溢出”发生)。在这种情况下,旧值(x)实际上比旧值(x + 1)更大。请参见http://en.wikipedia.org/wiki/Integer_overflow。@TravisBemrose - user2340939
1
如何在处理容器和size_type(无符号)时遵循此指南而不遇到所有已签名/未签名的比较警告?您只需将vector::size()的结果转换为有符号整数吗? - Emile Cormier
显示剩余14条评论

9
这份指南非常误导人。盲目地使用 int 而不是 unsigned int 并不能解决问题,只是把问题转移到别处。在对定点整数进行算术运算时,你必须绝对注意整数溢出。如果你的代码以某些给定的输入方式编写,无法优雅地处理整数溢出,则无论你使用 signed 还是 unsigned int,你的代码都是有问题的。使用 unsigned int 时,你还必须注意整数下溢,而在使用 doublefloat 时,你还必须注意许多与浮点算术相关的其他问题。

只需查看由谷歌发布的有关标准Java二分搜索算法漏洞的文章,就可以了解为什么您必须注意整数溢出。事实上,该文章展示了C++代码将类型转换为unsigned int保证正确行为。文章还从一个Java中的漏洞开始介绍,猜猜怎么着,他们没有unsigned int。然而,他们仍然遇到了整数溢出的问题。


1
您必须了解任何值的有效范围,无论它是否带符号。真正的问题在于,一些作者似乎建议将“unsigned”用作子范围类型。但实际上它并不是;C++没有子范围类型。 - James Kanze
我认为溢出/下溢并不是问题。我认为问题在于试图使用无符号数进行算术运算时经常被忽视的陷阱。 - Travis Bemrose

5

在进行操作时,请使用正确的数据类型。对于计数器来说,float并不合适,signed int也不是。计数器的常规操作包括print+=1

即使您有一些不寻常的操作,例如打印观看次数的差异,您也不一定会遇到问题。当然,其他答案提到了不正确的代码abs(i2-i1),但是期望程序员使用正确的代码 max(i2,i1) - min(i2,i1) 也不是不合理的。这个代码对于signed int存在范围问题,因此没有统一的解决方案;程序员应该理解他们正在使用的数据类型的属性。


2
谷歌表示:"一些人,包括一些教科书作者,建议使用无符号类型来表示永远不会是负数的数字。这旨在作为一种自我记录。"
我个人使用无符号整数作为索引参数。
int foo(unsigned int index, int* myArray){
    return myArray[index];
}

谷歌建议:使用断言来指定变量为非负数。不要使用无符号类型。

int foo(int index, int* myArray){
    assert(index >= 0);
    return myArray[index];
}

谷歌的优势:如果在调试模式下传递了一个负数,我的代码有望返回越界错误。谷歌的代码保证会断言。

我的优势:我的代码可以支持更大的myArray大小。

我认为最终决定因素在于,你的代码有多干净?如果你清理了 所有警告,那么当编译器警告你尝试将有符号变量赋值给无符号变量时,就会很清楚了。如果你的代码已经有了一堆警告,编译器的警告可能会被忽略。

最后注意一点:谷歌说:“有时gcc会注意到这个bug并警告你,但通常不会。” 在Visual Studio上,我没有看到这种情况,针对负数和从有符号赋值到无符号的检查总是会被警告。但如果你使用gcc,你可能需要小心。


1
您的_pro_实际上是要求size_t成为无符号类型的最初动机。这在16位机器上是有效的(当时非常普遍),但今天并不是很相关。问题在于C++(和C)的无符号类型不能很好地模拟整数,甚至不能很好地模拟非负整数集合,因此应该避免使用它们。 - James Kanze
1
例如,在非负整数集合上未定义减法。非负整数集合是整数集合的子集,减法的结果可能是负数,或者您不支持减法。如果abs(i1-i2)合法,则必须得到两个值之间的差异。 - James Kanze
1
假设您使用整数版本的 <cstdlib>,而不是浮点数版本的 <cmath>,则 std::abs(i1 - i2) 甚至无法编译通过。在 gcc 中会出现 error: call of overloaded ‘abs(unsigned int)’ is ambiguous 的错误提示。 - b4hand
2
@JamesKanze 从某个非负整数模数的非负整数有限集上完全定义了减法。模算术的整个研究领域都依赖于它的定义。实际上,我会说对于模算术中减法的行为非常熟悉的人比补码算术的人要多得多。 - b4hand
1
@JamesKanze “它的定义方式对于大多数用途来说都是错误的。” 我知道你只是想表达一个观点,但“大多数用途”真的吗? - Jonathan Mee
显示剩余4条评论

1
您的具体问题是:
“使用无符号是否是不良实践?”唯一正确的答案应该是否定的。这并不是不良实践。
有许多风格指南,每个指南都有不同的重点,虽然在某些情况下,一个组织可能选择不在其产品中使用无符号,但其他工具链和平台几乎要求使用它。
谷歌似乎得到了很多尊重,因为他们拥有良好的商业模式(可能像其他人一样雇用一些聪明的人)。
CERT IIRC 建议对于缓冲区索引使用无符号,因为如果发生溢出,至少仍然在自己的缓冲区内,这里有一些内在的安全性。
语言和标准库设计者的意见是什么(可能是被接受的智慧的最佳代表)。strlen 返回一个 size_t,这可能是无符号的(与平台相关),其他答案表明这是一个过时的做法,因为新型电脑具有广泛的架构,但这忽略了 C 和 C++ 是通用编程语言,应该在大型和小型平台上很好地扩展。
底线是这是许多宗教问题之一;肯定没有解决,这种情况下,我通常为绿地开发选择我的宗教,并为现有工作选择代码库的现有约定。一致性很重要。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接