Apple introduced blocks (anonymous functions or lambdas) as C extensions for its
parallel programming model Grand Central Dispatch. Unlike ordinary C
functions, blocks can capture surrounding variable contexts. The captured
variables are casts to const by default, and for mutable variables you can mark
it with __block storage qualifier. However, there is a lot of pitfalls in
__block variables. Can you identify all of them?
The above is compile configuration and the program structure of the quiz.
You can download and test the code form Github.
Quiz 1
12345678910111213
voidquiz_1(void){__blockintx=1;printf("x address is %p\n",&x);BoringBlocklocalBlock=^{x++;// Dummy use of xprintf("End of quiz 1\n\n");};boringBlock=Block_copy(localBlock);printf("after copy, x address is %p\n",&x);}
What would be printed if we execute quiz_1() then boringBlock()? Would &x be
printed in same address or different addresses?
In block implementation spec, captured __block variable
x will be moved to heap after we execute Block_copy.
On my machine it prints:
x address is 0x7fff613d04f8
after copy, x address is 0x7fe9a1c13f78
End of quiz 1
Memory allocation on stack is much faster then heap, so variable and block
literal are both allocated on stack by default. It is copied to heap only when
necessary.
Quiz 2
123456789101112
voidquiz_2(void){__blockintx=1;BoringBlocklocalBlock=^{printf("x is %d\n",x);printf("End of quiz 2\n\n");};boringBlock=Block_copy(localBlock);x++;}
Now, if we change the variable x in quiz_2() scope, would captured variable
x also changes its value?
Actually it does! Though x is in different memory address,
we can just use it as normal int value, and it behaves just as
expected. On my machine it prints:
x is 2
End of quiz 2
Quiz 3
12345678910111213
voidquiz_3(void){__blockintx=1;__blockint*ptr=&x;BoringBlocklocalBlock=^{printf("x is %d, *ptr is %d\n",x,*ptr);printf("End of quiz 3\n\n");};boringBlock=Block_copy(localBlock);x++;}
Though ptr and x are both moved to the heap,
ptr still points to the original address of x.
Thus, the value in *ptr is garbage. If there are other
functions that use the stack before you use boringBlock(). It
would print:
clean up stack
x is 2, *ptr is 24
End of quiz 3
Oops
Quiz 4
12345678910
voidquiz_4(void){__blockintx[1]={1};void(^localBlock)(void)=^{printf("x[0] is %d\n",x[0]);printf("End of quiz 4\n\n");}x[0]=2;}
Actually, complier won’t let you compile this. C array and
struct contains C array are both invalid with
__block.
Quiz 5
123456789101112131415161718
BoringBlockquiz_5(void){__blockintx=1;printf("x address is %p\n",&x);BoringBlocklocalBlock=^{x++;printf("x is %d, &x is %p\n",x,&x);};boringBlock=Block_copy(localBlock);printf("x address is %p\n",&x);BoringBlockretBlock=Block_copy(localBlock);printf("x address is %p\n",&x);returnretBlock;}
Block execution:
123456
BoringBlockretBlock=quiz_5();boringBlock();retBlock();Block_release(boringBlock);Block_release(retBlock);printf("End of quiz 5\n\n");
What if we copied the block twice. Would the address change twice also?
x address is 0x7fff613d04f8
x address is 0x7fe9a1c13f78
x address is 0x7fe9a1c13f78
x is 2, &x is 0x7fe9a1c13f78
x is 3, &x is 0x7fe9a1c13f78
End of quiz 5
So, how does memory management work? Actually, compiler use reference
counting on __block variables instead of block literals. For
more curious, see my next post.