【问题标题】:Can a lambda safely return the address of a copied variable?lambda 可以安全地返回复制变量的地址吗?
【发布时间】:2013-09-27 18:40:51
【问题描述】:

给出以下示例代码:

int main()
{
    int i;
    auto f = [=]()mutable->int*
    {
            return &i;
    };

    return 0;
}
  1. g++ v.4.8.1 警告“返回局部变量‘i’的地址”。
  2. Clang v.3.2(MacOS 的 Clang)警告“堆栈内存地址 与返回的局部变量“i”相关联”。
  3. VS2012 和 VS2013 RC 均未发出任何警告。

我对 lambdas 的理解是编译器会生成一个仿函数类。该仿函数类将具有所有复制变量的成员(示例中为i)。我相信在我的代码上下文中,只要f 存在,返回其成员之一的地址是安全的。在我看来,所有编译器都弄错了。我认为在f 超出范围后使用f 的成员i 的地址的警告是有效的,但是关于“局部变量'i'”的警告是不正确/具有误导性的。我说的对吗?

【问题讨论】:

  • 你可以制作istatic
  • 仿函数类将引用“i”,但“i”本身仍然只是堆栈上的一个局部变量。一旦 main() 返回,i 就不再存在了。
  • @joeking 他在询问错误,例如,即使代码完全相同,您也不会收到相同的错误:struct S { int* operator()() { return &i; } int i; } f;
  • @joeking 你能详细说明一下吗?他正在按值([=])捕获i,那么为什么会有参考?此外,在main() 返回后,没有人争辩说这一切都超出了范围。
  • 我也认为这段代码有未定义的行为,因为你复制 i 而不初始化它:(

标签: c++ c++11 lambda


【解决方案1】:

是的,你是对的。 & 运算符应用于成员,而不是本地对象。

演示很简单:只需修改您的示例以输出地址。

#include <iostream>

int main() {
    int i;
    std::cout << & i << '\n';

    std::cout << [=]() mutable -> int * {
        return & i;
    } () << '\n';
}

http://ideone.com/OqsDyg

顺便说一句,在 GCC 4.9 中的 -Wall 下编译时没有警告。

【讨论】:

  • 我非常愿意相信你,因为你对 C++ 问题有很多很好的答案,但老实说,一些解释或来源会很好。我想我们都尝试过这个例子或类似的东西,但最大的问题是,我们怎么知道我们可以依赖这种行为?
  • @us2012 不知道我可以添加什么...... OP 关于它如何工作的信念是正确的。指定 lambdas 的标准语言相当复杂。我不知道它会有多大帮助。每次使用来自周围本地上下文的名称实际上是在命名一个仿函数成员,并且该成员是引用或副本。一些编译器显然错误地认为(d)它是一个引用,但我怀疑错误只出现在消息中,因为否则mutable 会被破坏。
【解决方案2】:

一些术语:

  • [&amp;](){ /*..*/ } 中的=&amp; 称为默认捕获
  • odr-use of a variable 粗略表示该变量不会出现在discarded-value-expression(如(void)some_variableint x = some_variable, 5;)中,也不会出现在常量表达式中。
  • compound-statement 是“功能块”{ statements }
  • 变量的名称是一个id-expression

[expr.prim.lambda]/3

lambda-expression 的类型(也是闭包对象的类型)是唯一的、未命名的非联合类类型——称为 闭包类型 — 其属性如下所述。

/11

如果 lambda-expression 具有关联的 capture-default 及其 compound-statement odr-uses (3.2) this 或具有自动存储持续时间的变量并且未显式捕获使用 odr 的实体,则称使用 odr 的实体隐式捕获

因此,i 被隐式捕获。

/14

如果实体被隐式捕获并且捕获默认值为=,或者如果它是使用不包含&amp; 的捕获显式捕获的,则通过副本捕获该实体。对于通过副本捕获的每个实体,在闭包类型中声明了一个未命名的非静态数据成员。

闭包类型中有一个非静态数据成员(int 类型)。

/17

每个 id-expression 是通过复制捕获的实体的 odr-use (3.2) 转换为对闭包类型的相应未命名数据成员的访问。

我们甚至不需要解释这一点,因为本段为我们提供了一个与 OP 非常相似的示例:

void f(const int*);
void g() {
    const int N = 10;
    [=] {
        int arr[N]; // OK: not an odr-use, refers to automatic variable
        f(&N);      // OK: causes N to be captured; &N points to the
                    // corresponding member of the closure type
    };
}

如果我们将此应用于 OP 的示例,我们会看到 &amp;i 指的是闭包类型的 internal 非静态数据成员。标准中未指定诊断消息是否合适;)

【讨论】:

  • [basic.scope.block]/1 表示“在块范围内声明的变量是局部变量。”因此,OP 的 lambda 确实返回局部变量的地址。
猜你喜欢
  • 1970-01-01
  • 2021-04-02
  • 2018-02-10
  • 2020-05-06
  • 1970-01-01
  • 1970-01-01
  • 2021-04-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多