未指定 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() 会正确返回对mainstr 的引用。鉴于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(&amp;str == &amp;f1());。跨度> 似乎 clang 不会中断:godbolt.org/z/PtJc3j 附带说明,std::function&lt;const T &amp;()&gt; 接受 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返回类型推导规则,去掉了引用性。 因此,[&amp;]() return str;; 返回string。因此,在 void f(std::function&lt;const std::string&amp;()&gt; 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&amp;

您可以使用以下代码检查 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 函数的返回类型时的段错误的主要内容,如果未能解决你的问题,请参考以下文章

将结构从 main() 传递给工作函数时的段错误

GCC 生成的程序集 - C 函数调用时的段错误

在struct中访问函数时的段错误

并行测试与地理相交时的段错误

推送到成员向量时的段错误

64位平台上,函数返回指针时遇到的段错误问题