考虑以下†:
size_t r = 0;
r--;
const bool result = (r == -1);
结果初始化的比较是否result
具有明确定义的行为?
它的结果是否true
如我所料?
写这个问答是因为我不确定两个因素。
在我的回答中,它们都可以通过使用“关键[ly]”一词来识别。
†此示例的灵感来自于计数器无符号时的循环条件方法:
for (size_t r = m.size() - 1; r != -1; r--)
考虑以下†:
size_t r = 0;
r--;
const bool result = (r == -1);
结果初始化的比较是否result
具有明确定义的行为?
它的结果是否true
如我所料?
写这个问答是因为我不确定两个因素。
在我的回答中,它们都可以通过使用“关键[ly]”一词来识别。
†此示例的灵感来自于计数器无符号时的循环条件方法:
for (size_t r = m.size() - 1; r != -1; r--)
size_t r = 0;
r--;
const bool result = (r == -1);
严格来说, 的值result
是实现定义的。在实践中,几乎可以肯定的是true
;如果有一个实现,我会感到惊讶false
。
r
afterr--
的值是/中SIZE_MAX
定义的宏的值。<stddef.h>
<cstddef>
对于比较r == -1
,通常对两个操作数进行算术转换。通常算术转换的第一步是将整数提升应用于两个操作数。
r
是 type size_t
,实现定义的无符号整数类型。-1
是类型的表达式int
。
在大多数系统size_t
上,至少与int
. 在这样的系统上,整数提升会导致 的值r
被转换为unsigned int
或保持其现有类型(如果size_t
与 具有相同的宽度int
,但转换等级较低,则前者可能发生)。现在左操作数(无符号)至少具有右操作数(有符号)的等级。右操作数转换为左操作数的类型。这种转换产生与 相同的值r
,因此相等比较产生true
。
那是“正常”的情况。
假设我们有一个size_t
16 位的实现(假设它是typedef
for unsigned short
)并且int
是 32 位。所以SIZE_MAX == 65535
和INT_MAX == 2147483647
。或者我们可以有一个 32-bitsize_t
和一个 64-bit int
。我怀疑是否存在任何此类实现,但标准中没有任何内容禁止它(见下文)。
现在比较的左侧有 typesize_t
和 value 65535
。由于signed int
可以表示 typesize_t
的所有值,因此整数提升将 value 转换65535
为 typeint
。运算符的两边==
都有 type int
,因此通常的算术转换无关。表达式等价于65535 == -1
,很明显false
。
正如我所提到的,这种事情不太可能发生在类型表达式上size_t
——但它很容易发生在更窄的无符号类型上。例如,如果在该类型被签名的系统上r
声明为 anunsigned short
或 an unsigned char
,甚至是 plain char
,则 的值result
可能是false
。(我说可能是因为short
甚至unsigned char
可以具有与 相同的宽度int
,在这种情况下result
将是true
。)
在实践中,您可以通过显式进行转换而不是依赖于实现定义的常用算术转换来避免潜在问题:
const bool result = (r == (size_t)-1);
或者
const bool result = (r == SIZE_MAX);
C++11 标准参考:
size_t
18.2 第 6-7 段:
6 该类型
size_t
是实现定义的无符号整数类型,其大小足以包含任何对象的字节大小。7 [注意:建议实现选择 其整数转换等级(4.13)不大于的类型,
ptrdiff_t
除非需要更大的大小来包含所有可能的值。——尾注]size_t
signed long int
So there's no prohibition on making size_t
narrower than int
. I can almost plausibly imagine a system where int
is 64 bits, but no single object can be bigger than 232-1 bytes so size_t
is 32 bits.
是的,结果是你所期望的。
让我们分解一下。
此时的价值是r
多少?好吧,下溢是明确定义的,并导致在r
运行比较时取其最大值。std::size_t
没有特定的已知界限,但与 a 相比,我们可以对其范围做出合理的假设int
:
std::size_t
是 sizeof 运算符结果的无符号整数类型。[..]std::size_t
可以存储理论上可能的任何类型(包括数组)对象的最大大小。
而且,只是为了让它不碍事,表达式-1
是一元-
应用于文字的,并且在任何系统上1
都有类型:int
[C++11: 2.14.2/2]:
整数文字的类型是表 6 中可以表示其值的相应列表中的第一个。[..]
(我不会引用所有描述如何将一元-
应用于int
结果的文本int
,但确实如此。)
有理由认为,在大多数系统上, anint
将无法保持std::numeric_limits<std::size_t>::max()
.
现在,这些操作数会发生什么?
[C++11: 5.10/1]:
(==
等于)和!=
(不等于)运算符具有与关系运算符相同的语义限制、转换和结果类型,除了它们的较低优先级和真值结果。[..]
[C++11: 5.9/2]:
通常的算术转换是在算术或枚举类型的操作数上执行的。[..]
让我们检查一下这些“常见的算术转换”:
[C++11: 5/9]:
许多期望算术或枚举类型的操作数的二元运算符会以类似的方式导致转换和产生结果类型。目的是产生一个通用类型,这也是结果的类型。这种模式称为通常的算术转换,其定义如下:
- 如果任一操作数属于范围枚举类型 (7.2),则不执行任何转换;如果另一个操作数的类型不同,则表达式格式错误。
- 如果任一操作数的类型为
long double
,则另一个应转换为 long double`。- 否则,如果任一操作数为
double
,则另一个应转换为double
。- 否则,如果任一操作数为
float
,则另一个应转换为float
。- 否则,应在两个操作数上执行积分提升 (4.5)。59然后以下规则应适用于提升的操作数:
- 如果两个操作数具有相同的类型,则不需要进一步转换。
- 否则,如果两个操作数都具有有符号整数类型或都具有无符号整数类型,则具有较小整数转换等级的类型的操作数应转换为具有较大等级的操作数的类型。
- 否则,如果无符号整数类型的操作数的秩大于或等于另一个操作数类型的秩,则将有符号整数类型的操作数转换为无符号整数类型的操作数的类型。
- 否则,如果带符号整数类型的操作数的类型可以表示无符号整数类型的操作数类型的所有值,则将无符号整数类型的操作数转换为有符号整数类型的操作数的类型。
- 否则,两个操作数都应转换为与带符号整数类型的操作数类型对应的无符号整数类型。
我已经强调了在这里生效的段落,至于为什么:
[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]:
如果目标类型是无符号的,则结果值是与源整数一致的最小无符号整数(模 2 n其中n是用于表示无符号类型的位数)。 [ 注意:在二进制补码表示中,这种转换是概念性的,位模式没有变化(如果没有截断)。——尾注]
[C++11: 4.7/3]:
如果目标类型是有符号的,则如果它可以在目标类型(和位域宽度)中表示,则该值不变;否则,该值是实现定义的。
这意味着std::size_t(-1)
; std::numeric_limits<std::size_t>::max()
至关重要的是,上述子句中的值n与用于表示无符号类型的位数有关,而不是与源类型有关。否则,我们会做std::size_t((unsigned int)-1)
,这根本不是一回事——它可能比我们想要的值小很多数量级!
事实上,现在我们知道转换都是明确定义的,我们可以测试这个值:
std::cout << (std::size_t(-1) == std::numeric_limits<size_t>::max()) << '\n';
// "1"
而且,为了说明我之前的观点,在我的 64 位系统上:
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"