C++ 11中使用lambdas的非确定性损坏

Jon*_*rop 5 c++ lambda visual-studio-2010 visual-c++ c++11

灵感来自Herb Sutter引人注目的演讲不是你父亲的C++,我决定再使用微软的Visual Studio 2010来看看最新版本的C++.我特别感兴趣的是Herb断言C++是"安全的",因为我没有听说过C++ 11解决了众所周知的向上funarg问题.据我所知,C++ 11无法解决这个问题,因此不"安全".

您不希望返回对局部变量的引用,因为本地是在函数返回后将不再存在的堆栈帧上分配的,因此,该函数将返回一个悬挂指针,指向已分配的内存,这将导致非 - 确定数据损坏.C和C++编译器知道这一点,并在您尝试将引用或指针返回到本地时发出警告.例如,这个程序:

int &bar() {
  int n=0;
  return n;
}
Run Code Online (Sandbox Code Playgroud)

导致Visual Studio 2010发出警告:

warning C4172: returning address of local variable or temporary
Run Code Online (Sandbox Code Playgroud)

但是,C++ 11中的lambdas可以很容易地通过引用捕获局部变量并返回该引用,从而产生等效的悬空指针.考虑以下函数foo返回一个lambda函数,该函数捕获局部变量n并返回它:

#include <functional>

std::function<int()> foo(int n) {
  return [&](){return n;};
}
Run Code Online (Sandbox Code Playgroud)

这种看似无害的功能是内存不安全和损坏数据的来源.调用此函数将lambda放在一个位置然后调用lambda并在另一个地方打印它的返回值为我提供了这个输出:

1825836376
Run Code Online (Sandbox Code Playgroud)

此外,Visual Studio 2010不提供任何警告.

对我来说,这看起来像是一个非常严重的设计缺陷.即使是最简单的重构也可能会产生一个lambda交叉堆栈帧,无声地引入非确定性数据损坏.然而,似乎有关于这个问题的宝贵信息很少(例如,在StackOverflow上搜索"向上funarg"和C++没有提供命中).人们是否意识到这一点?有人在研究解决方案或描述变通方法吗?

Luc*_*ton 3

这并不是 lambda 所特有的,在生命周期中你可能会做很多坏事(你已经注意到至少一种情况)。虽然与 C++03 相比,C++11 在某些方面可能更安全,但 C++ 并未强调内存安全。

这并不是说 C++不想安全,但我想说的是,通常的哲学“不要为不使用的东西付费”通常会妨碍添加安全防护措施(不考虑诸如停止问题可能会阻止对所有无效程序发出诊断)。如果您可以解决向上 funarg 问题,同时不影响其他所有案例的性能,那么标准委员会就会感兴趣。(我并不是说以刻薄的方式,我认为这是一个有趣且困难的问题。)

由于您似乎正在追赶,那么到目前为止,作者(和其他人)的智慧是通常避免对 lambda 表达式(例如[&, foo, bar])使用按引用捕获所有捕获,并小心使用 by-一般而言,参考捕获。您可以将 lambda 表达式的捕获列表视为 C++ 中必须小心生命周期的另一个地方;或者另一种观点是将 lambda 表达式视为函子的对象文字表示法(实际上它们是这样指定的)。当你设计一个关于生命周期的类类型时,你必须小心:

struct foo {
    explicit foo(T& t)
        : ref(t)
    {}

    T& ref;
};

foo make_foo()
{
    T t;
    // Bad
    return foo { t };
    // Not altogether different from
    // return [&t] {};
}
Run Code Online (Sandbox Code Playgroud)

在这方面,lambda 表达式不会改变编写“明显”错误代码的现状,并且它们继承了所有先前存在的警告。