在C中,给定
char arr [10];
uint32_t i = -1;
char * p = &arr [1];
(p + i) 是否溢出/未定义或等于 &arr [0]? 为什么C标准(6.5.6.8)中的指针算术规则如此混乱?
语言定义指针可以对任何整数类型做+,-,+=,-=操作,当指针添加负整数值时会发生什么?如果指针的表示形式为 4 个字节,但整数操作数int64_t,该怎么办?
C99标准在数组索引术语(6.5.6.8)中定义了指针算法,根据我的理解,它指出:
char * ptr = …;
char * new_ptr = ptr + int_expr;
assert( (new_ptr - ptr) == (int_expr) );
模糊的间接定义是什么原因?
将-1
分配给uint32_t
将其转换为UINT32_MAX
(即4294967295
),每减少模数6.2.5p9。
所以你的代码等效于:
char arr [10];
uint32_t i = UINT32_MAX;
char * p = &arr [1];
p
指向数组arr
中的第二个元素。所以p+i
,即p + 4294967295
,产生一个指针,该指针肯定不在数组对象内。所以这将是未定义的行为。
例如,如果将i
类型更改为int32_t
,则它可以保存负值(正如您最初所希望的那样)。p + i
,即p - 1
会发出指向数组arr
中第一个元素的指针(相当于&arr[0]
)。没有未定义的行为 因为生成的指针p + i
(==&arr[0]
) 仍然指向数组对象并且完全有效。
是的,它会溢出,不,它不会等于 &arr[0]。
因为变量i
是uint32_t
类型,所以它实际上没有值-1
,而是非常大的数字4294967295
,11111111 11111111 11111111 11111111
二进制或0xFFFFFFFF
十六进制。
如果将 i 的类型更改为类似int
的值,则i
的值将-1
,(p+i)
将引用arr[0]
鉴于您的示例,i
是无符号数据类型,您肯定会指向数组之外arr
因为-1
被视为0xFFFFFFFF
。 但这里令人困惑的部分可能不是指针算术,而是索引变量类型转换期间的溢出。
另一方面,使用带符号数据类型进行i
您将处于安全状态:
只要在一个数据对象的范围内,指针算术就是安全的。您也可以将一个元素指向数组的最后一个元素之后。
在C中,无论您编写*(arr+i)
还是arr[i]
都完全相同。 这意味着您的示例
char * p = &arr [1];
与
char * p = arr+1;
由此你可以推导出p + (-1)
等于arr+1-1 == arr
并且等于&arr[0]
这完全可以指向该数组的边界内。
你指的是:
将整数类型的表达式加到或减去时 从指针中,结果具有指针操作数的类型。如果 指针操作数指向数组对象的元素,并且数组 足够大,结果指向 原始元素使得下标的差异 结果和原始数组元素等于整数表达式。
这并不令人困惑,这只是指针算法的定义。并应读作:
E array[N];
assert( (&array[X]+D) == &array[X+D] );
前提是 X 和 X+D 都在 [0,N] 中(您可以将一个指向最后一个元素)。
D
可以是任何整数表达式。在您的情况下,它具有无符号整数类型(-1,因为uint32_t
是UINT32_MAX
),因此它是未定义的行为,因为结果超出了数组 (1+UINT32_MAX>10) 的范围。
如果你使用了int32_t
,结果将指向数组的第一个元素:
char array[10];
assert( (&array[1]-1) == &array[0] );