当 C++ 将元素从函数的 return 值存储到 std::vector 时出现意外结果
Unexpected result when C++ store element into std::vector from return value of function
当函数涉及重分配时,我发现有些编译器可能会保存函数调用前的地址。它导致存储在无效地址中的return值。
上面的描述中有一个例子来解释行为。
#include <stdio.h>
#include <vector>
using namespace std;
vector<int> A;
int func() {
A.push_back(3);
A.push_back(4);
return 5;
}
int main() {
A.reserve(2);
A.push_back(0);
A.push_back(1);
A[1] = func();
printf("%d\n", A[1]);
return 0;
}
有一些常见的C++编译器,测试结果如下
- GCC(GNU 编译器集合):运行时错误或输出
1
- Clang: 输出
5
- VC++:输出
5
这是未定义的行为吗?
此行为在 C++17 之前的所有 C++ 版本中均未定义。原因很简单,赋值运算符的两侧可以按任意顺序求值:
- 假设首先对
A[1]
求值,你会得到一个 int&
指向 A
的第二个元素。
- 然后,评估
func()
,这可以重新分配向量的存储,留下先前检索的 int&
悬空引用。
- 最后,执行分配,写入未分配的存储。由于标准分配器缓存内存,OS 通常不会捕获此错误。
仅在 C++17 中,special rule 20 进行了赋值:
In every simple assignment expression E1=E2 and every compound
assignment expression E1@=E2, every value computation and side-effect
of E2 is sequenced before every value computation and side effect of
E1
对于 C++17,A[1]
必须在调用 func()
之后进行计算,然后提供定义的、可靠的行为。
如果您检查 documentation,在 "Iterator Invalidation" 下,您会看到 push_back()
如果更改容量,可能会使 每个迭代器 无效,因为它必须重新分配内存。请记住,对于 std::vector
,指针也是有效的迭代器。因为 push_back()
可能会也可能不会重新分配,而且您无法知道它是否会,所以行为是未定义的。
当函数涉及重分配时,我发现有些编译器可能会保存函数调用前的地址。它导致存储在无效地址中的return值。
上面的描述中有一个例子来解释行为。
#include <stdio.h>
#include <vector>
using namespace std;
vector<int> A;
int func() {
A.push_back(3);
A.push_back(4);
return 5;
}
int main() {
A.reserve(2);
A.push_back(0);
A.push_back(1);
A[1] = func();
printf("%d\n", A[1]);
return 0;
}
有一些常见的C++编译器,测试结果如下
- GCC(GNU 编译器集合):运行时错误或输出
1
- Clang: 输出
5
- VC++:输出
5
这是未定义的行为吗?
此行为在 C++17 之前的所有 C++ 版本中均未定义。原因很简单,赋值运算符的两侧可以按任意顺序求值:
- 假设首先对
A[1]
求值,你会得到一个int&
指向A
的第二个元素。 - 然后,评估
func()
,这可以重新分配向量的存储,留下先前检索的int&
悬空引用。 - 最后,执行分配,写入未分配的存储。由于标准分配器缓存内存,OS 通常不会捕获此错误。
仅在 C++17 中,special rule 20 进行了赋值:
In every simple assignment expression E1=E2 and every compound assignment expression E1@=E2, every value computation and side-effect of E2 is sequenced before every value computation and side effect of E1
对于 C++17,A[1]
必须在调用 func()
之后进行计算,然后提供定义的、可靠的行为。
如果您检查 documentation,在 "Iterator Invalidation" 下,您会看到 push_back()
如果更改容量,可能会使 每个迭代器 无效,因为它必须重新分配内存。请记住,对于 std::vector
,指针也是有效的迭代器。因为 push_back()
可能会也可能不会重新分配,而且您无法知道它是否会,所以行为是未定义的。