I'm trying to understand how closures are actually sent for lambda expressions calls:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int *A = (int *) malloc((argc - 1) * sizeof(int));
const auto myLovelyLambda = [=]()
{
// ++++ captured
for (auto i=0;i<argc-1;i++) {
A[i] = atoi(argv[i+1]);
// + ++++ captured
// |
// +- captured
}
};
myLovelyLambda();
for (int i=0;i<argc-1;i++) {
printf("%d\n", A[i]);
}
return 0;
}
When I inspect the generated machine code, I see the captured entities are passed on stack:
$ clang --std=c++17 -g -O0 main.cpp -o main
$ objdump -S -D main > main.asm
$ sed -n "22,31p" main.asm
; const auto myLovelyLambda = [=]()
100003e6c: b85f83a8 ldur w8, [x29, #-8]
100003e70: 910043e0 add x0, sp, #16
100003e74: b90013e8 str w8, [sp, #16] // <--- captured
100003e78: f85e83a8 ldur x8, [x29, #-24]
100003e7c: f9000fe8 str x8, [sp, #24] // <--- captured
100003e80: f85f03a8 ldur x8, [x29, #-16]
100003e84: f90013e8 str x8, [sp, #32]. // <--- captured
; myLovelyLambda();
100003e88: 9400001c bl 0x100003ef8 <__ZZ4mainENK3$_0clEv>
Do I have any control of how the compiler manages this closure move?
You have to conceptually separate the initialization of the object of closure type, and the function call. A lambda expression has a corresponding closure type. In your case,
myLovelyLambdawould translate into something like this:Note that the order of
argc,argv, andAin the closure type is unspecified according to [expr.prim.lambda] p10:Initialization and calling is then transformed like this:
Not really. You can't have
volatilecaptures in a lambda, so the compiler is free to transform and reorder the initialization of the captures in the object significantly. It can also optimize away the lambda completely through inlining, so that the assembly is indistinguishable form having yourforloop directlymain.It is also unspecified in what order lambda captures are initialized within the closure object, and in what order they are destroyed. See C++11: In what order are lambda captures destructed?. In the end, you can just let the compiler figure it out. In your example, you don't need tight control over captures order.