二维数组的反向迭代器奇怪的行为

Sei*_*eun 10 c++ visual-c++ reverse-iterator

我有一个二维数组。按正向迭代行是完全可以的,但是当我反向执行时,它不起作用。我不明白为什么。

我正在使用 MSVC v143 和 C++20 标准。

int arr[3][4];
for (int counter = 0, i = 0; i != 3; ++i) {
    for (int j = 0; j != 4; ++j) {
        arr[i][j] = counter++;
    }
}

std::for_each(std::begin(arr), std::end(arr), [](auto const& row) {
    for (auto const& i: row) {
        fmt::print("{} ", i);
    }
    fmt::print("\n");
});

std::for_each(std::rbegin(arr), std::rend(arr), [](auto const& row) {
    for (auto const& i: row) {
        fmt::print("{} ", i);
    }
    fmt::print("\n");
});
Run Code Online (Sandbox Code Playgroud)

第一个的输出for_each很好:

0 1 2 3
4 5 6 7
8 9 10 11
Run Code Online (Sandbox Code Playgroud)

然而第二个是垃圾:

-424412040 251 -858993460 -858993460
-424412056 251 -858993460 -858993460
-424412072 251 -858993460 -858993460
Run Code Online (Sandbox Code Playgroud)

当我打印他们的地址时,我无法理解:

<Row addr=0xfbe6b3fc58/>
0 1 2 3
<Row addr=0xfbe6b3fc68/>
4 5 6 7
<Row addr=0xfbe6b3fc78/>
8 9 10 11
<Row addr=0xfbe6b3fb98/>
-424412040 251 -858993460 -858993460
<Row addr=0xfbe6b3fb98/>
-424412056 251 -858993460 -858993460
<Row addr=0xfbe6b3fb98/>
-424412072 251 -858993460 -858993460
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?

Gug*_*ugi 5

这很可能是与多维数组指针相关的 MSVC 代码生成错误:std::reverse_iterator::operator*()基于范围的循环中的隐藏本质上是执行 a *--p,其中是指向数组末尾的p指针类型。int[4]在单个语句中递减和取消引用会导致 MSVC 加载局部变量的地址,p而不是被递减的 指向的前一个元素的地址,本质上导致返回p局部变量的地址。p

您可以在以下独立示例中更好地观察问题(https://godbolt.org/z/x9q5M74Md):

#include <iostream>

using Int4 = int[4]; // To avoid the awkward pointer-to-array syntax
int arr[3][4] = {};

Int4 & test1()
{
  Int4 * p = arr;
  Int4 * pP1 = p + 1;

  // Works correctly
  --pP1;
  Int4 & deref = *pP1;
  return deref;
}

Int4 & test2()
{
  Int4 * p = arr;
  Int4 * pP1 = p + 1;

  // msvc incorrectly stores the address of the local variable pP1 (i.e. &pP1) in deref
  Int4 & deref = *--pP1;
  return deref;
}


int main()
{
  std::cout << "arr   = 0x" << &arr[0][0] << std::endl;
  std::cout << "test1 = 0x" << &test1() << std::endl; // Works
  std::cout << "test2 = 0x" << &test2() << std::endl; // Bad
}
Run Code Online (Sandbox Code Playgroud)

在此示例中,&test1()正确打印 的第一个元素的地址arr。但实际上打印的是局部变量&test2()的地址,即它打印的。MSVC 甚至警告返回局部变量的地址(C4172)。clang 和 gcc 工作正常。MSVC v19.23 之前的版本也可以正确编译代码。test2::pP1&test2::pP1test2()pP1

查看汇编输出,clang 和 gcc 为test1()和发出相同的代码test2()。但 MSVC 正在做:

; test1()
mov     rax, QWORD PTR pP1$[rsp]
mov     QWORD PTR deref$[rsp], rax

; test2()
lea     rax, QWORD PTR pP1$[rsp]
mov     QWORD PTR deref$[rsp], rax
Run Code Online (Sandbox Code Playgroud)

请注意lea代替mov语句,这意味着test2()加载 的地址pP1

MSVC 似乎对多维数组的指针感到困惑。