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

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

本文关键字:对象 安全 是否 size 两个      更新时间:2023-10-16

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

#include <iostream>
main()
{
    size_t a = 10;
    size_t b = 100;
    int result = a - b;
}

该程序产生了-90,虽然正确,但让我对类型不匹配、有符号/无符号问题或只是普通的未定义行为感到紧张,如果size_t碰巧用于复杂的数学。

我的问题是,用size_t对象做数学是否安全,特别是取差异?我正在考虑使用 size_t 作为索引等内容的标准。我在这里看到了一些关于这个主题的有趣文章,但它们没有解决数学问题(或者我错过了(。

减去 2 size_t的什么类型?

可以包含size_t的有符号类型的 typedef?

这不能保证可以移植工作,但也不是 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

经验法则(转述上述视频中的钱德勒·卡鲁斯(:如果您可以自己计算,请使用int,否则使用std::int64_t


除非它的转换等级小于int,例如如果std::size_tunsigned short。在这种情况下,结果是一个int,一切都会正常工作(除非int宽度不超过short(。然而

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

size_t 类型是无符号的。任何两个size_t值的减法是定义的行为

但是,首先,如果从较小的值中减去较大的值,则结果是实现定义的。结果是数学值,简化为最小的正残基模SIZE_T_MAX + 1。例如,如果 size_t 的最大值为 65535,并且减去两个size_t值的结果为 -3,则结果将是 65536 - 3 = 65533。 在不同的编译器或具有不同size_t的机器上,数值将不同。

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

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

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

因此,结果是,您的代码实际上非常可移植,因为几乎所有主流机器都是基于符号扩展和位截断的转换规则的二进制补充,包括有符号和无符号之间的转换规则。

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

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

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