我曾经认为,无论整数类型如何,总是很好地定义向指针添加整数类型(假设指针指向特定大小的数组等)。 C ++ 11标准说([expr.add]):
当向指针添加或从指针中减去具有整数类型的表达式时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式。换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+ N(等效地,N +(P))和(P)-N(其中N具有值n)指向分别为数组对象的第i + n和第i - n个元素,只要它们存在即可。此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向一个超过数组对象的最后一个元素,如果表达式Q指向一个超过数组对象的最后一个元素,表达式(Q)-1指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出;否则,行为未定义。
另一方面,最近引起我注意的是,指针的内置添加运算符是以ptrdiff_t
来定义的,这是一个有符号的类型(见13.6 / 13)。这似乎暗示如果一个malloc()
具有非常大(无符号)的大小,然后尝试通过添加std::size_t
值的指针到达已分配空间的末尾,这可能会导致未定义的行为,因为无符号std::size_t
将转换为可能为UB的ptrdiff_t
。
我想象会出现类似的问题,例如在operator[]()
的{{1}}中,这是以无符号std::vector
的形式实现的。总的来说,在我看来,这样几乎不可能完全使用平台上可用的内存存储。
值得注意的是,在向指针添加无符号值时,GCC和Clang都没有抱怨签名无符号积分转换,所有相关诊断都已打开。
我错过了什么吗?
编辑:我想澄清一下,我在谈论涉及指针和整数类型(不是两个指针)的添加内容。
EDIT2 :制定问题的等效方式可能是这个。如果size_type
的正范围小于ptrdiff_t
,此代码是否会在第二行产生UB?
size_t
答案 0 :(得分:5)
您的问题基于错误的前提。
减去指针会产生ptrdiff_t§[expr.add] / 6:
当减去指向同一数组对象的元素的两个指针时,结果是两个数组元素的下标的差异。结果的类型是实现定义的有符号整数类型;此类型应与标题(18.2)中定义为std :: ptrdiff_t的类型相同。
然而不意味着添加是根据ptrdiff_t
定义的。相反,仅添加一个转换(§[expr.add] / 1):
对算术或枚举类型的操作数执行通常的算术转换。
“通常的算术转换”在§[expr] / 10中定义。这仅包括从无符号类型到签名类型的一次转换:
否则,如果带有符号整数类型的操作数的类型可以表示具有无符号整数类型的操作数类型的所有值,则具有无符号整数类型的操作数应转换为带有符号整数的操作数的类型类型。
因此,虽然可能存在一些问题,但确切地说size_t
将被转换为什么类型(以及它是否完全转换)可能存在问题,但毫无疑问的一点是:它可以被转换的唯一方式到ptrdiff_t
,如果其所有值都可以在不更改的情况下表示为ptrdiff_t
。
所以,给定:
size_t N;
T *p;
...表达式p + N
永远不会失败,因为在添加之前N
转化为ptrdiff_t
会有一些(想象的)转换。< / p>
由于提到了§13.6,或许最好备份并仔细查看§13.6到底是什么:
本子条款中规定了表示第5章中定义的内置运算符的候选运算符函数。这些候选函数参与运算符重载决策过程,如13.3.1.2所述,仅用于其他用途。
[强调补充]
换句话说,§13.6定义了一个将ptrdiff_t
添加到指针的运算符的事实不意味着当任何其他整数类型添加到指针时,它是第一个转换为ptrdiff_t,或类似的东西。更一般地说,§13.6中定义的运算符从不用于执行任何算术运算。
有了这个,以及你从§[expr.add]中引用的其余文本,我们可以很快得出结论,当指针添加size_t
时,只有在没有那么多元素时才会溢出在指针之后的数组中。
鉴于上述情况,您可能会再遇到一个问题。如果我有这样的代码:
char *p = huge_array;
size_t N = sizeof(huge_array);
char *p2 = p + N;
ptrdiff_t diff = p2 - p;
......最终的减法可能会溢出吗?对此的简短答案是:是的,它可以。