c++ - clang++ 9.0 如何神奇地治愈 lambda 中的悬挂引用使用?

标签 c++ lambda clang++ dangling-pointer

我在 wandbox 上进行试验,希望找到编译器警告,以帮助解决 lambda 中无意中的悬挂引用。我有 this example以多种方式表现不当:

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++ 和所有版本的 clang++ 直到 8.x 都是这种情况。但是clang 9.0奇迹般的用一种神奇的方式处理了它:

0x7ffd193bfa30 0x7ffd193bfa30 0x7ffd193bfa30 0x7ffd193bfa30 
0x7ffd193bfa30: 0, 0x7ffd193bfa30: 1, 0x7ffd193bfa30: 2, 0x7ffd193bfa30: 3

真正有趣的部分是指向引用的指针在所有四个 lambda 中具有相同的值 - 但取消引用它们会返回四个不同的值。我试图想出一个很好的解释来解释这是怎么回事,但我很困惑。

我猜这是一种有意的优化,其中编译器推断出我想要做什么,并让它发生。由于使用悬空引用属于“未定义行为”类别,因此编译器可以自由地做它喜欢的事。但真的是这样吗?

如果编译器足够聪明,可以解决这个问题,它似乎也足够聪明,可以发出警告,但我不明白。

最佳答案

根据评论系列,特别是来自@RaymondChen 的评论,很明显 clang 没有解决这个问题。生成的代码使它看起来 就像他们修复了它,但这只是一些非常偶然的未定义行为。

几乎可以 100% 确定,我们可以这样说:

  • lambda 函数创建了一个对变量 i 的悬垂引用,这是一个在使用 lambda 之前从堆栈中消失的 auto。
  • 然后调用 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 的 Lifetime profile如果成功的话,这将是一个非常好的方法。

关于c++ - clang++ 9.0 如何神奇地治愈 lambda 中的悬挂引用使用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59398973/

相关文章:

c++ - std::cout 在具有静态存储持续时间的对象的构造函数中的使用

c# - Lambda 方法来填充 ToDictionary() 方法中的值字段?

c# - 在 C# 中编写匿名函数的推荐方法是什么?

c++ - 使用uniform_real_distribution时clang性能下降

c++ - 这是编译器错误还是我的代码?

c++ - 什么是 CInternetSession 的必要清理

c++ - 错误 : Vector subscript out of range c++

c++ - 由于内存损坏,整数溢出是否会导致未定义的行为?

c++11 - C++11 lambda 的正确缩进

c++ - 为什么简单地使用 ostringstream 会生成这么多汇编代码?