取两个size_t对象的差异是否安全
Is it safe to take the difference of two size_t objects?
我正在为我的团队研究使用 size_t
与 int
(或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_t
,a - 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_t
是unsigned short
。在这种情况下,结果是一个int
,一切都会正常工作(除非int
宽度不超过short
(。然而
- 我不知道有任何平台可以做到这一点。
- 这仍然是特定于平台的,请参阅第一段。
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。
因此,结果是,您的代码实际上非常可移植,因为几乎所有主流机器都是基于符号扩展和位截断的转换规则的二进制补充,包括有符号和无符号之间的转换规则。
但是,在从无符号转换为有符号时,当值被扩大时,不会发生符号扩展。因此,他的一个问题是int
比size_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
。
- 在什么条件下使用 std::memcpy 在对象之间复制是安全的?
- 线程调用的函数对对象删除是否安全?
- 将对象的字节复制到数组并再次复制回来是否安全
- std::memmove在同一对象之间是否始终安全
- asio 链对象线程安全吗?
- 同时调用 ASIO 对象的 API 是否安全?
- 如何将带有缓冲区的对象从插件发送到节点线程安全
- 对于琐碎的对象,在"this"上调用新放置是否安全?
- 单一实例对象是否通过线程安全返回shared_ptr
- 编译器在 const ref 类型参数上使用临时对象时是否应该警告不安全的行为?
- 我怎么知道C++编译器是否制作线程安全的静态对象代码
- 在容器中存储指向对象的指针时的线程安全
- 将对象(如 STL 对象)传入和传出静态库是否安全
- 原子对象在普通对象安全的任何上下文中都是不安全的
- 编写安全包装类以管理用户定义对象的指针
- 链接 gcc 6、gcc 7 和 gcc 8 对象是否安全?
- 关于"pure"函数对象的常量和线程安全
- 通过引用从 c++ 函数异常返回对象是否安全
- 调用不访问已删除对象中的任何类成员的类方法是否安全
- 对象切片:通过按值派生为 Base - 安全还是危险?