*((*(&array + 1)) – 1)可以安全地使用自动数组的最后一个元素吗?
假设我想获得大小未知的自动数组的最后一个元素。 我知道我可以使用sizeof
运算符来获得数组的大小,并相应地得到最后一个元素。
正在使用*((*(&array + 1)) - 1)
安全吗?
喜欢:
char array[SOME_SIZE] = { ... }; printf("Last element = %c", *((*(&array + 1)) - 1));
int array[SOME_SIZE] = { ... }; printf("Last element = %d", *((*(&array + 1)) - 1));
等等
我相信这是不明确的行为,因为彼得在他的回答中提到的原因。
关于*(&array + 1)
有一个巨大的争论。 一方面,dereferencing &array + 1
似乎是合法的,因为它只是将T (*)[]
types更改为T []
,但另一方面,它仍然是未初始化,未使用和未分配内存的指针。
我的答案依赖于以下几点:
C99 6.5.6.7(添加剂操作员的语义)
对于这些运算符而言,指向不是数组元素的对象的指针的行为与以对象types作为其元素types的长度为1的数组的第一个元素的指针相同。
由于&array
不是一个指向数组元素的对象的指针,所以根据这个意思,代码就相当于:
char array_equiv[1][SOME_SIZE] = { ... }; /* ... */ printf("Last element = %c", *((*(&array_equiv[0] + 1)) - 1));
也就是说, &array
是一个指向10个字符&array
的指针,所以它的行为与指向长度为1的数组的第一个元素的指针相同,其中每个元素都是10个字符的数组。
现在,连同下面的条款(在其他答案中已经提到;这个确切的摘录公然从ameyCU的答案中被盗):
C99第6.5.6.8节 –
[…]
如果expression式P指向数组对象的最后一个元素,则expression式(P)+1点[…]
如果结果指向一个超过数组对象的最后一个元素,则不应将其用作所评估的一元运算符的操作数。
清楚地表明它是UB:它相当于取消引用一个指向array_equiv
的最后一个元素的array_equiv
。
是的,在现实世界中,它可能是有效的,因为实际上原始代码并不真正解引用一个内存位置,它大部分是从T (*)[]
到T []
的types转换,但是我很确定从一个严格的标准遵从的观点,这是未定义的行为。
不它不是。
&array
的types是指向char[SOME_SIZE]
指针(在给出的第一个示例中)。 这意味着&array + 1
指向内存立即超过array
的末尾。 解除引用(如(*(&array+1))
给出未定义的行为。
无需进一步分析。 一旦expression式的任何部分给出未定义的行为,整个expression式就会发生。
我不认为这是安全的。
从标准中引用 @dasblinkenlight 在他的回答 (现在删除),还有一些我想补充:
C99第6.5.6.8节 –
[…]
如果expression式P指向数组对象的最后一个元素,则expression式(P)+1点[…]
如果结果指向一个超过数组对象的最后一个元素,则不应将其用作所评估的一元运算符的操作数。
就像它说的那样,我们不应该这样做*(&array + 1)
因为它会经过数组的最后一个元素,所以不应该使用*
。
同样众所周知, 取消引用指向未授权内存位置的指针会导致未定义的行为 。
这可能是安全的,但有一些警告。
假设我们有
T array[LEN];
那么&array
的types是T(*)[LEN]
。
接下来, &array + 1
又是typesT(*)[LEN]
,指向刚刚超过原始数组的末尾。
接下来, *(&array + 1)
的types是T[LEN]
,它可以隐式转换为T*
,仍然指向刚刚结束的原始数组。 (所以我们没有取消引用无效的内存位置: *
运算符没有被评估)。
接下来, *(&array + 1) - 1
是typesT*
,指向最后一个数组的位置。
最后,我们对此进行解引用(如果数组长度不为零,则是合法的): *(*(&array + 1) - 1)
给出最后一个数组元素,types为T
的值。
请注意,唯一一次我们实际上取消引用指针是在这最后一步。
现在,可能的警告。
首先, *(&array + 1)
正式显示为试图取消引用指向无效内存位置的指针。 但事实并非如此。 这就是数组指针的本质:这种正式的解引用只会改变指针的types,实际上并不会导致尝试从引用的位置检索值。 也就是说, array
的types是T[LEN]
但可以隐式转换为types&T
,指向数组的第一个元素; &array
是一个指向typesT[LEN]
的指针,指向数组的开始处; *(&array+1)
又是typesT[LEN]
,可以隐式转换为types&T
。 从来没有一个指针实际解除引用。
其次, &array + 1
实际上可能是一个无效的地址,但实际上并不是这样:My C ++ 11参考手册明确告诉我:“将一个超出数组末尾的指针指向一个元素保证工作”而K&R也有类似的说法,所以我认为这一直是标准的行为。
最后,在数组长度为零的情况下,expression式将取消引用arrays之前的内存位置,该位置可能是未分配的/无效的。 但是,如果使用更传统的方法使用sizeof()
而不先testing非零长度,也会出现这个问题。
总之,我不相信这个expression式的行为有任何未定义的或实现依赖的。
Imho可能工作,但可能是不明智的。 你应该仔细检查你的swdevise,并问问自己为什么你想要数组的最后一个条目。 数组的内容完全不了解,还是可以用c结构和联合来定义结构。 如果是这种情况,请避开char数组中的复杂指针操作,并在您的c代码,结构和联合中尽可能正确地定义数据。
所以,而不是:
printf("Last element = %c", *((*(&array + 1)) - 1));
它可能是 :
printf("Checksum = %c", myStruct.MyUnion.Checksum);
这澄清了你的代码。 数组中的最后一个字母对于不熟悉此数组中的内容的人意味着什么。 myStruct.myUnion.Checksum对任何人都有意义。 研究myStruct结构可以向任何人解释整个数据结构。 如果可以用这种方式声明的话,请使用类似的东西。 如果你遇到罕见的情况,你不能这样回答,我想他们是有道理的
-
array
是一个int[SOME_SIZE]
types的数组int[SOME_SIZE]
-
&array
是一个types为int(*)[SOME_SIZE]
的指针int(*)[SOME_SIZE]
-
&array + 1
是types为int(*)[SOME_SIZE]
的过去末尾指针int(*)[SOME_SIZE]
-
*(&array + 1)
是int[SOME_SIZE]
types的左值int[SOME_SIZE]
其他答案已经引用了标准的相关部分,但是我认为最后一点可能有助于澄清混淆。
混淆似乎超过了这样的想法:即使标准在技术上禁止,取消引用&array + 1
也会产生一个过去的结束int*
,这听起来应该是合理的。
但是这不是发生了什么:取消引用它试图产生一个左值 (本质上是一个引用)到一个不存在的types为int[SOME_SIZE]
对象,这真的不应该听起来合理。
即使它是定义的行为,而不是使用一个神秘的伎俩,做一些清晰的东西好多了,
template< typename T, size_t N > T& last(T (&array)[N]) { return array[N-1]; } // ... int array[SOME_SIZE] = { ... }; printf("Last element = %d", last(array));
一个)
如果指针操作数和[P + N]的结果都指向相同数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出;
[…]
如果expression式P指向数组对象的元素或指向数组对象的最后一个元素的元素,并且expression式Q指向同一数组对象的最后一个元素,则expression式((Q)+1) – ( P)具有与((Q) – (P))+ 1和 – ((P) – ((Q)+1))相同的值,并且如果expression式P指向最后一个元素即使expression式(Q)+1不指向数组对象的元素,也是如此。
这表明,使用数组元素的计算最后一个元素实际上是完全正确的。 正如一些人在这里写道,使用不存在的对象进行计算已经是非法的了,我想我已经包含了这个部分。
那么我们需要关心这个部分:
如果结果指向一个超过数组对象的最后一个元素,则不应将其用作所评估的一元运算符的操作数。
有一个重要的部分,其他答案省略,那就是:
如果指针操作数指向数组对象的一个元素
这不是事实。 我们解引用的指针操作数不是指向数组对象元素的指针,而是指向指针的指针。 所以这整个条款是完全不相关的。 但是,也有人说:
对于这些[additive]运算符来说,指向不是数组元素的对象的指针与指向长度为1的数组的第一个元素的指针的行为相同,该对象的types是元素types。
这是什么意思?
这意味着我们指向一个指针的指针实际上又是指向一个长度为[1]的数组的指针。 现在我们可以closures这个循环,因为正如第一段所述,我们可以用数组中的一个进行计算,所以我们可以用数组进行计算,就好像它是一个长度数组[2]!
以更graphics的方式:
ptr -> (ptr to int[10])[0] -> int[10] -> (ptr to int[10])[1]
所以我们可以用(ptr to int [10])[1]进行计算,尽pipe它在技术上是在长度数组之外的[1]。
b)
发生的步骤是:
types为int [SOME_SIZE]的array
ptr到第一个元素数组
&array
ptr到types为int [SOME_SIZE]的ptr到&array
的第一个元素
+ 1
ptr,比int [SOME_SIZE]types的ptr多一个)到第一个元素数组,到一个inttypes的ptr
根据C99 6.5.6.8节,这还不是指向int [SOME_SIZE + 1]的指针。 这还不是ptr + SOME_SIZE + 1
*
我们将指针取消引用到指针。 现在 ,在取消引用之后,我们有一个根据C99第6.5.6.8节的指针,该指针经过数组的元素,并且不允许被解引用。 这个指针允许存在,我们可以使用运算符,除了一元运算符。 但是我们还没有在那个指针上使用那个。
-1
现在我们从inttypes的ptr减去数组中最后一个元素之后的一个元素,让ptr指向数组的最后一个元素。
*
parsing一个ptr int到数组的最后一个元素,这是合法的。
C)
最后但并非最不重要:
如果这是非法的,那么macros的抵消也是非法的,这被定义为:
((size_t)(&((st *)0)->m))