【问题标题】:Is "int *ptr = *( ( &a ) + 1 );" where "a" is int[5] well-defined by the Standard?是“int *ptr = *( ( &a ) + 1 );”其中“a”是标准明确定义的 int[5]?
【发布时间】:2018-12-05 12:33:42
【问题描述】:

基于这个问题 (strange output issue in c) 有一个关于这一行的答案 (provided by @Lundin):

int *ptr = (int*)(&a+1);

他说的地方:

the cast (int*) was hiding this bug.

所以我带来了以下内容:

#include <stdio.h>

int main( void ){
    int a[5] = {1,2,3,4,5};

    int *ptr = *( ( &a ) + 1 );
    printf("%d", *(ptr-1) );
}

我想知道是不是这样:

int *ptr = *( ( &a ) + 1 );

标准是否明确定义?

编辑

在某些时候@chux 指向§6.3.2.3.7,即:

A pointer to an object type may be converted to a pointer to a different object type. If the
resulting pointer is not correctly aligned68) for the referenced type, the behavior is
undefined. Otherwise, when converted back again, the result shall compare equal to the
original pointer. When a pointer to an object is converted to a pointer to a character type,
the result points to the lowest addressed byte of the object. Successive increments of the
result, up to the size of the object, yield pointers to the remaining bytes of the object.

但我不确定我是否理解正确。

【问题讨论】:

  • @EugeneSh。它在 print 语句中向后移动 1,将其返回到数组的末尾,这应该是有效的。
  • ( &amp;a ) + 1 - 指向数组。然后取消引用并分配给ptr
  • @EugeneSh。 &amp;a 的类型为int (*a)[5],因此*((&amp;a) + 1) 的类型为int *,并指向数组末尾之后的地址。
  • @user3386109 (&amp;a) + 1 的类型为 (*a)[5] 并指向数组。 *((&amp;a) + 1) 正在取消引用它。
  • @Michi 使指针“过去”一个对象的地址不是问题。指针数学定义明确。如何使用该指针是一个问题,特别是如果它被取消引用,就像这里一样。

标签: c arrays pointers language-lawyer pointer-arithmetic


【解决方案1】:

由于取消引用运算符*,此表达式调用未定义的行为:

int *ptr = *( ( &a ) + 1 );

首先,让我们从( &amp;a ) + 1 开始。这部分是有效的。 &amp;a 具有 int (*)[5] 类型,即指向大小为 5 的数组的指针。通过加 1 执行指针运算是有效的,即使 a 不是数组的元素。

C standard 详细说明加法运算符的第 6.5.6 节中,第 7 段指出:

对于这些运算符,指向对象的指针是 不是数组元素的行为与指向第一个元素的指针相同 长度为 1 的数组的元素,对象的类型为其 元素类型。

还允许创建一个指向数组末尾后一个元素的指针。所以&amp;a + 1是允许的。

问题是当我们取消引用这个表达式时。第 8 段指出:

当一个整数类型的表达式被添加或减去时 从一个指针,结果具有指针操作数的类型。如果 指针操作数指向数组对象的一个​​元素,而数组 足够大,结果指向一个元素偏移量 原始元素使得下标的差异 结果和原始数组元素等于整数表达式。 换句话说,如果表达式 P 指向一个 数组对象,表达式(P)+N(相当于N+(P))和(P)-N (其中 N 的值为 n)分别指向第 i+n 个和 数组对象的第 i-n 个元素,前提是它们存在。此外,如果 表达式 P 指向数组对象的最后一个元素,即 表达式 (P)+1 指向数组对象的最后一个元素后一个, 如果表达式 q 指向数组的最后一个元素之后 对象,表达式 (Q)-1 指向数组的最后一个元素 目的。如果指针操作数和结果都指向元素 相同的数组对象,或数组的最后一个元素 对象,评估不应产生溢出;否则, 行为未定义。 如果结果指向最后一个元素之后 数组对象,不得用作一元 * 的操作数 被评估的运算符。

由于不允许取消引用指向数组末尾之后的指针,因此行为未定义。

回到引用帖子中的表达式:

int *ptr = (int*)(&a+1);
printf("%d %d", *(a+1), *(ptr-1));

这也是未定义的行为,但出于不同的原因。在这种情况下,int (*)[5] 将转换为 int *,然后使用转换后的值。使用这种转换值是合法的唯一情况是将对象指针转换为指向字符类型的指针,例如char *unsigned char * 并随后取消引用以读取对象表示的字节。

编辑:

似乎上面的两行实际上定义得很好。在指针解引用*(ptr-1) 发生时,被访问的对象具有有效类型int,它与ptr-1 的解引用类型匹配。将指针值&amp;a+1int (*)[5] 强制转换为int * 是有效的,并且对强制转换的指针值执行指针运算也是有效的,因为它指向a 内部或超过它的一个元素。

【讨论】:

  • 第二个引号以“被评估”结尾。要了解这意味着什么,请参阅第 6.5.3.2/3 节。
  • @user3386109 在这种情况下会评估 * 运算符,因为它不是 &amp; 运算符的直接操作数。
  • 关于int *ptr = (int*)(&amp;a+1) 不同意“这种转换合法的唯一情况是将对象指针转换为指向字符类型的指针”。相反,“指向对象类型的指针可能会转换为指向不同对象类型的指针。如果生成的指针未正确对齐引用的类型,则行为未定义。” C11 §6.3.2.3 7. 由于对齐不是问题,因此转换定义明确。
  • @chux 虽然转换本身很好,但它使用转换后的值是个问题,即取消引用转换后的指针。因此,虽然分配给ptr 是可以的,但随后读取*(ptr-1) 是不行的。编辑澄清。
  • malloc 的情况是可以的,因为返回的内存在分配之前没有有效的类型。实际上,现在我考虑一下,我同意你和@chux。转换有效且ptr指向a末尾之后的一个元素,后续的指针运算是有效的,因为结果指向a内部,并且取消引用是有效的,因为访问对象的有效类型(即int) 匹配取消引用的指针类型。
【解决方案2】:

*( ( &amp;a ) + 1 ) 是 UB 由于

... 如果结果指向数组对象的最后一个元素,它 不得用作评估的一元 * 运算符的操作数。 C11 §6.5.6 8

( &amp;a ) + 1 指向“过去”。使用* 与“不应”相悖。

int a[5] = {1,2,3,4,5};
int *ptr = *( ( &a ) + 1 );

即使 aint a 这也适用,因为

就这些运算符而言,指向不是数组元素的对象的指针与指向长度为 1 且对象类型作为其元素类型的数组的第一个元素的指针的行为相同。 §6.5.6 7

【讨论】:

  • 如果aint aptr 就必须是int,这样就可以清楚地看到UB;所以我认为不需要答案的第二部分。
  • @Acorn 分配指向int 的指针有其自身的问题 6.3.2.3 5. 然而这里的 UB 仅存在于 *( ( &amp;a ) + 1 ) 上。
  • 我不确定这是否直接适用,因为它实际上并不是指向数组末尾的指针。取消引用使它成为一个指向数组末尾之后的指针。
  • @ChristianGibbons &amp;a 是一个指针。 ( &amp;a ) + 1 是“过去”并且是一个指针。 §6.5.6 8 清楚地解决了使用**( ( &amp;a ) + 1 ) 一样的问题。
【解决方案3】:

int *ptr = *( ( &amp;a ) + 1 ); 被调用未定义的行为。

C11 - §6.5.6“加法运算符”(P8):

当一个整数类型的表达式被添加到指针或从指针中减去时, 结果具有指针操作数的类型。 如果指针操作数指向一个元素 一个数组对象,并且数组足够大,结果指向一个元素的偏移量 原始元素使得结果和原始的下标的差异 数组元素等于整数表达式。 换句话说,如果表达式P 指向 数组对象的i-th 元素,表达式(P)+N(等效于N+(P))和 (P)-N(其中N 的值为n)分别指向i+n-th 和i−n-th 元素 数组对象,前提是它们存在。此外,如果表达式P 指向最后一个 数组对象的元素,表达式(P)+1 指向数组对象的最后一个元素 数组对象,如果表达式Q 指向数组对象的最后一个元素, 表达式(Q)-1 指向数组对象的最后一个元素。[...]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-07
    • 1970-01-01
    • 2017-12-11
    • 1970-01-01
    • 1970-01-01
    • 2022-12-17
    • 2013-04-19
    • 2014-08-25
    相关资源
    最近更新 更多