关于指针 - 积分加法中的类型,溢出和UB的混淆

blu*_*rni 8 c++ pointers c++11

我曾经认为,无论整数类型如何,总是很好地定义向指针添加整数类型(假设指针指向一定大小的数组等).C++ 11标准说([expr.add]):

当向指针添加或从指针中减去具有整数类型的表达式时,结果具有指针操作数的类型.如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式.换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+ N(等效地,N +(P))和(P)-N(其中N具有值n)指向分别为数组对象的第i + n和第i - n个元素,只要它们存在即可.此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向一个超过数组对象的最后一个元素,如果表达式Q指向一个超过数组对象的最后一个元素,表达式(Q)-1指向数组对象的最后一个元素.如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义.

另一方面,最近我注意到指针的内置add运算符是按照ptrdiff_t有符号类型定义的(见13.6/13).这似乎暗示,如果一个人做了malloc()一个非常大的(无符号)的大小,然后试图达到通过指针还加有分配的空间的末端std::size_t值,这可能会导致不确定的行为,因为无符号std::size_t将被转换为ptrdiff_t这可能是UB.

我想类似的问题就会出现,例如在operator[]()std::vector,这是在一个无符号的条件来实现size_type.一般来说,在我看来,这样几乎不可能完全使用平台上可用的内存存储.

值得注意的是,在向指针添加无符号值时,GCC和Clang也没有抱怨签名无符号积分转换与所有相关诊断打开.

我错过了什么吗?

编辑:我想澄清我正在谈论涉及指针和整数类型(不是两个指针)的添加.

EDIT2:制定问题的等效方式可能是这个.如果ptrdiff_t正范围小于size_t?,此代码是否会在第二行产生UB ?

char *ptr = static_cast<char * >(std::malloc(std::numeric_limits<std::size_t>::max()));
auto end = ptr + std::numeric_limits<std::size_t>::max();
Run Code Online (Sandbox Code Playgroud)

Jer*_*fin 5

您的问题基于错误的前提.

减去指针会产生ptrdiff_t§[expr.add]/6:

当减去指向同一数组对象的元素的两个指针时,结果是两个数组元素的下标的差异.结果的类型是实现定义的有符号整数类型; 此类型应与标题(18.2)中定义为std :: ptrdiff_t的类型相同.

这并没有,但是,意味着除了在来定义ptrdiff_t.相反,仅添加一个转换(§[expr.add]/1):

对算术或枚举类型的操作数执行通常的算术转换.

"通常的算术转换"在§[expr]/10中定义.这仅包括从无符号类型到签名类型的一次转换:

否则,如果具有有符号整数类型的操作数的类型可以表示具有无符号整数类型的操作数类型的所有值,则具有无符号整数类型的操作数应转换为具有有符号整数类型的操作数的类型.

因此,虽然可能存在一些问题,但确切地说size_t将转换为什么类型(以及它是否完全转换),但在一点上毫无疑问:ptrdiff_t如果它的所有值都可以转换为a的唯一方法就是作为一个代表,没有变化ptrdiff_t.

所以,给定:

size_t N;
T *p;
Run Code Online (Sandbox Code Playgroud)

...表达式p + N永远不会因为一些(想象)转换失败Nptrdiff_t加入发生之前.

由于提到了§13.6,或许最好备份并仔细查看§13.6到底是什么:

表示本条款中定义的内置运算符的候选运算符函数在本子条款中规定.这些候选函数参与13.3.1.2中描述的运算符重载解析过程,并且不用于其他目的.

[强调补充]

换句话说,§13.6定义了一个ptrdiff_t向指针添加a的运算符的事实并不意味着当任何其他整数类型被添加到指针时,它首先被转换为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;
Run Code Online (Sandbox Code Playgroud)

...最终的减法可能会溢出吗?简单而简单的答案是:是的,它可以.