Exploration and comparison of closures in C Language

Source: Internet
Author: User

The following is a copy from cool. Here, I am a little lazy, and I did not sort the format carefully again, but I only had to sort it a little. Khan.

Here we mainly discuss the extended feature block of C language. This feature is an extension added by Apple for C, C ++, and Objective-C, allowing these languages to create closures using the syntax of Lambda-like expressions. Some time ago, when encapsulating CoreData access (allowing developers to write relevant code more concisely and quickly), I had a better understanding of the block mechanism, I think it can be confirmed with the Lambda expression in c ++ 11, so I recently reviewed it and shared it with you.

0. Create an anonymous Function

The following two sections of code are used to create and call anonymous functions and output the Hello and World statements. Use Objective-C and C ++ 11 respectively:

[Cpp]
^ {Printf ("Hello, World! \ N ");}();
[Cpp] view plaincopy
[] {Cout <"Hello, World" <endl ;}();

One advantage of Lambda expressions is that developers can create functions temporarily as needed, which is convenient.

In terms of the syntax for creating a closure (or Lambda function), Objective-C uses the top-point number ^, while C ++ 11 uses pair square brackets [].

However, the term "anonymous function" is for programmers, and the compiler still adopts certain naming rules.

For example, in the following Objective-C code,

[Cpp]
# Import <Foundation/Foundation. h>

Int (^ maxBlk) (int, int) = ^ (intm, intn) {returnm> n? M: n ;};

Int main (intargc, constchar * argv [])
{
^ {Printf ("Hello, World! \ N ");}();

Int I = 1024;
Void (^ blk) (void) = ^ {printf ("% d \ n", I );};
Blk ();

Return 0;
}

Three functions are generated:

[Cpp]
_ MaxBlk_block_func_0
_ Main_block_func_0
_ Main_block_func_1

Visible function naming rules: __{ $ Scope} _ block_func _ {$ index }. {$ Scope} is the block function. If {$ Scope} is global, the block name is used; {$ index} indicates the order in which the block appears in the Scope of {$ Scope} (block number ).

1. syntactically, how to capture external variables

In the code above, we can see that "anonymous functions" can directly access the variable I in the peripheral scope:

[Cpp]
Int I = 1024;
Void (^ blk) (void) = ^ {printf ("% d \ n", I );};
Blk ();

When an anonymous function is combined with a non-local variable, a closure is formed (in my opinion ).
This code can successfully output the I value.

We move the same logic to C ++:

[Cpp]
Inti= 1024;
Auto func = [] {printf ("% d \ n", I );};
Func ();

GCC outputs: Error: 'I' is not captured. Obviously, variables in the peripheral scope cannot be directly captured in C ++.

Use BNF to represent the context-independent Syntax of a Lambda expression, which exists:

[Cpp]
Lambda-expression: lambda-introducer lambda-parameter-declarationopt compound-statement
Lambda-introducer: [lambda-captureopt]

Therefore, you can add some options in square brackets:
[Cpp]
[] Capture nothing (or, a scorched earth strategy ?)
[&] Capture any referenced variable by reference
[=] Capture any referenced variable by making a copy
[=, & Foo] Capture any referenced variable by making a copy, but capture variable foo by reference
[Bar] Capture bar by making a copy; don't copy anything else
[This] Capture the thispointer of the enclosing class

Modify the code according to the syntax so that it can run successfully:
[Cpp]
Bash-3.2 # vi testLambda. cpp
Bash-3.2 # g ++-4.7-std = c ++ 11 testLambda. cpp-o testLambda
Bash-3.2 #./testLambda
1024
Bash-3.2 # cat testLambda. cpp
# Include <iostream>

Using namespace std;

Int main ()
{
Int I = 1024;
Auto func = [=] {printf ("% d \ n", I );};
Func ();

Return 0;
}
Bash-3.2 #


2. How to modify external variables in terms of syntax

The above Code uses the symbol = to capture the external variable I by copying it.
However, if you try to modify the variable I in the Lambda expression:

[Cpp]
Auto func = [=] {I = 0; printf ("% d \ n", I );};

An error is returned:
[Cpp]
TestLambda. cpp: In the lambda function:
TestLambda. cpp: 9: 24: Error: assign a value to the read-only variable 'I

The external variables captured by copying are read-only. Python also has a similar classic case. I personally think there are some similarities:

[Cpp]
X = 10
Def foo ():
Print (x)
X + = 1
Foo ()

This code will throw an UnboundLocalError. For the cause, see the FAQ.

In the closure Syntax of C ++, if you need to write permissions on external variables, you can use the symbol & to capture them by referencing:

[Cpp]
Int I = 1024;
Auto func = [&] {I = 0; printf ("% d \ n", I );};
Func ();

In turn, put the logic for modifying external variables in the Objective-C code:

[Cpp]
Int I = 1024;
Void (^ blk) (void) = ^ {I = 0; printf ("% d \ n", I );};
Blk ();

The following error is returned:

[Cpp]
Main. m: 14: 29: error: variable is not assignable (missing _ block type specifier)
Void (^ blk) (void) = ^ {I ++; printf ("% d \ n", I );};
~ ^
1 error generated.

It can be seen that in the block syntax, the external variables captured by default are also read-only. To modify the external variables, you need to use the _ block type indicator for modification.
Why? Please proceed to the following :)

3. How to capture external variables from implementation perspective

Closure is a syntactic sugar for programming languages, including Block and Lambda, which is introduced to facilitate programmer development. Therefore, the support for the Block feature will be implemented on the compiler front-end, and the intermediate code will be C language.

First look at the intermediate code generated by the following code.

[Cpp]
Int main (intargc, constchar * argv [])
{
Int I = 1024;
Void (^ blk) (void) = ^ {printf ("% d \ n", I );};
Blk ();

Return 0;
}

First, implement the block struct:

[Cpp]
# Ifndef BLOCK_IMPL
# Define BLOCK_IMPL
Struct _ block_impl {
Void * isa;
Int Flags;
Int Reserved;
Void * FuncPtr;
};
// Omit Part of the Code

# Endif

The first isa pointer is used to indicate the type of the struct so that it is still in the Cocoa object system, similar to PyObject In the Python object system.

The second and third members are the flag and retention spaces.

The fourth member is the corresponding "anonymous function". In this example, the corresponding function is:

[Cpp]
Static void _ main_block_func_0 (struct _ main_block_impl_0 * _ cself ){
Inti = _ cself-> I; // bound by copy
Printf ("% d \ n", I );
}

Function _ main_block_func_0 introduces the parameter _ cself, which is of the type struct _ main_block_impl_0, from the parameter name, we can see that its function is similar to the this pointer in C ++ or the self of Objective-C.
The structure of struct _ main_block_impl_0 is as follows:

[Cpp]
Struct _ main_block_impl_0 {
Struct _ block_impl impl;
Struct _ main_block_desc_0 * Desc;
Int I;
_ Main_block_impl_0 (void * fp, struct _ main_block_desc_0 * desc, int_ I, intflags = 0): I (_ I ){
Impl. isa = & _ NSConcreteStackBlock;
Impl. Flags = flags;
Impl. FuncPtr = fp;
Desc = desc;
}
};
From the name of _ main_block_impl_0, we can see that this struct serves the first block in the main function, that is, blk in the sample code. You can also guess that the struct of blocks in different scenarios is different, but in essence, the first member must be struct _ block_impl impl, because this member is the cornerstone of block implementation.

Struct _ main_block_impl_0 introduces a new struct, which is also the last struct in the intermediate code:

[Cpp]
Static struct _ main_block_desc_0 {
Unsigned long reserved;
Unsigned long Block_size;
} _ Main_block_desc_0_DATA = {0, sizeof (struct _ main_block_impl_0 )};

It can be seen that the value information contained in the struct of this descriptive nature is the size of struct _ main_block_impl_0.

The intermediate code corresponding to the main function is as follows:

[Cpp]
Int main (intargc, constchar * argv [])
{
Int I = 1024;
Void (* blk) (void) = (void (*) (void) & __ main_block_impl_0 (void *) _ main_block_func_0, & __ main_block_desc_0_DATA, I );
(Void (*) (struct _ block_impl *) blk)-> FuncPtr) (struct _ block_impl *) blk );

Return 0;
}

From the intermediate code corresponding to the main function, we can see that the essence of the block execution is to use the block struct itself as the _ cself parameter. Here, the corresponding _ main_block_impl_0, use the FuncPtr function pointer of the struct member to call the corresponding function, which corresponds to _ main_block_func_0.

Here, the local variable I is copied as a value, as a parameter of the constructor _ main_block_impl_0, and assigned a value to its member variable I in the form of an initialization list. Therefore, it is reasonable not to allow direct modification of external variables based on this implementation-because the external variables cannot be directly modified by passing values.

4. How to modify external variables (_ block type indicator)

To modify external variables, use _ block to modify them:

[Cpp]
Int main (intargc, constchar * argv [])
{
_ Block int I = 1024;
Void (^ blk) (void) = ^ {I = 0; printf ("% d \ n", I );};
Blk ();

Return0;
}

Now let's look at the intermediate code and find an extra struct:
[Cpp]
Struct _ Block_byref_ I _0 {
Void * _ isa;
_ Block_byref_ I _0 * _ forwarding;
Int _ flags;
Int _ size;
Int I;
};

Therefore, the int variable I modified with _ block is transformed into the last member variable of the _ Block_byref_ I _0 struct.

The structure corresponding to the blk in the Code has also changed:

[Cpp]
Struct _ main_block_impl_0 {
Struct _ block_impl impl;
Struct _ main_block_desc_0 * Desc;
_ Block_byref_ I _0 * I; // by ref
_ Main_block_impl_0 (void * fp, struct _ main_block_desc_0 * desc, _ Block_byref_ I _0 * _ I, intflags = 0): I (_ I->__ forwarding ){
Impl. isa = & _ NSConcreteStackBlock;
Impl. Flags = flags;
Impl. FuncPtr = fp;
Desc = desc;
}
};

The change of _ main_block_impl_0 is that the member variable I of the int type is changed to the _ Block_byref_ I _0 * type. The name indicates that it is captured by reference.
The corresponding functions are also different:

[Cpp]
Static void _ main_block_func_0 (struct _ main_block_impl_0 * _ cself ){
_ Block_byref_ I _0 * I = _ cself-> I; // bound by ref
(I->__ forwarding-> I) = 0; // looks amazing
Printf ("% d \ n", (I->__ forwarding-> I ));
}

The main function has also changed:
[Cpp]
Int main (intargc, constchar * argv [])
{
_ Block _ Block_byref_ I _0 I = {(void *) 0, (_ Block_byref_ I _0 *) & I, 0, sizeof (_ Block_byref_ I _0), 1024 };
Void (* blk) (void) = (void (*) (void) & __ main_block_impl_0 (void *) _ main_block_func_0, & __ main_block_desc_0_DATA, (struct _ Block_byref_ I _0 *) & I, 570425344 );
(Void (*) (struct _ block_impl *) blk)-> FuncPtr) (struct _ block_impl *) blk );

Return 0;
}

The first two lines of code create two key struct, which are highlighted.
The changes in _ main_block_desc_0 are not shown here, which will be discussed later.

The essence of using the _ block type indicator is to introduce the _ Block_byref _ {$ var_name }_{ $ index} struct, the variable modified by the _ block keyword is placed in this struct. In addition, the block struct can indirectly access external variables by introducing _ Block_byref _ {$ var_name }_{ $ index} pointer type members.

Through this design, we can modify the variables of the external scope. Again, we should say the following:

There is no problem in computer science that can't be solved by adding another level of indirection.

Pointers are the most commonly used indirect means. In essence, pointers are also used for indirect access. Why do we need to specifically introduce the _ Block_byref _ {$ var_name }_{ $ index} struct, instead of directly using int * to access the external variable I?

In addition, what is the role of the _ forwarding pointer member in the _ Block_byref _ {$ var_name }_{ $ index} struct?

Please proceed to the following :)

5. Memory Management Actions

In Objective-C, the block feature is introduced to allow programmers to write concurrent code more concisely and elegantly (with GCD that looks like sensitive words ). It is common to pass the block as a function parameter for subsequent callback execution.

Let's take a look at a complete and executable code:

[Cpp]
# Import <Foundation/Foundation. h>
# Include <pthread. h>

Typedef void (^ DemoBlock) (void );

Void test ();
Void * testBlock (void * blk );

Int main (int argc, const char * argv [])
{
Printf ("Before test () \ n ");
Test ();
Printf ("After test () \ n ");

Sleep (5 );
Return 0;
}

Void test ()
{
_ Block int I = 1024;
Void (^ blk) (void) = ^ {I = 2048; printf ("% d \ n", I );};

Pthread_tthread;
Int ret = pthread_create (& thread, NULL, testBlock, (void *) blk );
Printf ("thread returns: % d \ n", ret );

Sleep (3); // if you sleep for 1 s, the program will crash.
}

Void * testBlock (void * blk)
{
Sleep (2 );

Printf ("testBlock: Begin to exec blk. \ n ");
DemoBlock demoBlk = (DemoBlock) blk;
DemoBlk ();

ReturnNULL;
}

In this example, the blk variable in the block type of the test () function is passed to testBlock as a function parameter.
Under normal circumstances, this code can be successfully run and the output is:

[Cpp]
Before test ()
Threadreturns: 0
TestBlock: Begin to exec blk.
2048
After test ()

If the last line of the test () function is changed to sleep for 1 s according to the annotation, the program will crash after the following result is output:

[Cpp]
Before test ()
Threadreturns: 0
After test ()
TestBlock: Begin to exec blk.

From the output, we can see that when you want to execute blk, test () has been executed and returned to the main function, and the corresponding function Stack has been expanded. At this time, the variables on the stack no longer exist, A crash occurs when access continues-this is also the reason that external variable I is not directly accessed using int.

5.1 copy the block struct
As mentioned above, the first member of block struct _ block_impl is the isa pointer, which makes it a subclass of NSObject. Therefore, we can copy it to the stack through the corresponding memory management mechanism:

[Cpp]
Void test ()
{
_ Block int I = 1024;
Void (^ blk) (void) = ^ {I = 2048; printf ("% d \ n", I );};

Pthread_tthread;
Intret = pthread_create (& thread, NULL, testBlock, (void *) [blk copy]);
Printf ("thread returns: % d \ n", ret );

Sleep (1 );
}

Void * testBlock (void * blk)
{
Sleep (2 );

Printf ("testBlock: Begin to exec blk. \ n ");
DemoBlock demoBlk = (DemoBlock) blk;
DemoBlk ();
[DemoBlk release];

Return NULL;
}

Run the command again to get the output:

[Cpp]
Before test ()
Threadreturns: 0
After test ()
TestBlock: Begin to exec blk.
2048

It can be seen that after the test () function stack is expanded, demoBlk can still be successfully executed, because the block struct _ main_block_impl_0 corresponding to blk is already on the stack. But this is not enough --

5.2 copy the captured variable (_ block Variable)
When copying the block struct, the captured _ block Variable, namely _ Block_byref_ I _0, is copied to the stack. This task falls on the _ main_block_desc_0 struct not discussed earlier:

[Cpp]
Static void _ main_block_copy_0 (struct _ main_block_impl_0 * dst, struct _ main_block_impl_0 * src) {_ Block_object_assign (void *) & dst-> I, (void *) src-> I, 8/* BLOCK_FIELD_IS_BYREF */);}

Static void _ main_block_dispose_0 (struct _ main_block_impl_0 * src) {_ Block_object_dispose (void *) src-> I, 8/* BLOCK_FIELD_IS_BYREF */);}

Static struct _ main_block_desc_0 {
Unsignedlongreserved;
UnsignedlongBlock_size;
Void (* copy) (struct _ main_block_impl_0 *, struct _ main_block_impl_0 *);
Void (* dispose) (struct _ main_block_impl_0 *);
} _ Main_block_desc_0_DATA = {0, sizeof (struct _ main_block_impl_0), _ main_block_copy_0, _ main_block_dispose_0 };

The _ main_block_impl_0 struct on the stack is src, And the _ main_block_impl_0 struct on the stack is dst. When a replication action occurs, the _ main_block_copy_0 function calls the src member variable I, that is, the _ Block_byref_ I _0 struct is also copied to the stack.
5.3 _ forwarding pointer
After the replication is completed, the _ main_block_impl_0 struct exists on the stack and heap. What if the block structure on the stack and stack all operate on the captured external variables?

The following is an example code:

[Cpp]
Void test ()
{
_ Block int I = 1024;
Void (^ blk) (void) = ^ {I ++; printf ("% d \ n", I );};

Pthread_tthread;
Intret = pthread_create (& thread, NULL, testBlock, (void *) [blk copy]);
Printf ("thread returns: % d \ n", ret );

Sleep (1 );
Blk ();
}

Void * testBlock (void * blk)
{
Sleep (2 );

Printf ("testBlock: Begin to exec blk. \ n ");
DemoBlock demoBlk = (DemoBlock) blk;
DemoBlk ();
[DemoBlk release];

ReturnNULL;
}

When calling pthread_create to create a thread in the test () function, blk is copied to the stack as a parameter of the testBlock function.
The blk struct in the test () function is located in the stack. It is executed after 1 s of sleep and auto-incrementing for I.
After the testBlock function is sleeping for 2 seconds, execute the block structure on the heap. Here, it is demoBlk.
Output after the above code is executed:

[Cpp]
Beforetest ()
Thread returns: 0
1025
Aftertest ()
TestBlock: Begin to execblk.
1026

It can be seen that, whether on the stack or on the stack block structure, the modification is the same _ block variable.

This is the role of the _ forwarding pointer member mentioned above:

At first, the member pointer _ forwarding of the _ block Variable on the stack points to the _ block Variable itself, that is, the _ Block_byref_ I _0 struct on the stack.

When the _ block variable is copied to the stack, the _ block Variable's _ forwarding member on the stack points to the copy on the stack to maintain consistency.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.