*(&arr+1)-arr如何给出数组大小
int arr[] = { 3, 5, 9, 2, 8, 10, 11 };
int arrSize = *(&arr + 1) - arr;
std::cout << arrSize;
我不知道这是如何工作的。所以任何人都可以帮助我解决这个问题。
回答
如果我们将数组与指针一起“绘制”,它将看起来像这样:
+--------+--------+-----+--------+-----+
| arr[0] | arr[1] | ... | arr[6] | ... |
+--------+--------+-----+--------+-----+
^ ^ ^
| | |
&arr[0] &arr[1] |
| |
&arr &arr + 1
表达式&arr和的类型&arr + 1是int (*)[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 Yes, dereference will happen first. Without it, the subtraction wouldn't be possible because the pointers would have unrelated types (`int (*)[7]` and `int *`). Dereferencing `(&arr + 1)` produces a value of type `int [7]`, which then decays to `int *`, making the pointer types the same. The dereference here merely changes the pointer type, and not its numerical value.
- @AyxanHaqverdili Remember that expressions can't have reference types. http://eel.is/c++draft/expr#type-1 And `decltype` adds fake `&` and `&&` to types of expressions to indicate value categories of those expressions (for lvalues and xvalues respectively; types of prvalues are unchanged).
回答
该程序具有未定义的行为。(&arr + 1)是指向“超越”的有效指针arr,并且具有 type int(*)[7],但它不指向 an int [7],因此取消引用它是无效的。
碰巧的是,您的实现假设在int [7]您声明的那个之后还有一秒,并从指针算法发明的虚构数组的第一个元素的位置减去该数组的第一个元素的位置。
- I'm not sure this is definitely UB. I asked a related question ([Dereferencing one past the end pointer to array type](https://stackoverflow.com/questions/52727045/dereferencing-one-past-the-end-pointer-to-array-type)) and the answer to "is it legal" seems to be "it's an active core language issue". Edit : it still shouldn't be used, as it is also not definitely allowed yet.
- @AyxanHaqverdili: clang doesn't accept it as `constexpr`, but compiles it ok with `return ...`. https://godbolt.org/z/r96YhWEe1 - With `-fsanitize=undefined`, clang makes asm that checks if the stack pointer is within 28 bytes of the top of virtual address space, so it's checking for pointer overflow. (It of course runs fine). That may be a sign that it doesn't "understand" / "accept" what's going on; it just wrote that memory so the pointer's definitely valid if it didn't crash. So probably it doesn't realize that it's just doing 1-past-end of a valid object.
- @PeterCordes Interesting analysis. The standard requires compilers to check for undefined behavior in constexpr. I used constexpr there to "get compilers' opinions" so to speak 🙂
- @FrançoisAndrieux clang thinks it's UB, GCC and MSVC think it's not https://godbolt.org/z/r4e14e9MG
回答
您需要探索&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] 的指针。
但我不会在这里声明“语言律师”状态,因为在该部分中没有明确提及取消引用此类指针。
回答
如果你有这样的声明
int arr[] = { 3, 5, 9, 2, 8, 10, 11 };
表达式&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
和
*( &arr + 1 ) - arr
是在第一种情况下,我们需要明确指定数组中元素的数量,以获取数组最后一个元素之后的内存地址,而在第二种情况下,编译器本身将计算在数组中的最后一个元素之后的内存地址知道数组声明的数组的最后一个元素。