未指定 lambda 函数的返回类型时的段错误
Posted
技术标签:
【中文标题】未指定 lambda 函数的返回类型时的段错误【英文标题】:Segfault when not specifying return type of lambda function 【发布时间】:2019-06-18 16:04:40 【问题描述】:我有这个示例代码:
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
#include <functional>
#include <iostream>
#include <string>
void f(std::function<const std::string&()> fn)
std::cout << "in f" << std::endl;
std::cout << "str: " << fn() << std::endl;
int main()
std::string str = "a";
auto fn1 = [&]() return str; ;
auto fn2 = [&]() const std::string& str2 = str; return str2; ;
auto fn3 = [&]() -> const std::string& return str; ;
std::cout << "in main" << std::endl;
std::cout << "fn1: " << fn1() << std::endl;
std::cout << "fn2: " << fn2() << std::endl;
std::cout << "fn3: " << fn3() << std::endl;
f(fn1); // Segfaults
f(fn2); // Also segfaults
f(fn3); // Actually works
return 0;
当我第一次写这篇文章时,我预计在f()
中调用fn1()
会正确返回对main
中str
的引用。鉴于str
分配到f()
返回之后,这对我来说看起来不错。但实际发生的是试图在f()
segfaults 中访问fn1()
的返回。
fn2()
也会发生同样的事情,但令人惊讶的是 fn3()
可以正常工作。
鉴于fn3()
有效而fn1()
无效,关于 C++ 如何推断 lambda 函数的返回值,我是否遗漏了什么?这将如何产生这个段错误?
作为记录,如果我运行此代码,以下是输出:
只打电话给f(fn3)
:
in main
fn1: a
fn2: a
fn3: a
in f
str: a
只打电话给f(fn2)
:
in main
fn1: a
fn2: a
fn3: a
in f
Segmentation fault (core dumped)
只打电话给f(fn1)
:
in main
fn1: a
fn2: a
fn3: a
in f
Segmentation fault (core dumped)
【问题讨论】:
前两个 lambda 按值返回,因此与std::function
不兼容,std::function
期望通过引用返回的可调用对象。我不知道为什么会编译,因为我希望在尝试使用不兼容的可调用类型构造 std::function
时它会失败。
这是你在valgrind中运行的程序。
关于此类错误的一个建议:检查函数或 lambdas 返回的任何地址,以确保f1()
的结果与str
相同,即assert(&str == &f1());
。跨度>
似乎 clang 不会中断:godbolt.org/z/PtJc3j
附带说明,std::function<const T &()>
接受 T()
函数并带有自动悬空引用的事实是一个错误的未记录功能。
【参考方案1】:
没有尾随返回类型的 lambda,如:
[&]()return str;;
相当于:
[&]()->autoreturn str;;
所以这个 lambda 返回一个 str 的副本。
调用std::function
对象将产生以下等效代码:
const string& std_function_call_operator()
// functor = [&]->autoreturn str;;
return functor();
当这个函数被调用时,str
被复制到一个临时文件中,引用被绑定到这个临时文件,然后这个临时文件被销毁。所以你得到了著名的悬空参考。这是一个非常经典的场景。
【讨论】:
呃,很容易做到。顽皮的 C++!【参考方案2】:lambda 的返回类型推导更改N3638。现在lambda
的返回类型使用auto
返回类型推导规则,去掉了引用性。
因此,[&]() return str;;
返回string
。因此,在 void f(std::function<const std::string&()> fn)
中调用 fn()
返回一个悬空引用。绑定对临时对象的引用会延长临时对象的生命周期,但在这种情况下,绑定发生在std::function
的机器深处,所以当f()
返回时,临时对象已经消失了。
lambda 推导规则
auto 和 lambda 返回类型使用稍微不同的规则 从表达式确定结果类型。自动使用规则 17.9.2.1 [temp.deduct.call],在所有情况下都明确放弃*** cv 限定,而 lambda 返回类型是基于 关于左值到右值的转换,它只删除 cv 限定 对于非类类型。结果:
struct A ; const A f(); auto a = f(); // decltype(a) is A auto b = [] return f(); ; // decltype(b()) is const A This seems like an unnecessary inconsistency.
约翰·斯派塞:
差异是故意的; auto 仅用于提供 const 如果您明确要求,请键入,而 lambda 返回类型应该 通常是表达式的类型。
丹尼尔·克鲁格勒:
另一个不一致:使用 auto,使用
braced-init-list
可以推断std::initializer_list;
的专业化如果 对于 lambda 返回类型也可以这样做。附加说明,2014 年 2 月:
EWG 注意到 g++ 和 clang 在处理此示例时有所不同 并将其提交给 CWG 解决。
让我们看看你的code:
fn1: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >
fn2: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >
fn3: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&
如您所见,只有最后一个实际上是const&
您可以使用以下代码检查 lambda 的返回类型:
//https://***.com/a/20170989/10933809
#include <functional>
#include <iostream>
#include <string>
void f(std::function<const std::string&()> fn)
std::cout << "in f" << std::endl;
std::cout << "str: " << fn() << std::endl;
#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
# include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>
template <class T>
std::string
type_name()
typedef typename std::remove_reference<T>::type TR;
std::unique_ptr<char, void(*)(void*)> own
(
#ifndef _MSC_VER
abi::__cxa_demangle(typeid(TR).name(), nullptr,
nullptr, nullptr),
#else
nullptr,
#endif
std::free
);
std::string r = own != nullptr ? own.get() : typeid(TR).name();
if (std::is_const<TR>::value)
r += " const";
if (std::is_volatile<TR>::value)
r += " volatile";
if (std::is_lvalue_reference<T>::value)
r += "&";
else if (std::is_rvalue_reference<T>::value)
r += "&&";
return r;
int main()
std::string str = "a";
auto fn1 = [&]() return str; ;
auto fn2 = [&]() const std::string& str2 = str; return str2; ;
auto fn3 = [&]() -> const std::string& return str; ;
std::cout << "in main" << std::endl;
std::cout << "fn1: " << fn1() << std::endl;
std::cout << "fn2: " << fn2() << std::endl;
std::cout << "fn3: " << fn3() << std::endl;
auto f1=fn1();
std::cout << "fn1: " << type_name<decltype(fn1())>() << std::endl;
std::cout << "fn2: " << type_name<decltype(fn2())>() << std::endl;
std::cout << "fn3: " << type_name<decltype(fn3())>() << std::endl;
f(fn1); // Segfaults
f(fn2); // Also segfaults
f(fn3); // Actually works
return 0;
【讨论】:
您的限制是错误的,lambda 可以有多个语句,甚至多个返回语句,而无需显式指定返回类型。 @Jarod42 这是 c++11 open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#975 的情况。我编辑答案 您可以“要求”编译器在错误消息中为您提供类型名称:demo @Oliv 我不知道,你有链接吗? 我明白为什么 fn1 会被推断为普通的 std::string。但是为什么 fn2 会发生这种情况呢?鉴于我返回一个 const ref,推导的返回值不应该是 const std::string& 吗?以上是关于未指定 lambda 函数的返回类型时的段错误的主要内容,如果未能解决你的问题,请参考以下文章