【问题标题】:How does *(&arr + 1) - arr give the length in elements of array arr?*(&arr + 1) - arr 如何给出数组 arr 元素的长度?
【发布时间】:2020-04-15 20:12:19
【问题描述】:
#include <iostream>
using namespace std;

int main() { 
   int  arr[5] = {5, 8, 1, 3, 6};
   int len = *(&arr + 1) - arr;
   cout << "The length of the array is: " << len;
   return 0;
} 

对于上面的代码,我不太明白这两段代码是干什么的:

*(&arr + 1) 

*(&arr)
&arr

谁能解释一下?因为当我运行以下两个代码时,我得到以下相同的输出:

&amp;arr(我觉得这个指向arr第一个元素的地址)

*(&amp;arr)那我不太明白这是做什么的,符号*&amp;arr有什么作用(即到这里的地址)?,因为我运行它们时两个输出是一样的

最后,当通过以下代码将整数 1 添加到地址时,到底发生了什么: &amp;arr + 1

【问题讨论】:

  • *(&amp;arr + 1) 调用未定义的行为。这段代码不计算数组的长度,只是坏掉了
  • 但这怎么给出长度:*(&arr + 1) - arr
  • 未定义的行为意味着任何事情都可能发生。包括意外的结果。虽然这可能只是在您的计算机和编译器上
  • 请尽快忘记这个技巧。请改用规范的sizeof(arr)/sizeof(*arr)
  • @TedLyngmo 是的,这在 C++ 中甚至更好。不过,sizeof 方法适用于 C 和 C++。

标签: c++ arrays


【解决方案1】:

这是一个雷区,但我会试一试:

  • &amp;arr 返回指向 int[5] 的指针
  • + 1 指针步进一级 int[5]
  • *(&amp;arr + 1) 将结果取消引用回 int(&amp;)[5]
    我不知道这是否会导致未定义的行为,但如果没有,下一步将是:
  • *(&amp;arr + 1) - arr 在两个int[5] 衰减到int 指针后执行指针运算,返回两个int 指针之间的差异,即5

重写以使其更清晰:

int  arr[5] = {5, 8, 1, 3, 6};

int (*begin_ptr)[5] = &arr + 0;     // begin_ptr is a  int(*)[5]
int (*end_ptr)[5]   = &arr + 1;     // end_ptr is a    int(*)[5]

// Note:
//       begin_ptr + 1        ==  end_ptr
//       end_ptr - begin_ptr  ==  1

int (&begin_ref)[5] = *begin_ptr;   // begin_ref is a  int(&)[5]
int (&end_ref)[5]   = *end_ptr;     // end_ref is a    int(&)[5]   UB here?

auto len = end_ref - begin_ref; // the array references decay into int*
std::cout << "The length of the array is: " << len << '\n'; // 5

如果它是 UB 或未打开但在分配引用的存储之前引用一个对象确实看起来有点可疑,我会留下这个问题。

【讨论】:

  • 构造一个指向最后一个元素之后的指针很好,所以&amp;arr + 1 没问题。取消引用它是 UB 所以*(&amp;arr + 1) 不行。
  • @bolov 是的,我正盯着我写的东西,并试图与自己推理。它仍然没有访问内存,只是类型发生了变化。这有关系吗?
  • 是的,您正在取消对末尾的引用,这是不允许的。我现在非常努力地想出利用相同技术的代码,但找不到任何东西。我即将问语言律师问题。
  • 这段代码实际上并没有解引用超过arr本身的结尾,它是创建一个指向arr结尾的指针,但它并没有取消引用该指针来访问arr 本身的内存。现在,代码使用了一个临时指针,将整个 arr 视为一个单元素数组,用于 +1 算术,然后取消引用该指针,但因为后备内存是另一个数组,而这段代码只是在操作指针,而不是实际访问任何内存,我不确定行为实际上是未定义的......
【解决方案2】:

例子:

int  arr[] = {1, 2, 3, 4, 5, 6}; 
int size = *(&arr + 1) - arr; 

在这里,指针算术发挥了作用。我们不需要将每个位置显式转换为字符指针。

&amp;arr ==> 指向包含 6 个元素的数组的指针。 [请参阅此处了解 &arr 之间的区别 和arr]

(&amp;arr + 1) ==> 前面6个整数的地址为 指针类型是指向数组的指针 6 个整数。

*(&amp;arr + 1) ==> 与 (&arr + 1) 相同的地址,但 指针的类型是“int *”。

*(&amp;arr + 1) - arr ==> 因为 *(&arr + 1) 点 到地址 6 个整数 在arr之前,区别 两者之间是 6。

【讨论】:

  • 您好,对于您的第一个亮点,您能否提供“this”的链接?对于您的第二个亮点,您能解释一下“前面 6 个整数的地址”是什么意思吗?前面6个整数是什么意思(前面是什么对不起,从哪里开始计算)?
  • @john_w 在arr的起始地址之前
  • @john_w 向指针加 1 实际上会增加它指向的总位数。所以(&amp;arr + 1) 将增加 6 位(假设 int 的大小为 1)。跨度>
【解决方案3】:

鉴于以下事实:

  • 当您将指针增加/减少整数值X 时,指针的值将增加/减少X 乘以指针指向的类型的字节数。

  • 当你减去 2 个相同类型的指针时,结果是它们持有的地址之间的差除以所指向的类型的字节数。

  • 当您仅通过名称引用数组时,它衰减为指向数组第一个元素的指针。

arr 变量的类型是int[5],即5 个ints 的数组。 &amp;arr 返回一个指向arrint[5]* 指针(从技术上讲,它实际上写成int(*)[5],但为了简单起见,这里不用担心)。让我们在下面调用这个指针temp

然后,+ 1temp 的值增加 1 个 int[5] 元素。换句话说,存储在temp 中的地址增加了1 * sizeof(int[5])1 * (sizeof(int) * 5) 字节数。这有效地为您提供了一个指向arr 末尾的int[5]* 指针(即指向&amp;arr[5])。没有int[5] 元素物理上 存在于该内存地址,但创建指向它的指针是合法的,用于指针运算。

取消引用temp 会在arr 的末尾为您提供对int[5] 的引用。当传递给operator- 时,该引用衰减int* 指针。

- arr 中,当传递给operator- 时,对arr 的引用衰减int* 指向arr[0] 的指针。

因此,给定以下代码:

int len = *(&amp;arr + 1) - arr;

实际上与此相同:

int len = &amp;arr[5] - &amp;arr[0];

实际上与此相同:

int len = (&lt;address of arr[5]&gt; - &lt;address of arr[0]&gt;) / sizeof(int);

因此,结果是 5。

【讨论】:

    【解决方案4】:

    也许我来不及参与讨论,但我认为这是一个很好的问题,应该得到更彻底的答案。

    我最初在 here 看到 op sn-p

    这里一共有4个操作。

    &amp;arr 实际上做的是动态创建一个二维数组,它的第一个维度等于 1,并获取指向这个二维数组头部的指针。 如果您不熟悉 2d 数组,Shahbaz 已经很好地介绍了它 Why can't we use double pointer to represent two dimensional arrays?
    特别是post中array2的结构体,指向这个新创建的二维数组的指针类型为int (*)[5]

    &amp;arr+1 中的+1 对其第一维进行指针运算。回想一下,第一个维度只是 1。这正是 (&amp;arr + 1) 指向原始数组末尾之后的内存地址的原因。

    *(&amp;arr + 1) 中的* 将二维数组指针(类型为int (*)[5])转换回一维数组指针(类型为int*)。

    最后*(&amp;arr + 1) - arr中的- arr是指针减法。按标准(N1570):

    6.5.6 加法运算符
    ....
    9 当两个指针相减时,两个指针都指向同一个数组对象的元素,或者指向数组对象最后一个元素的元素;结果是两个数组元素的下标之差。

    最后一个问题出现了,在How do I determine the size of my array in C?的讨论中 我们知道sizeof 方法只适用于堆栈上的数组,但是这个方法呢?不幸的是,此方法也仅适用于堆栈。如果您在函数内收到数组的指针,则数组大小信息会丢失,您无法为其动态创建二维数组。接下来的指针算术将简单地崩溃。

    【讨论】:

      【解决方案5】:

      &arr ==> 指向包含 n 个元素的数组的指针。 (&arr + 1) ==> 前面 6 个整数的地址为 指针类型是指向数组的指针 n 个整数。

      *(&arr + 1) ==> 与 (&arr + 1) 相同的地址,但是 指针的类型是“int *”。

      *(&arr + 1) - arr ==> 因为 *(&arr + 1) 点 到地址 n 个整数 在arr之前,区别 两者之间是n。

      1. (&arr + 1) 指向数组末尾之后的内存地址。
      2. *(&arr + 1) 只是将上述地址转换为 int *。
      3. 数组的结束地址减去数组的开始地址,得到数组的长度。

      关于此的更多信息...>>>>>>>>>>

      诀窍是使用表达式 (&arr)[1] - arr 来获取数组 arr 的大小。 arr 和 &arr 都指向同一个内存位置,但它们的类型不同。

      1. arr 的类型为 int* 并衰减为指向第一个元素的指针 大批。因此,关于数组大小的任何知识都消失了。

      2. &arr 产生一个 int (*)[n] 类型的指针,即指向一个数组的指针 n 个整数。所以,&arr 指向整个数组和 *(&arr + 1) (或 &arr)[1]) 指向数组之后的下一个字节。

      这是因为指针算法在 C 中的工作方式。我们知道指向 int 的指针在递增 1 时会前移 sizeof(int)。类似地,指向 int[n] 的指针会前移 sizeof(int[ n]),即整个数组的大小。

      【讨论】:

        猜你喜欢
        • 2010-11-18
        • 2019-12-19
        • 1970-01-01
        • 1970-01-01
        • 2020-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-06-16
        相关资源
        最近更新 更多