reverse_iterator 二维数组的奇怪行为
reverse_iterator weird behavior with 2D arrays
我有一个二维数组。按正向顺序迭代行是完全可以的,但是当我反向执行时,它不起作用。我不知道为什么。
我正在使用 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");
});
第一个 for_each
的输出很好:
0 1 2 3
4 5 6 7
8 9 10 11
然而第二个是垃圾:
-424412040 251 -858993460 -858993460
-424412056 251 -858993460 -858993460
-424412072 251 -858993460 -858993460
当我打印他们的地址时,我无法理解:
<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
这里发生了什么?
这很可能是 MSVC 的代码生成错误,与指向多维数组的指针有关:隐藏在 range-based 循环中的 std::reverse_iterator::operator*()
本质上是在执行 *--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
}
在此示例中,&test1()
正确打印了 arr
的第一个元素的地址。但是 &test2()
实际上打印了 local 变量 test2::pP1
的地址,即它打印了 &test2::pP1
。 MSVC 甚至警告说 test2()
returns 局部变量的地址 pP1
(C4172)。
clang 和 gcc 工作正常。 MSVC v19.23 之前的版本也能正确编译代码。
查看汇编输出,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
注意 lea
而不是 mov
语句,这意味着 test2()
加载 pP1
.
的地址
MSVC 似乎对指向多维数组的指针感到困惑。
我有一个二维数组。按正向顺序迭代行是完全可以的,但是当我反向执行时,它不起作用。我不知道为什么。
我正在使用 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");
});
第一个 for_each
的输出很好:
0 1 2 3
4 5 6 7
8 9 10 11
然而第二个是垃圾:
-424412040 251 -858993460 -858993460
-424412056 251 -858993460 -858993460
-424412072 251 -858993460 -858993460
当我打印他们的地址时,我无法理解:
<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
这里发生了什么?
这很可能是 MSVC 的代码生成错误,与指向多维数组的指针有关:隐藏在 range-based 循环中的 std::reverse_iterator::operator*()
本质上是在执行 *--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
}
在此示例中,&test1()
正确打印了 arr
的第一个元素的地址。但是 &test2()
实际上打印了 local 变量 test2::pP1
的地址,即它打印了 &test2::pP1
。 MSVC 甚至警告说 test2()
returns 局部变量的地址 pP1
(C4172)。
clang 和 gcc 工作正常。 MSVC v19.23 之前的版本也能正确编译代码。
查看汇编输出,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
注意 lea
而不是 mov
语句,这意味着 test2()
加载 pP1
.
MSVC 似乎对指向多维数组的指针感到困惑。