【问题标题】:Why is this code involving arrays and pointers behaving as it does?为什么此代码涉及数组和指针的行为如此?
【发布时间】:2021-10-07 12:18:52
【问题描述】:

有人问我以下代码的输出是什么:

int a[5] = { 1, 3, 5, 7, 9 };
int *p = (int *)(&a + 1);
printf("%d, %d", *(a + 1), *(p - 1));
  1. 3, 9
  2. 错误
  3. 3, 1
  4. 2, 1

答案是NO.1


很容易得到*(a+1)是3。

但是int *p = (int *)(&a + 1);*(p - 1) 呢?

【问题讨论】:

  • 如果您删除演员表(int *),警告将说明发生了什么。 initialization of ‘int *’ from incompatible pointer type ‘int (*)[5]’
  • Coderon,提示:避免强制转换 - 它们往往会隐藏弱代码。
  • 看起来像一个在线求职面试问题
  • 在我看来像是一道考试题。我希望您从这个问题中学到的一件事以及您发现它是多么难以回答的是,在现实世界的程序中进行这样的指针杂耍并不是一个好主意。
  • 这里的关键是&a中,a这个名字不会衰减为指向它第一个元素的指针,所以是的地址数组。它的类型是指向 5 个数组的指针 int

标签: c language-lawyer


【解决方案1】:

这个问题的答案可能是“1) 3,9”或“2) 错误”(或者更具体地说是 undefined behavior),具体取决于您阅读 C standard 的方式。

首先,让我们来看看这个:

&a + 1

& 运算符获取数组a 的地址,为我们提供int(*)[5] 类型的表达式,即指向大小为5 的int 数组的指针。向此添加1 将指针视为指向int [5] 数组的第一个元素,结果指针指向 a 之后。

此外,即使&a 指向一个单一对象(在本例中为int [5] 类型的数组),我们仍然可以在该地址上加1。这是有效的,因为 1) 指向单个对象的指针可以被视为指向大小为 1 的数组的第一个元素的指针,以及 2) 指针可能指向数组末尾之后的一个元素。

C 标准的第 6.5.6p7 节规定了以下关于将指向对象的指针视为指向大小为 1 的数组的第一个元素的指针:

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

第 6.5.6p8 节说明了以下关于允许指针指向数组末尾的内容:

当一个整数类型的表达式被添加到或 从指针中减去,结果具有指针的类型 操作数。如果指针操作数指向数组的元素 对象,并且数组足够大,结果指向一个元素 从原始元素偏移,使得 结果和原始数组元素的下标等于 整数表达式。换句话说,如果表达式 P 指向 i - 数组对象的第一个元素,表达式(P)+N (等效地,N+(P))和(P)-N(其中N 的值为n)指向, 分别是数组对象的第 i+n-th 和 i−n-th 个元素, 只要它们存在。此外,如果表达式P 指向 数组对象的最后一个元素,表达式(P)+1 指向过去 数组对象的最后一个元素,如果表达式Q 指向一个数组对象的最后一个元素, 表达式(Q)-1 指向数组的最后一个元素 如果指针操作数和结果都指向 同一数组对象的元素,或最后一个元素 数组对象的元素,评估不应产生 溢出;否则,行为未定义。如果结果点 超过数组对象的最后一个元素,不得用作 计算的一元 * 运算符的操作数。

现在是有问题的部分,即演员阵容:

(int *)(&a + 1)

这会将int(*)[5] 类型的指针转​​换为int * 类型。这里的意图是将指向int [5]的1元素数组末尾的指针更改为int的5元素数组末尾。

但是,C 标准并不清楚是否允许这种转换以及对结果的后续操作。假设指针正确对齐,它确实允许从一种对象类型转换为另一种对象类型并返回。虽然对齐不应该是一个问题,但使用这个指针是不确定的。

所以这个指针赋值给p

int *p = (int *)(&a + 1)

然后按如下方式使用:

*(p - 1)

如果我们假设p 有效地指向数组a 之后的一个元素,则从中减去1 会得到指向数组最后一个元素的指针。 * 运算符然后将此指针取消引用到最后一个元素,产生值 9。

所以如果我们假设(int *)(&a + 1) 产生一个有效的指针,那么答案是 1) 3,9 否则答案是 2) 错误。

【讨论】:

  • 使用这个指针是不确定的 那么,你也不相信offsetof 在 C 中有用吗?
  • @LanguageLawyer offsetof 是标准的一部分,因此从用户的角度来看,它如何做并不重要。
  • 您的回答与我的评论有何关联?我是否写过offsetof 无法实现?您对可以对转换指针执行的操作的分析意味着不能转换为指向char 的指针,向它添加一些偏移量并转换为指向某个对象类型的指针以尝试获取指向结构成员的指针在偏移处。
  • sizeof(int[5]) 保证等于sizeof(int)*5 没有填充?如果不是,那是另一种失败模式
  • 如果我们有未定义的行为,那么答案不能是 1)、2)、3) 和 4) 吗?为什么只有 1) 和 2)?
【解决方案2】:

排队

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

注意写的是&a,而不是a。这很重要。

如果只是写了a,那么数组将有decayed 指向指向第一个元素的指针,即指向&a[0]。但是,由于使用了表达式&a,因此该表达式的结果与使用a&a[0] 的值相同,但类型不同:类型是指向5 的数组的指针int 元素,而不是指向单个 int 元素的指针。

根据指针运算规则,将指针递增1 将使内存地址增加它所指向的对象的大小。由于指针不是指向单个元素,而是指向一个由 5 个元素组成的数组,因此内存地址将增加5 * sizeof(int)。因此,在增加指针后,指针的值(但不是类型)将等价于&a[5],即在数组末尾之后。

在将此指针转换为int * 并将结果分配给p 后,表达式p 完全等效于&a[5](在值和类型上)。

因此,表达式*(p - 1)等价于*(&a[5] - 1),也就是等价于*(&a[4]),或者干脆a[4]

【讨论】:

    【解决方案3】:

    这个:

    &a + 1;
    

    a 的地址,一个数组,加1,这增加了一个a 的大小,即5 个整数。然后索引“倒退”,一个整数,以a 的最后一个元素结束。

    【讨论】:

      【解决方案4】:

      通常,只要在表达式中使用数组,它们就会“衰减”为指向第一个元素的指针。此规则有一些例外,其中一个例外是 & 运算符。

      &a 因此产生一个指向int (*)[5] 类型数组的指针。那么&a + 1是对这种类型的指针运算,意味着指针地址增加了一个int [5]的大小。我们最终会指向数组之外,但 C 实际上允许我们这样做,只要我们不取消引用该位置。

      然后将指针强制类型转换为(int *),我们也可以这样做 - 只要我们不取消引用或导致未对齐等,C 几乎允许任何方式的野指针转换。

      p - 1int 类型进行指针运算,并且数组中的实际数据类型也是int,因此我们可以取消引用该位置。我们最终到达数组的最后一项。

      【讨论】:

        猜你喜欢
        • 2023-03-25
        • 2020-12-01
        • 2010-12-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-23
        • 1970-01-01
        相关资源
        最近更新 更多