clang++ 9.0 如何神奇地治愈 lambda 中的悬空引用使用?
How does clang++ 9.0 magically cure dangling reference use in a lambda?
我在wandbox上做实验,希望找到编译器警告,这将有助于在lambda中无意中悬空引用。我有这个以多种方式行为不端的例子:
std::array<std::function<const int *(void)>,N> createFunctions()
{
std::array<std::function<const int *(void)>,N> fns;
for ( int i = 0 ; i < N ; i++ ) {
std::cout << &i << " ";
fns[i] = [&]() {
return &i;
};
}
std::cout << "n";
return fns;
}
这会将一组 lambda 填充到函数数组中。每个 lambda 返回一个指针,指向对捕获变量的引用,i
。周围应该很麻烦。
我的驱动程序例程打印出指针,并取消引用它。
int main()
{
auto fns = createFunctions();
for (int j = 0 ; j < N ; j++ ) {
if (j != 0)
std::cout << ", ";
std::cout << fns[j]() << ": " << *fns[j]();
}
std::cout << "n";
return 0;
}
如果将此 lambda 修改为通过复制传递i
,您将获得如下输出 - 四个指针和四个具有唯一值的指针:
0x7ffc80e65358 0x7ffc80e65358 0x7ffc80e65358 0x7ffc80e65358
0x7ffc80e65380: 0, 0x7ffc80e653a0: 1, 0x7ffc80e653c0: 2, 0x7ffc80e653e0: 3
当它写错时,取一个无声地超出范围的引用,它奇迹般地运行而没有出错,但清楚地显示了它的方式错误
0x7ffeebdfe9f4 0x7ffeebdfe9f4 0x7ffeebdfe9f4 0x7ffeebdfe9f4
0x7ffeebdfe9f4: 32766, 0x7ffeebdfe9f4: 32766, 0x7ffeebdfe9f4: 32766, 0x7ffeebdfe9f4: 32766
所有四个指针都是相同的,并且它们指向的值是虚假的。
所有版本的 g++ 以及 8.x 之前的所有 clang++ 版本都是如此。但是 clang 9.0 奇迹般地以一种神奇的方式处理了它:
0x7ffd193bfa30 0x7ffd193bfa30 0x7ffd193bfa30 0x7ffd193bfa30
0x7ffd193bfa30: 0, 0x7ffd193bfa30: 1, 0x7ffd193bfa30: 2, 0x7ffd193bfa30: 3
真正有趣的是,指向引用的指针在所有四个 lambda 中都具有相同的值 - 但取消引用它们会返回四个不同的值。我试图想出一个很好的解释,解释这是怎么回事,但我被难住了。
我猜这是一种有意的优化,编译器已经推断出我想做什么,并让它发生。由于使用悬空引用属于"未定义行为"类别,因此编译器可以自由地做它喜欢的事情。但事实果真如此吗?
如果编译器足够聪明来解决这个问题,那么它似乎足够聪明,可以发出警告,但我不明白。
根据评论列车,特别是@RaymondChen的评论,很明显 clang没有解决这个问题。生成的代码使它看起来像是他们修复了它,但这只是一些非常偶然的未定义行为。
几乎 100% 确定,我们可以这样说:
- lambda函数创建了一个对变量
i
的悬空引用,一个在使用lambda之前从堆栈中消失的自动。 - 然后调用lambda指向堆栈上或周围的随机数据,其值可以是任何内容。
- 在大多数实现中,所指向的值显然不是所期望的。
- 幸运的是,在 clang 9 中,悬空的引用指针指向循环变量
j
,它在范围内,并且从 0 迭代到 3,看起来好像我们以某种方式拥有悬空引用的良好副本。
更改main()
中的循环以迭代 lambda 引用,如下所示:
for (auto fn: fns ) {
消除了循环变量 j,因此现在输出为:
0x7ffc0f21a100 0x7ffc0f21a100 0x7ffc0f21a100 0x7ffc0f21a100
0x7ffc0f21a100: 4212390, 0x7ffc0f21a100: 4212390, 0x7ffc0f21a100: 4212390, 0x7ffc0f21a100: 4212390
仍然在寻找检测这种编程错误的好方法,这为我们提供了人类永恒警惕的辅助手段。赫伯·萨特(Herb Sutter(的终身档案将是一个很好的方法,如果它发生的话。
- C++Boost Asio Pool线程,带有lambda函数和传递引用变量
- c++ lambda:柯里和函数:使用按值捕获与按引用捕获返回不同的结果
- 引用捕获和在 lambda 中通过引用发送参数有什么区别 (C++)
- 修改在 std::future 的 lambda 中引用捕获的值
- clang++ 9.0 如何神奇地治愈 lambda 中的悬空引用使用?
- 为什么 lambda nullptr 取消引用在这种情况下有效?
- 仅通过引用捕获的 lambda 表达式是否保证不会抛出?
- 当lambda执行时,C++lambda捕获的引用具有不同的值
- 无法通过引用捕获 lambda 中的成员变量
- 在 lambda 中从引用类型捕获的值的类型,不使用通用捕获
- 将引用和指针传递给 lambda
- 为什么访问我的引用捕获变量会导致我的 lambda 函数出现段错误?
- 我无法通过引用捕获传递 lambda
- 我们应该在 lambda 中通过常量引用来捕获吗?
- 在 lambda 中通过引用捕获thread_local变量无法按预期工作
- lambda 表达式中引用捕获的 constexpr 变量和非显式捕获的 constexpr 变量之间的区别
- 列出生成器(lambda的向量)会导致通过引用捕获的非常奇怪的行为
- 无法使用带有常量引用的 lambda 对 std::vector 进行排序
- 返回对 std::函数包装的 lambda 中静态变量的引用会导致段错误
- lambda 和映射,引用参数 - 编译错误