*((*(&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))