简体   繁体   中英

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. 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. I was wondering why the compiler can't do that?

For example, this 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):

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.

What you see is not the non optimized lambda, but all the stuff around std::function<\/code> .

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).<\/li>
  2. <\/li><\/ol>
    <\/li>
  3. Also addresses of functions to be invoked when lambda is used (polymorphism without vtable) are added to labda on stack<\/li><\/ol>

    Now you do not know what test<\/code> will do with f<\/code> . It can clone f<\/code> so in such case f<\/code> have to be copied from stack to other place. Also on other side when test<\/code> is compiled compiler do not know what kind of lambda was passed. Creating copy may have side effect or not. So creating a copy must be possible and must be compatible with predefined ABI.

    When content of test<\/code> is not know f<\/code> must be fully created to make it possible to clone it when needed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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