取两个 size_t 对象的差是否安全?

Is it safe to take the difference of two size_t objects?

我正在为我的团队调查使用 size_tint(或 long 等)的标准。我看到指出的最大缺点是取两个 size_t 对象的差异可能会导致问题(我不确定具体问题——也许有些东西不是 2s 补码和 signed/unsigned 愤怒编译器)。我使用 V120 VS2013 编译器用 C++ 编写了一个快速程序,它允许我执行以下操作:

#include <iostream>

main()
{
    size_t a = 10;
    size_t b = 100;
    int result = a - b;
}

该程序导致了 -90,尽管它是正确的,但让我担心类型不匹配、signed/unsigned 问题,或者如果 size_t 恰好被用于复杂的数学。

我的问题是用 size_t 对象做数学运算是否安全,特别是求差? 我正在考虑使用 size_t 作为索引之类的标准。我在这里看到了一些关于该主题的有趣帖子,但它们没有解决数学问题(或者我错过了)。

What type for subtracting 2 size_t's?

typedef for a signed type that can contain a size_t?

这不能保证可移植地工作,但也不是 UB。代码必须 运行 没有错误,但生成的 int 值是实现定义的。因此,只要您在保证所需行为的平台上工作,这就很好(当然只要差异可以用 int 表示),否则,只需在所有地方使用签名类型(见最后一段) .

减去两个 std::size_t 将产生一个新的 std::size_t,其值将由换行决定。在您的示例中,假设 64 位 size_ta - b 将等于 18446744073709551526。这不适合(常用的 32 位)int,因此将实现定义的值分配给 result.

老实说,除了位魔术,我建议不要使用无符号整数。标准委员会的几个成员同意我的观点:https://channel9.msdn.com/Events/GoingNative/2013/Interactive-Panel-Ask-Us-Anything 9:50, 42:40, 1:02:50

经验法则(从上面的视频中解释 Chandler Carruth):如果您可以自己数,请使用 int,否则使用 std::int64_t


除非其转换排名低于int,例如如果 std::size_tunsigned short。在这种情况下,结果是 int 并且一切正常(除非 int 不比 short 宽)。不过

  1. 我不知道有任何平台这样做。
  2. 这仍然是特定于平台的,请参阅第一段。

如果你不使用 size_t,你就完蛋了:size_t 是一种用于内存大小的类型,因此保证它总是足够大那个目的。 (uintptr_t 非常相似,但它既不是第一个这样的类型,也不是标准库使用的,也不是不包含 stdint.h。)如果你使用 int,当您的分配超过 2GiB 的地址 space(如果您在 int 只有 16 位的平台上,则为 32kiB!)时,您可能会出现未定义的行为,即使机器有更多内存并且您正在执行在 64 位模式下。

如果您需要可能变为负数的 size_t 差值,请使用带符号的变体 ssize_t

size_t 类型是无符号的。任意两个size_t值相减就是defined-behavior

但是,首先,如果用较小的值减去较大的值,结果是implementation-defined。结果是数学值,减少到最小正余数模 SIZE_T_MAX + 1。例如,如果 size_t 的最大值是 65535,并且减去两个 size_t 值的结果是 -3,那么结果将是 65536 - 3 = 65533。在不同的编译器或机器上有不同size_t,数值会不同

其次,size_t 值可能超出类型 int 的范围。如果是这种情况,我们会得到第二个 implementation-defined 由强制转换产生的结果。在这种情况下,任何行为都可以适用;它只需要由实现记录,并且转换不能失败。例如,结果可以限制在 int 范围内,生成 INT_MAX。在将更宽(或等宽)的无符号类型转换为更窄的有符号类型时,在二进制补码机(几乎所有)上看到的一个常见行为是简单的位截断:从无符号值中取出足够的位来填充有符号值,包括其符号位.

由于二进制补码的工作方式,如果原始算术上正确的抽象结果本身适合 int,则转换将产生该结果。

例如,假设在二进制补码机上将一对 64 位 size_t 值相减,得到抽象算术值 -3,即变为正值 0xFFFFFFFFFFFFFFFD。当它被强制转换为 32 位 int 时,那么在二进制补码机的许多编译器中常见的行为是将低 32 位作为结果 int 的映像:0xFFFFFFFD.当然,这只是 32 位中的值 -3。

所以结果是,你的代码事实上相当可移植因为几乎所有主流机器都是基于转换规则的二进制补码关于符号扩展和位截断,包括有符号和无符号之间。

除了在从无符号转换为有符号时加宽值时不会发生符号扩展。因此,他的一个问题是 intsize_t 宽的罕见情况。如果16位size_t的结果是65533,由于1减4,转换为32位int时不会产生-3;它将产生 65533!