签名溢出未定义。无符号溢出定义为模运算。
所以我的问题是,以下定义或未定义:
#include <assert.h>
#include <cstdint>
struct X { int x;/* ... anything ... */ };
X array[3] = { 2, 3, 4 /* or other init values that is compatible with X */};
X* element = array + 1;
std::uintptr_t plus1 = 1;
std::uintptr_t minus1 = 0-plus1;
int main()
{
printf("%p\n%p\n", element + plus1, array + 2);
printf("\n");
printf("%p\n%p\n", element + minus1, array);
assert(element + plus1 == array + 2);
assert(element + minus1 == array);
}
虽然我陈述plus1
/ minus1
,但我的意思是任何+/-值。如果我理解正确,这应该工作。我是对的吗?
答案 0 :(得分:3)
只要涉及的指针保留在单个数组对象中或刚好超过数组的末尾,指针算法就只能很好地定义。从技术上讲,表达式element + minus1
是未定义的行为 - 因为minus1
是一个非常大的值,它会超出数组的末尾。
现在在实践中,这可能会起作用,但在技术上仍未定义。
答案 1 :(得分:2)
指针算法在抽象机器中定义。
在抽象机器中,ptr+x
仅在对象中存在ptr
时才有效,以使其地址在边缘的-x
范围内。
这个抽象机器不关心指针或有符号或无符号整数的特定大小。在此抽象机器中,有符号和无符号整数的值为实数整数或未指定的值。
具有32位minus1
的 uintptr_t
等于0xffffffff,一个大的正整数。
element
是否在一个足够大的对象中指向0xffffffff * sizeof(X)以后它仍然在对象中?不,它没有。
所以element+minus1
是未定义的操作。任何事情都可能发生。
在你的硬件上,指针算法在机器代码中的天真错误可能导致它缠绕。但依靠这一点并不安全。
首先,优化者有时喜欢证明事物。如果他们证明element
大于array
的地址,那么 element
的无符号添加可能使其等于array
。因此,编译器可以将element+unsigned value == array
优化为false
。
如果您更改优化设置,升级编译器或完全无害的内容(如更改内联内容,内联其他代码或链接时优化启发式或月亮更改阶段),则可能会发生此类优化。
依赖它工作是危险的,即使它确实如此,因为你现在负责审核而不是源代码,而是它生成的机器代码。
答案 2 :(得分:1)
std::uintptr_t
是无符号积分。std::intptr_t
是签名的积分。因此std::uintptr_t
的溢出被定义,而std::intptr_t
的溢出导致UB。
此外,指针算法仅在数组内有效(到数组末尾之后)。
minus1
是std::uintptr_t
可以容纳的最大数字。
来自http://en.cppreference.com/w/cpp/language/operator_arithmetic
- 如果指针P指向数组的第i个元素,则表达式P + n,n + P和Pn是指向i + nth,i + nth和i-的相同类型的指针。分别是同一数组的第n个元素。指针添加的结果也可以是一个过去的结束指针(即指针P,使得表达式P-1指向数组的最后一个元素)。任何其他情况(即,尝试生成指向不指向同一数组的元素或指向结束的指针的指针)会调用未定义的行为。
element + minus1
与element - 1
不同。
element + minus1
超出了有效的数组范围,因此导致UB。