简体   繁体   English

"为什么不能通过堆栈指针访问通过引用捕获的 C++ 局部变量?"

[英]Why can't C++ locals captured by reference be accessed through the stack pointer?

I noticed that compilers implement capture by reference by making an array on the stack of pointers to the captured locals, which can be passed to the lambda to access them.我注意到编译器通过在指向捕获的局部变量的指针堆栈上创建一个数组来实现引用捕获,可以将其传递给 lambda 以访问它们。 That surprised me because the compiler knows where the locals are relative to the stack pointer, so I thought it could just pass the stack pointer.这让我感到惊讶,因为编译器知道本地变量相对于堆栈指针的位置,所以我认为它可以只传递堆栈指针。 That would make one less indirection in the lambda and save the work of putting the pointers on the stack.这将减少 lambda 中的间接性并节省将指针放入堆栈的工作。 I was wondering why the compiler can't do that?我想知道为什么编译器不能这样做?

For example, this C++:例如,这个 C++:

#include <functional>
extern void test(std::function<void()>& f);
int test2(int x, int y)
{
    std::function<void()> f([&]() { x += y; });
    test(f);
    return x;
}

generates this assembly on Clang 13 -O3 (comments mine):在 Clang 13 -O3 上生成此程序集(我的评论):

mov     dword ptr [rsp + 8], edi    // put x on the stack
mov     dword ptr [rsp + 12], esi   // put y on the stack
lea     rax, [rsp + 8]
mov     qword ptr [rsp + 16], rax   // put &x on the stack
lea     rax, [rsp + 12]
mov     qword ptr [rsp + 24], rax   // put &y on the stack
mov     qword ptr [rsp + 40], offset std::_Function_handler<void (), test2(int, int)::$_0>::_M_invoke(std::_Any_data const&)
mov     qword ptr [rsp + 32], offset std::_Function_handler<void (), test2(int, int)::$_0>::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation)
lea     rdi, [rsp + 16]
call    test(std::function<void ()>&)

and similar on GCC and MSVC.在 GCC 和 MSVC 上类似。

What you see is not the non optimized lambda, but all the stuff around std::function<\/code> .您看到的不是未优化的 lambda,而是std::function<\/code>周围的所有内容。

If you simplify your code to:如果您将代码简化为:

template < typename F>
void test(F& f )
{
    f();
}

int test2(int x, int y)
{
    auto f=[&]() { x += y; };
    test(f);
    return x;
}

int main()
{
    return test2(1,2);
}
  1. Here is godbolt demo<\/a> (extended a bit).这是Godbolt 演示<\/a>(扩展了一点)。<\/li>
  2. I've added testABI<\/code> to show that parameters to you function are passed by registry.我添加了testABI<\/code>以显示您的函数的参数是由注册表传递的。<\/li><\/ol>
     useTestABI(): # @useTestABI() mov edi, 3 mov esi, 5 jmp testABI(int, int) # TAILCALL<\/code><\/pre> 
          
    1. So in test2<\/code> starts by creating x<\/code> and y<\/code> to create locals on stack so it would be possible to pass them by reference.因此,在test2<\/code>中,首先创建x<\/code>和y<\/code>以在堆栈上创建局部变量,因此可以通过引用传递它们。<\/li>
    2. After that lambda is created on stack, so addresses of x<\/code> and y<\/code> are saved to stack.在堆栈上创建 lambda 之后,将x<\/code>和y<\/code>的地址保存到堆栈中。 Also addresses of functions to be invoked when lambda is used (polymorphism without vtable) are added to labda on stack使用 lambda 时要调用的函数的地址(没有 vtable 的多态性)也被添加到堆栈上的 labda<\/li><\/ol>

      Now you do not know what test<\/code> will do with f<\/code> .现在你不知道test<\/code>会对f<\/code>做什么。 It can clone f<\/code> so in such case f<\/code> have to be copied from stack to other place.它可以克隆f<\/code>所以在这种情况下f<\/code>必须从堆栈复制到其他地方。 Also on other side when test<\/code> is compiled compiler do not know what kind of lambda was passed.另一方面,编译test<\/code>时编译器不知道通过了哪种 lambda。 Creating copy may have side effect or not.创建副本可能有副作用,也可能没有。 So creating a copy must be possible and must be compatible with predefined ABI.因此,必须可以创建副本并且必须与预定义的 ABI 兼容。

      So this shortcut you are describing is possible only when compiler could known content of test<\/code> .因此,只有当编译器可以知道test<\/code>的内容时,您所描述的这种快捷方式才有可能。 When content of test<\/code> is not know f<\/code> must be fully created to make it possible to clone it when needed.当不知道test<\/code>内容时,必须完全创建f<\/code>才能在需要时克隆它。

      "

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM