C++ 指针积分加法中关于类型、溢出和UB的混淆

C++ 指针积分加法中关于类型、溢出和UB的混淆,c++,pointers,c++11,C++,Pointers,C++11,我曾经认为,无论整数类型如何,向指针添加整数类型(前提是指针指向某个大小的数组等)总是定义良好的。C++11标准规定([expr.add]): 将具有整数类型的表达式添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的某个元素,且数组足够大,则结果将指向与原始元素偏移的元素,以便生成的数组元素与原始数组元素的下标之差等于整数表达式。换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+N(相当于N+(P))和(P)-N(其中N的值为N)分别指向i+N-th

我曾经认为,无论整数类型如何,向指针添加整数类型(前提是指针指向某个大小的数组等)总是定义良好的。C++11标准规定([expr.add]):

将具有整数类型的表达式添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的某个元素,且数组足够大,则结果将指向与原始元素偏移的元素,以便生成的数组元素与原始数组元素的下标之差等于整数表达式。换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+N(相当于N+(P))和(P)-N(其中N的值为N)分别指向i+N-th和i− 数组对象的第n个元素,前提是它们存在。此外,如果表达式P指向数组对象的最后一个元素,表达式(P)+1指向数组对象的最后一个元素,如果表达式Q指向数组对象的最后一个元素,则表达式(Q)-1指向数组对象的最后一个元素。如果指针操作数和结果都指向同一数组对象的元素,或超过数组对象最后一个元素的元素,则计算不应产生溢出;否则,行为是未定义的

另一方面,我最近注意到,指针的内置add运算符是根据
ptrdiff\u t
定义的,这是一种有符号类型(参见13.6/13)。这似乎暗示,如果一个人使用非常大(无符号)的大小执行
malloc()
,然后尝试通过使用
std::size\t
值的指针加法到达分配空间的末尾,这可能会导致未定义的行为,因为未签名的
std::size_t
将转换为
ptrdiff_t
,这可能是UB

我想类似的问题也会出现,例如,在
std::vector
操作符[]()
中,它是根据无符号
大小类型实现的。一般来说,在我看来,这样做几乎不可能充分利用平台上可用的内存存储

值得注意的是,当向指针添加无符号值时,nor GCC或Clang抱怨在打开所有相关诊断的情况下进行有符号无符号整数转换

我错过什么了吗

编辑:我想澄清一下,我所说的加法涉及指针和整数类型(而不是两个指针)

EDIT2:表述问题的一种等效方法可能是这样的。如果
ptrdiff\u t
的正范围小于
size\u 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();
char*ptr=static_cast(std::malloc(std::numeric_limits::max());
自动结束=ptr+std::数值限制::最大值();

你的问题基于错误的前提

指针相减产生一个ptrdiff_t§[expr.add]/6:

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

然而,这并不意味着加法是根据
ptrdiff\u t
定义的。相反,对于加法,只指定一个转换(§[expr.add]/1):

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

§[expr]/10中定义了“常用算术转换”。这只包括一次从无符号类型到有符号类型的转换:

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

因此,虽然关于
size\u t
将被转换为什么类型(以及是否被转换)可能存在一些疑问,但有一点是毫无疑问的:将其转换为
ptrdiff\u t
的唯一方法是,它的所有值都可以表示为
ptrdiff\u t

因此,鉴于:

size_t N;
T *p;
…表达式
p+N
永远不会失败,因为在加法发生之前,
N
会(想象)转换为
ptrdiff\t

既然有人提到了§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;
…最终减法是否可能溢出?对此,简短而简单的回答是:是的,它可以