考虑以下†:
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--)
答案 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
,是一种实现定义的无符号整数类型。 -1
是int
类型的表达式。
在大多数系统上,size_t
至少与int
一样宽。在此类系统上,整数促销会导致r
的值转换为unsigned int
或保留其现有类型(如果size_t
的宽度与{int
相同,则会发生前者1}},但转换率较低)。现在左操作数(无符号)至少具有右操作数的等级(有符号)。右操作数将转换为左操作数的类型。此转换产生与r
相同的值,因此相等比较产生true
。
那是&#34;正常&#34;情况下。
假设我们有一个size_t
为16位的实现(让我们说typedef
unsigned short
为int
,SIZE_MAX == 65535
为32位。所以INT_MAX == 2147483647
和size_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
。 (我说可能是因为int
或result
可以与true
具有相同的宽度,在这种情况下const bool result = (r == (size_t)-1);
将是const bool result = (r == SIZE_MAX);
。)
实际上,您可以通过显式转换来避免潜在的问题,而不是依赖于实现定义的常规算术转换:
size_t
或
size_t
C ++ 11标准参考:
ptrdiff_t
18.2第6-7段:
6类型
size_t
是实现定义的无符号整数类型 它足够大,可以包含任何对象的字节大小。7 [注意:建议实施选择类型
signed long int
和size_t
的整数转换等级(4.13)为否 大于int
的大小,除非更大的大小 必须包含所有可能的值。 - 结束说明]
所以没有禁止使int
比size_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 long
,unsigned int
或std::size_t
。
如果unsigned long long
为unsigned long
或std::size_t
,则unsigned int
的排名大于int
的排名,因此,也是std::size_t
。
如果unsigned int
为std::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}}