是否将下溢的无符号整数与-1定义良好?

时间:2014-12-10 15:58:36

标签: c++ c++11 language-lawyer unsigned-integer signed-integer

考虑以下

size_t r = 0;
r--;
const bool result = (r == -1);

结果初始化result的比较是否具有明确定义的行为? 结果是true,正如我所期待的那样?


本Q& A的编写是因为我不确定两个因素 在我的回答中,可以通过使用术语“关键[ly]”来识别它们。

这个例子的灵感来自于计数器无符号时的循环条件方法:
for (size_t r = m.size() - 1; r != -1; r--)

2 个答案:

答案 0 :(得分:20)

size_t r = 0;
r--;
const bool result = (r == -1);

严格地说,result的值是实现定义的。在实践中,它几乎肯定是true;如果有false的实施,我会感到惊讶。

r之后r--的值是SIZE_MAX的值,<stddef.h> / <cstddef>中定义的宏。

对于比较r == -1,在两个操作数上执行通常的算术转换。通常的算术转换的第一步是将整数促销应用于两个操作数。

r的类型为size_t,是一种实现定义的无符号整数类型。 -1int类型的表达式。

在大多数系统上,size_t至少与int一样宽。在此类系统上,整数促销会导致r的值转换为unsigned int或保留其现有类型(如果size_t的宽度与{int相同,则会发生前者1}},但转换率较低)。现在左操作数(无符号)至少具有右操作数的等级(有符号)。右操作数将转换为左操作数的类型。此转换产生与r相同的值,因此相等比较产生true

那是&#34;正常&#34;情况下。

假设我们有一个size_t为16位的实现(让我们说typedef unsigned shortintSIZE_MAX == 65535为32位。所以INT_MAX == 2147483647size_t。或者我们可以有一个32位int和一个64位size_t。我怀疑是否存在任何此类实现,但标准中没有任何内容禁止它(见下文)。

现在比较的左侧有65535类型和值int。由于已签名 size_t可以代表65535类型的所有值,因此整数促销会将值转换为int类型的== < / em>的。 int运算符的两侧都有65535 == -1类型,因此通常的算术转换无关。表达式相当于false,显然是size_t

正如我所提到的,类型r的表达式不太可能发生这种情况 - 但是对于较窄的无符号类型,它很容易发生。例如,如果unsigned short被声明为unsigned char,或char,或者甚至是在该类型已签名的系统上的普通result,则false的值为short 1}}可能是unsigned char。 (我说可能是因为intresult可以与true具有相同的宽度,在这种情况下const bool result = (r == (size_t)-1); 将是const bool result = (r == SIZE_MAX); 。)

实际上,您可以通过显式转换来避免潜在的问题,而不是依赖于实现定义的常规算术转换:

size_t

size_t

C ++ 11标准参考:

  • 5.10 [expr.eq]平等运营商
  • 5.9 [expr.rel]关系运算符(指定执行通常的算术转换)
  • 5 [expr]表达式,第9段:通常的算术转换
  • 4.5 [conv.prom]整体促销
  • 18.2 [support.types] ptrdiff_t

18.2第6-7段:

  

6类型size_t是实现定义的无符号整数类型   它足够大,可以包含任何对象的字节大小。

     

7 [注意:建议实施选择类型   signed long intsize_t的整数转换等级(4.13)为否   大于int的大小,除非更大的大小   必须包含所有可能的值。 - 结束说明]

所以没有禁止使intsize_t更窄。我可以几乎想象一个{{1}}为64位的系统,但没有一个对象可以大于2 32 -1个字节,所以{{1}}是32位。

答案 1 :(得分:13)

是的,结果就是您所期望的。

让我们分解。

此时r的价值是多少?好吧,下溢是明确定义的,并导致r在比较运行时获取其最大值。 std::size_t has no specific known bounds,但与int相比,我们可以对其范围做出合理的假设:

  

std::size_t是sizeof运算符结果的无符号整数类型。 [..] std::size_t可以存储任何类型(包括数组)理论上可能的对象的最大大小。

而且,为了解决问题,表达式-1是一元-应用于文字1,并且在任何系统上都有int类型:

  

[C++11: 2.14.2/2]:整数文字的类型是表6中相应列表中第一个可以表示其值的列表。 [..]

(我不会引用所有描述如何将一元-应用于int的{​​{1}}结果的文字,但确实如此。)

建议在大多数系统上,int无法容纳int,这是合理的。

现在,那些操作数会发生什么?

  

std::numeric_limits<std::size_t>::max() [C++11: 5.10/1]:(等于)和==(不等于)运算符与关系运算符具有相同的语义限制,转换和结果类型,除了它们的较低优先级和真值结果。 [..]

     

!=通常的算术转换是在算术或枚举类型的操作数上执行的。 [..]

让我们检查这些&#34;通常的算术转换&#34;:

  

[C++11: 5.9/2]:许多期望算术或枚举类型操作数的二元运算符会以类似的方式引起转换并产生结果类型。目的是产生一个通用类型,它也是结果的类型。

     

此模式称为通常的算术转换,其定义如下:

     
      
  • 如果任一操作数是作用域枚举类型(7.2),则不执行任何转换;如果是另一个   操作数的类型不同,表达式格式不正确。
  •   
  • 如果任一操作数的类型为[C++11: 5/9]:,则另一个操作数将转换为long double`。
  •   
  • 否则,如果任一操作数为long double,则另一个操作数应转换为double
  •   
  • 否则,如果任一操作数为double,则另一个操作数应转换为float
  •   
  • 否则,应对两个操作数执行整数提升(4.5)。 59 然后,以下规则应适用于提升的操作数:   
        
    • 如果两个操作数具有相同的类型,则无需进一步转换。
    •   
    • 否则,如果两个操作数都有有符号整数类型或两者都有无符号整数类型,则   具有较小整数转换等级类型的操作数应转换为类型   操作数更高的等级。
    •   
    • 否则,如果具有无符号整数类型的操作数的秩大于或等于另一个操作数的类型的等级,则带有符号整数类型的操作数应转换为   具有无符号整数类型的操作数的类型。
    •   
    • 否则,如果带有符号整数类型的操作数的类型可以表示具有无符号整数类型的操作数类型的所有值,则具有无符号整数类型的操作数应转换为带有符号整数的操作数的类型类型。
    •   
    • 否则,两个操作数都应转换为对应的无符号整数类型   带有符号整数类型的操作数的类型。
    •   
  •   

我已经突出显示了此处生效的段落,并且为什么

  

float:每个整数类型的整数转换等级定义如下

     
      
  • [..]
  •   
  • [C++11: 4.13/1]的排名应大于long long int的排名,long int的排名应大于int的排名,short int的排名应大于signed char的排名},应大于std::size_t的等级。
  •   
  • 任何无符号整数类型的等级应等于相应有符号整数类型的等级。
  •   
  • [..]
  •   

所有整数类型,即使是固定宽度类型,都由标准整数类型组成;因此,从逻辑上讲,unsigned long long必须是unsigned longunsigned intstd::size_t

  • 如果unsigned long longunsigned longstd::size_t,则unsigned int的排名大于int的排名,因此,也是std::size_t

  • 如果unsigned intstd::size_t,则unsigned int的等级等于int的等级,因此也等于[C++11: 4.7/2]:的等级。

无论哪种方式,根据通常的算术转换,已签名的操作数将转换为无符号操作数的类型(至关重要的是,不是反过来!) 。现在,这种转换需要什么?

  

[C++11: 4.7/3]: 如果目标类型是无符号的,则结果值是与源整数一致的最小无符号整数(模2 n 其中 n 是用于表示无符号类型的位数。) [注意:在二进制补码表示中,此转换是概念性的,没有变化在位模式中(如果没有截断)。 -end note]

     

std::size_t(-1)如果目标类型是有符号的,如果它可以在目标类型(和位字段宽度)中表示,则该值不变;否则,该值是实现定义的。

这意味着std::numeric_limits<std::size_t>::max()相当于std::size_t((unsigned int)-1);上述子句中的值 n 与用于表示 unsigned 类型的位数有关,而不是源类型,这一点至关重要。否则,我们正在做std::cout << (std::size_t(-1) == std::numeric_limits<size_t>::max()) << '\n'; // "1" ,这根本不是一回事 - 它可能比我们想要的值小许多个数量级!

事实上,既然我们知道转换都是明确定义的,我们可以测试这个值:

std::cout << std::is_same<unsigned long, std::size_t>::value << '\n';
std::cout << std::is_same<unsigned long, unsigned int>::value << '\n';
std::cout << std::hex << std::showbase
          << std::size_t(-1) << ' '
          << std::size_t(static_cast<unsigned int>(-1)) << '\n';
// "1"
// "0"
// "0xffffffffffffffff 0xffffffff"

而且,为了说明我之前在64位系统上的观点:

{{1}}