Compiler implementation of lambda expressions in C++

  • 2020-05-17 05:57:43
  • OfStack

What is Lambda?

C++ 11 adds a very important feature -- the Lambda expression. All the brothers in camp David are familiar with Objective-C, and many of them have a special liking for block, and use it to implement various callback functions and agents. Some people even choose to use FBKVOController, BlocksKit and other open source frameworks to change the handling of KVO and control events to block. The reason is simple, convenient, and intuitive. Functions are defined and used in the same place. The Lambda expression here is actually very similar to block, but of course if you compare it to the closure in Swift, that's one thing.

Now, Android has fully shifted to C++11/14 standard. If you look at the code, many places have changed a lot. The new standard is really a little subversive. I saw the lambda expression today, and I suddenly want to see how this product is realized. I wrote an example as follows, and called three lambda expressions respectively:


#include <stdlib.h> 
#include <stdio.h> 
//1.  There is no parameter  
auto hello = [] () {printf( "Hello world!\n");}; 
// 2. 1 A parameter  
auto hello_int = [] (int val){ printf("the value is %d\n", val); }; 
int main(int argc, char **argv) { 
 hello(); 
 hello_int(argc); 
 // 3.  With a capture list lambda expression  
 auto lambda = [argc, argv]() {printf("param: %d, path is:%s\n", argc, argv[0]);}; 
 lambda(); 
 return 0; 
}

It's easy to define the three lambda expressions, and lambda expressions, without going into detail about what they are, are basically books that introduce new standards. So I want to take a look at 1, how does the compiler actually implement expressions? The first impression is that the expression is implemented as an inline function, and the place of the call is automatically expanded, so that the parameters, the capture list and so on are well implemented.

A simple look at 1, no optimization at compile time, disassembly look at 1, as follows, the main function called:

The three lambda expressions in the code are called in order above. From this disassembly, it looks like it's not the same as it might have been if the function was called and not expanded inline.
The call functions in the red box [blue is the actual symbol, gray is after demangle, see this for analysis] are:


$_0::operator()(void)  
$_1::operator()(int) 
main::$_2::operator() const(void) 

These are the three overloaded () operators.. See the this parameter pressed before the call, this is the method call of the object, from the disassembly point of view, is to create the object on the stack, and then directly use, use is through operator()..

Isn't this a function object??

OK, then understand, lambda expression, compiler automatically converted to function object execution...

In the above example, the compiler converts the following:


#include <stdlib.h> 
#include <stdio.h> 
class $_0 { 
public: 
 void operator() { 
 printf( "Hello world!\n"); 
 } 
}; 
class $_1 { 
public: 
 void operator(int va) { 
  printf("the value is %d\n", val); 
 } 
}; 
class main::$_2 { 
public: 
 main::$_2(int i, char **v): argc(i), argv(v) {} 
 // With a capture list, you cannot modify the capture list ... 
 void operator() const { 
 printf("param: %d, path is:%s\n", argc, argv[0]); 
 } 
private: 
 int argc; 
 char **argv; 
} 
int main(int argc, char **argv) { 
 $_0 hello; 
 hello(); 
 $_1 hello_int; 
 hello_int(argc); 
 main::$_2 lambda(argc, argv); 
 lambda(); 
 return 0; 
}

Here, the converted lambda symbol, which is automatically generated by the compiler, looks a little awkward.


Related articles: