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

时间:2013-09-13 21:21:12

标签: c++ optimization

我想更好地了解为什么选择int而不是unsigned

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

我问这个问题是因为Bjarne Stroustrup和Chandler Carruth都建议偏好int而不是unsigned here (approx 12:30')

我可以看到使用int超过shortlong - int的论点是目标机器架构的“最自然”数据宽度。

但签约无签名总让我恼火。在典型的现代CPU架构上,签名值是否真的更快?是什么让他们更好?

13 个答案:

答案 0 :(得分:39)

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

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

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

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

  4. 当然有反驳,但这些是我想将整数声明为int而不是unsigned的主要原因。当然,这并非总是如此,在其他情况下,unsigned只是一个更好的工具,我只是在回答"为什么有人会更喜欢默认签署"具体问题。

答案 1 :(得分:31)

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

  

Andrei Alexandrescu

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

Chandler Carruth

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

Bjarne Stroustrup

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

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

  

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

答案 2 :(得分:19)

有几个原因:

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

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

答案 3 :(得分:18)

除了纯粹的社会学之外,没有理由更喜欢signed而不是unsigned,也就是说有些人认为普通程序员没有足够的能力和/或足够的注意力来编写适当的代码{ {1}}类型。这通常是各种“发言者”使用的主要推理,无论这些发言者的尊重程度如何。

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

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

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

在我想到的某些上下文中,唯一真正的选择unsigned的原因是混合整数/浮点代码signed整数格式通常直接由FPU指令集,而不支持signed格式,使编译器为浮点值和unsigned值之间的转换生成额外的代码。在此类代码unsigned中,类型可能会表现得更好。

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

在我的练习中,我尽我所能坚持signed,只有在我真的需要时才使用unsigned

答案 4 :(得分: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时,产生无符号零。

答案 5 :(得分:8)

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

通常,当您从值中减去1时,该值会变小。现在,对于signedunsigned int变量,将有一个时间减去1会创建一个更大的值。 unsigned intint之间的主要区别在于,使用unsigned int生成反常结果的值是常用值--- 0 ---而使用signed时,数字是安全的远远离正常的操作。

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

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

答案 6 :(得分:7)

回答实际问题:对于大量的事情,这并不重要。 int可以更轻松地处理第二个操作数大于第一个操作数的减法操作,并且仍然可以获得“预期”结果。

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

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

答案 7 :(得分:6)

  1. 默认情况下使用int :它可以更好地使用其他语言

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

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

    • 其规则为complicated且令人惊讶(任何一个都可以转换为另一个,具体取决于相对类型大小)
    • 开启-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吗?

答案 8 :(得分:3)

int类型相比,unsigned类型更接近于数学整数的行为。

选择unsigned类型是天真的,因为情况不需要表示负值。

问题是unsigned类型在零旁边有一个不连续的行为。尝试计算小负值的任何操作都会产生一些大的正值。 (更糟糕的是:一个是实现定义的。)

a < b之类的代数关系意味着a - b < 0在无符号域中失败,即使对于a = 3b = 4这样的小值也是如此。

如果for (i = max - 1; i >= 0; i--)未签名,则i之类的降序循环无法终止。

无符号怪癖可能会导致影响代码的问题,无论该代码是否只能表示正数量。

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

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

答案 9 :(得分:2)

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

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

如果您使用索引迭代内容,类型通常有size_type,您不应该关心它是签名还是未签名。

速度不是问题。

答案 10 :(得分:2)

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

对于诸如数组中项目数,字符串长度或内存块大小等用例,您可以溢出unsigned int,即使查看变量也可能没有注意到差异。如果它是一个有符号的int,那么该变量将小于零而且显然​​是错误的。

您可以在想要使用它时查看变量是否为零。这样,您无需在每次算术运算后检查溢出,就像无符号整数的情况一样。

答案 11 :(得分:2)

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

答案 12 :(得分: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将转换为unsigned,并且溢出并转到一个非常大的数字。