C++:嵌套的 lambda 函数引用传递参数的相同地址

C++: nested lambda function refer same address of passed parameter

提问人:Vlad Zhukovsky 提问时间:3/26/2023 最后编辑:Benjamin BuchVlad Zhukovsky 更新时间:3/27/2023 访问量:96

问:

我研究了 lambda,并在嵌套 lambda 函数时注意到了一些奇怪的事情。

int x{10}; // assume x has 0x1 address here
cout << "SCOPE: x address:  " << &x << endl; // showing 0x1 for x address
auto show =
    [](int x) {
        cout << "SHOW: x address: " << &x << endl;
    };
cout << "SCOPE: show-lambda address: " << &show << endl;
show(x); // showing 0x2 for x address
auto fv =
    [x, show]() {
        cout << "FV  : x address: " << &x << " " << endl; // 0x3
        cout << "FV  : show-lambda address:" << &show << endl;
        show(x); // showing 0x4 for x address
    };
auto fr =
    [&x, show]() {
        cout << "FR  : x address: " << &x << " " << endl; // showing 0x1 for x address
        cout << "FR  : show-lambda address:" << &show << endl;
        show(x); // showing 0x4 for x address - WHY???
    };
fv();
fr();
SCOPE: x address:  0x39f95c
SCOPE: show-lambda address: 0x39f95b
SHOW: x address: 0x39f928
FV  : x address: 0x39f950
FV  : show-lambda address:0x39f954
SHOW: x address: 0x39f8f8
FR  : x address: 0x39f95c
FR  : show-lambda address:0x39f948
SHOW: x address: 0x39f8f8

x得到地址。a1 = 0x39f95c

x按值(其值被复制)传递到 lambda 中,因此它显示新地址。SHOWa2 = 0x39f95b

我们称之为 lambda capture by value(其值被复制)。所以里面有新的地址.FVxFVa3 = 0x39f950

从我们称之为 lambda。 (from 的范围)是按值传递的,所以在里面我们得到新的地址FVSHOWxFVSHOWX - a4 = 0x39f8f8

我们称之为 lambda 捕获的引用(此处没有值副本)。所以里面有地址 - 很公平。FRxFRxa1 = 0x39f95c

从我们称之为 lambda。 (参考?(从 的范围)是按值传递的,所以在里面我们得到新的......等什么?这里的地址是。FRSHOWxxFVSHOWx0x39f8f8 = a4

我不明白。据我了解,lambda 被编译为某种类,因此它们是内存中的对象。你可以看到我还在里面记录了 lambda 地址和 .他们是不同的。这意味着这些 lambda 是不同的对象,因此它们内部的实例应该不同。但这不能让我得出任何结论。SHOWFVFRSHOWx

谁能解释一下我们是如何在里面获得相同的地址的?FRSHOW

C++ lambda 嵌套 传递值

评论

0赞 user12002570 3/26/2023
每次创建 lambda 时,它都会被编译为一个完全唯一(不同)的编译器特定类类型,其中包含一些已经重载的运算符。
0赞 Sam Varshavchik 3/26/2023
你知道什么是堆栈,它是如何工作的,以及它在这里有什么关系吗?
0赞 Vlad Zhukovsky 3/26/2023
@Jason 似乎我提到了这个问题。不包括“一些已经超载的操作员”。
0赞 Vlad Zhukovsky 3/26/2023
@SamVarshavchik我知道在调用函数时,该函数的内存在堆栈上分配,堆栈是进程内存的一部分。据我了解,当我调用我的 lambda 时,内存是在堆栈上分配的。仍然不知道如何回答我的问题。FV 和 FR 中的 SHOW lambda 是不同的实例。他们如何为 X 持有相同的地址?
0赞 463035818_is_not_an_ai 3/26/2023
您究竟不理解或期望或希望对输出有不同的了解?

答:

2赞 463035818_is_not_an_ai 3/26/2023 #1

您期望在最外层范围内只是副本的不同对象是同一对象。x

以下是对副本使用不同名称的代码:

#include <iostream>

using std::cout;
using std::endl;

int main() {
  
    int x {10};
    cout << "SCOPE: x address:  " << &x << endl; // this is x 
    auto show = [](int y) 
    {
        cout << "SHOW: y address: " << &y << endl; 
    };
    cout << "SCOPE: show-lambda address: " << &show << endl;
    show(x); // copy x
    auto fv = [ z = x, show]()  // copy x
    {
        cout << "FV  : z address: " << &z << " " << endl; 
        show(z); // copy x again 
    };
    auto fr = [&x, show]()
    { 
        cout << "FR  : x address: " << &x << " " << endl; //  this is x, because captured as reference
        show(x); // copy x 
    };
    fv();
    fr();
}

在输出中,虽然仍然有 3 个不同,因为被调用了 3 次(两次只是碰巧在同一个地址结束,但这只是偶然的:yshow

SCOPE: x address:  0x7ffe1f6d067c
SCOPE: show-lambda address: 0x7ffe1f6d067b
SHOW: y address: 0x7ffe1f6d0644
FV  : z address: 0x7ffe1f6d0670 
SHOW: y address: 0x7ffe1f6d0624
FR  : x address: 0x7ffe1f6d067c 
SHOW: y address: 0x7ffe1f6d0624

正如评论中提到的,打印相同的地址不仅仅是“偶然”。当函数返回时,其堆栈将被释放,并可由下一个调用的函数使用。你可以在这个例子中看到同样的效果:

#include <iostream>

void foo() {
    int x = 42;
    std::cout << &x << "\n";
}

int main() {
    foo();
    foo();
}

可能的输出

0x7ffddcc6fd9c
0x7ffddcc6fd9c

评论

1赞 Vlad Zhukovsky 3/26/2023
>>>仍然有 3 个不同的 y,因为 show 被调用了 3 次 - 我认为完全相同,因为我明白为什么 - y 是我们传递的参数的副本。 >>>两次只是碰巧在同一个地址结束,但这只是偶然的 - 可能这就是我想听到的, 谢谢。
1赞 Red.Wave 3/26/2023
不完全是偶然的。我敢打赌,如果是下一个语句,地址将与前一个语句相同。当对象超出范围时,它的内存可用于新对象。只要调用新函数生成足够相似的机器代码,堆栈帧就会保持相似。但同样,这仍然是UB;没有保证。show(x);show::x
0赞 Vlad Zhukovsky 3/27/2023
@Red.Wave 如果下一个语句是哪一个?归根结底,我仍然很困惑。lambda 内部的不同地址(登录我的代码证明了这一点),这意味着为这些 lambda 分配了不同的堆栈帧。不同的堆栈帧如何包含相同的地址让我感到困惑。show(x)showfv()fr()x
0赞 Vlad Zhukovsky 3/27/2023
@Red.Wave 如果 lambda 地址相同,我会接受。我可以把它想象成呼叫的内存被释放并分配给了 .但我看不出是不是这样。showshowfv()showfr()
1赞 Red.Wave 3/27/2023
@VladZhukovsky,我不确定,我理解正确。但是,将 lambda 视为具有函数调用运算符的结构,可以立即定义和实例化。您可以定义具有相同功能的聚合类;但那会更长,更不方便。构建和调用的点是不同的,也可能是遥远的。因此,位置捕获列表与函数调用运算符的堆栈帧完全无关。如果捕获列表不包含引用,则通常可以安全地将 lambda 作为结果返回,或者通过将其放入容器中将其放在堆中。