问题 使用棘手的lambda表达式的奇怪的未定义行为


我一直在努力解决lambda表达式的一个问题,这个问题正在破坏我的一个项目。我找到了一个解决方案,但我想确切地了解它的工作原理和原因,以及它是否可靠。

#include <iostream>
#include <functional>
#include <unordered_map>

typedef std::function<const int&(const int&)> Callback;

int f(int i, Callback callback) {
    if (i <= 2) return 1;
    return callback(i-1) + callback(i-2);
}

int main(void) {

    std::unordered_map<int, int> values;

    Callback callback = [&](const int& i) {
        if (values.find(i) == values.end()) {
            int v = f(i, callback);
            values.emplace(i, v);
        }
        return values.at(i);
    };

    std::cout << f(20, callback) << std::endl;

    return 0;

}

我知道这是计算第20个Fibonacci数的一种疯狂方法,但它是我能够详细说明的最紧凑的SSCCE。

如果我用上面的代码编译 g++ -O0 我得到了执行程序 6765,这实际上是第20个斐波纳契数。如果我编译 -O1-O2 要么 -O3 我明白了 262144,这是垃圾。

如果我用Valgrind配置程序(用。编译) -O0 -g),我明白了 Conditional jump or move depends on uninitialised value(s) 在线上 std::cout << f(20, callback) << std::endl; 但堆栈跟踪没有说任何有用的东西。

我不知道为什么我最终得到了这个:

Callback callback = [&](const int& i) -> const int& {

通过这一点修改,一切都按预期编译任何优化级别,Valgrind报告没有问题。

你能帮我理解发生了什么吗?


4939
2017-08-16 15:01


起源

只是出于好奇,程序没有 & 小号 - Bhathiya Perera
这是lambda合法的C ++ 11吗?也就是说,在我不看的时候,他们是否会在多行返回类型中扣除? - Yakk - Adam Nevraumont
@Casey - 正式地,如果编译器发出诊断信息,您可以将其称为符合标准的扩展。 - Pete Becker
延期?因此,如果lambda是多线的,那么标准不允许(或考虑)扣除? - gd1
@ gd1是的,C ++ 11只允许对lambdas进行返回类型推导,lambdas的主体是单个return语句(第5.1.2 / 4节)。 - Casey


答案:


没有 -> const int& lambda的返回类型是 int。自返回类型 Callback 是 const int&callback 最终返回对用于保存lambda表达式返回值的临时值的引用。未定义的行为。

对于这个简单的例子,简单的解决方法是根本不使用引用(Coliru的例子),但我认为你的真实代码处理比重量级更重的对象 int。不幸的是 std::function 未指定在分配返回对a的非引用的函数时发出警告 std::function 谁的回报类型  一个参考。


12
2017-08-16 15:22



我认为编译器可以从中推断出实际的返回类型 Callback 类型,也与事实相符 std::unordered_map::at(...) 返回一个引用。 或者至少拒绝编译错误。谢谢!编辑:是的,对象不是 int秒。 - gd1
返回类型推导就像 auto 和模板函数参数推导,即当你喂它时 T& 它演绎到了 T。 - Casey
事实上我被迫使用 auto&,但在lambda“签名”我不能使用 -> &, 不知何故。 - gd1
@ GD1 -> decltype(auto) 将演绎 int& 对于C ++ 14中的这个lambda,不确定它是否在g ++中实现。 - Casey
-> decltype(some_expr) 但是会工作的 - Xeo


卡西回答 是现货。我想补充一点。 (事实上​​,在凯西的好答案之上添加正在成为我的常见行为;-)。)实际上,我的评论是 GD1的评论 卡西岗位

我猜OP正在使用 g++ -std=c++1y 因为,在C ++ 11中,那里显示的lambda的返回类型设置为 void。实际上,只有当lambda的主体包含单个时,编译器才会推断出返回的类型 return 声明。否则,编译器假定此类型为 void。在C ++ 14中,返回类型的自动演绎(不仅对于lambda而且对于函数)更强大。

有人可以看到 n3582  - 批准用于C ++ 14的论文 gcc的实施是一个参考 - 说明

普通汽车从未推断出参考,[...]

在OP的示例中,返回的类型是 int (不 const int&)。我相信一个人可以使用 -> decltype(auto) 有自动扣除产生参考。


3
2017-08-16 15:50



谢谢你提供信息。但是我正在使用 --std=c++11。 - gd1
@CassioNeri,这是不正确的。虽然标准指定了您描述的行为,但GCC允许它并自动推断出正确的返回类型。看到: stackoverflow.com/questions/14450608/... 了解更多信息。 - OmnipotentEntity
@OmnipotentEntity:我看到了(感谢参考),即使没有 -std=c++1y GCC接受OP的lambda。然而,从逻辑上讲,这并不意味着gd1没有使用 -std=c++1y 因此,我的 猜测 错了。但是,他/她上面的评论确实暗示了这一点。 ;-) - Cassio Neri
+1试图修复我糟糕的答案;) - Casey