*(&arr + 1) - arr 如何给出数组大小

Rah*_*ami 36 c++ arrays

int arr[] = { 3, 5, 9, 2, 8, 10, 11 };      
int arrSize = *(&arr + 1) - arr;
std::cout << arrSize;
Run Code Online (Sandbox Code Playgroud)

我不知道这是如何工作的。所以任何人都可以帮助我解决这个问题。

Som*_*ude 31

如果我们将数组与指针一起“绘制”,它将看起来像这样:

+--------+--------+-----+--------+-----+
| arr[0] | arr[1] | ... | arr[6] | ... |
+--------+--------+-----+--------+-----+
^        ^                       ^
|        |                       |
&arr[0]  &arr[1]                 |
|                                |
&arr                             &arr + 1
Run Code Online (Sandbox Code Playgroud)

表达式&arr和的类型&arr + 1int (*)[7]。如果我们取消引用这些指针中的任何一个,我们会得到一个 type 值int[7],并且与所有数组一样,它将衰减为指向其第一个元素的指针。

所以,发生了什么是我们采取的第一个元素的指针之间的差值&arr + 1(反引用真正使这UB,但仍会有任何理智的编译工作)和指针的第一个元素&arr

所有指针运算都是在指向类型的基本单元中完成的,在这种情况下是int,因此结果是int所指向的两个地址之间的元素数。


知道数组会自然地衰减到指向其第一个元素的指针可能很有用,即表达式arr将衰减到&arr[0],其类型为int *

此外,对于任何指针(或阵列)p和索引i,所述表达*(p + i)恰好等于p[i]。所以*(&arr + 1)实际上与(&arr)[1](这使得UB更加明显)相同。

  • @ThomasMatthews 是的,取消引用将首先发生。如果没有它,减法就不可能进行,因为指针将具有不相关的类型(“int (*)[7]”和“int *”)。取消引用“(&amp;arr + 1)”会生成“int [7]”类型的值,然后该值衰减为“int *”,使指针类型相同。这里的取消引用仅更改指针类型,而不更改其数值。 (2认同)
  • @AyxanHaqverdili 请记住,表达式不能具有引用类型。http://eel.is/c++draft/expr#type-1 并且 `decltype` 将假的 `&amp;` 和 `&amp;&amp;` 添加到表达式类型中,以指示这些表达式的值类别(分别用于左值和 x 值;类型纯右值不变)。 (2认同)

Cal*_*eth 19

该程序具有未定义的行为。(&arr + 1)是指向“超越”的有效指针arr,并且具有 type int(*)[7],但它不指向 an int [7],因此取消引用它是无效的。

碰巧的是,您的实现假设在int [7]您声明的那个之后还有一秒,并从指针算法发明的虚构数组的第一个元素的位置减去该数组的第一个元素的位置。

  • 我不确定这绝对是UB。我问了一个相关的问题([取消引用指向数组类型的结束指针](/sf/ask/3690893181/))和“这是否合法”的答案似乎是“这是一个活跃的核心语言问题”。编辑:它仍然不应该被使用,因为它还不是绝对允许的。 (13认同)
  • @AyxanHaqverdili:clang 不接受它作为 `constexpr`,但可以使用 `return ...` 编译它。https://godbolt.org/z/r96YhWEe1 - 使用 `-fsanitize=undefined`,clang 使 asm 检查堆栈指针是否在虚拟地址空间顶部的 28 个字节内,因此它会检查指针溢出。(当然运行得很好)。这可能表明它不“理解”/“接受”正在发生的事情;它只是写入了该内存,因此如果没有崩溃,指针肯定是有效的。所以它可能没有意识到它只是在执行有效对象的末尾 1 操作。 (3认同)
  • @PeterCordes 有趣的分析。该标准要求编译器检查 constexpr 中未定义的行为。可以这么说,我在那里使用 constexpr 来“获取编译器的意见”:) (3认同)
  • @FrançoisAndrieux clang 认为它是 UB,GCC 和 MSVC 认为它不是 https://godbolt.org/z/r4e14e9MG (2认同)

Adr*_*ica 10

您需要探索&arr表达式的类型是什么,以及它如何影响+ 1对它的操作。

指针算术在指向类型的“原始单位”中工作;&arr数组地址,因此它指向类型为“7 个整数的数组”的对象。添加1到该指针实际上将类型的大小7 * sizeof(int)添加到地址 - 因此添加到地址。

但是,在外部表达式(减去arr)中,操作数是指向int对象1(不是数组)的指针,因此“单位”只是sizeof(int)- 比内部表达式小 7 倍。因此,减法导致数组的大小。


1这是因为,在这样的表达式中,数组变量(例如第二个操作数,arr)衰减为指向其第一个元素的指针;此外,您的第一个操作数也是一个数组,因为*运算符取消引用数组指针的修改值。


关于可能的 UB 的注意事项:其他答案(及其评论)表明取消引用操作*(&arr + 1)调用未定义的行为。然而,通过这个草案 C++17 标准,有最模糊的建议它可能不会:

6.7.2 复合类型
...
3     ... 出于指针算术 (8.5.6) 和比较 (8.5.9, 8.5.10) 的目的,考虑超过 n 个元素的数组 x 的最后一个元素末尾的指针相当于一个指向假设元素 x[n] 的指针。

但我不会在这里声明“语言律师”状态,因为在该部分中没有明确提及取消引用此类指针。


Vla*_*cow 5

如果你有这样的声明

int arr[] = { 3, 5, 9, 2, 8, 10, 11 };
Run Code Online (Sandbox Code Playgroud)

表达式&arr + 1将指向数组最后一个元素之后的内存。表达式的值等于表达式的值,arr + 7其中7是上面声明的数组中的元素数。唯一的区别是表达式&arr + 1 具有类型,int ( * )[7]而表达式arr + 7 具有类型int *

因此,由于整数运算,差异( arr + 7 ) - arr将产生 7:数组中的元素数。

在另一方面,非关联化的表达&att + 1具有类型int ( * )[7],我们将得到的类型的左值int[7],这又在表达式中使用*(&arr + 1) - arr被转换成类型的指针int * ,并且具有相同的值arr + 7,因为它是指出以上。因此该表达式将产生数组中元素的数量。

这两个表达式之间的唯一区别

( arr + 7 ) - arr
Run Code Online (Sandbox Code Playgroud)

*( &arr + 1 ) - arr
Run Code Online (Sandbox Code Playgroud)

是在第一种情况下,我们需要明确指定数组中元素的数量,以获取数组最后一个元素之后的内存地址,而在第二种情况下,编译器本身将计算在数组中的最后一个元素之后的内存地址知道数组声明的数组的最后一个元素。