为什么在复制块之前,__block变量会被移动到堆上?

9
我知道如果复制了访问它的块,__block变量将从栈移动到堆。但下面的测试代码告诉我,在复制块之前__block变量已经被移动到堆中。
也就是说,四个输出结果为:堆 => 堆 => 堆 => 堆,这不是我期望的结果:栈 => 栈 => 栈 => 堆。
能有人帮我澄清一下吗?
__block int x = 0;
int *pointerToX = &x;
//1. It's on the stack
NSLog(@"x's location is on the stack: %p", &x);
int (^block)() = ^{
    x += 1;
    return x;
};

//2. I think its stack, but it's heap
NSLog(@"x's location is on the %@: %p", (&x == pointerToX ? @"stack" : @"heap"), &x); //it's heap not stack

block();
//3. I think its stack, but it's heap
NSLog(@"x's location is on the %@: %p", (&x == pointerToX ? @"stack" : @"heap"), &x); //it's heap not stack

block = [block copy]; // The variable x will be moved to the heap
//4. I think its stack, but it's heap
NSLog(@"x's location is on the %@: %p", (&x == pointerToX ? @"stack" : @"heap"), &x); //heap

你为什么关心这个问题呢?如果将变量从栈移动到堆上,__block int x; int *ptr = &x; 会发生什么? - Bryan Chen
2
这是一个实现细节。你应该尽量不要过于深入地思考它们。 - Jonathan Grynspan
2
记录块的类(是的,块也是对象)也有助于确定它被复制到哪里(__NSStackBlock____NSMallocBlock__)。 - CodaFi
1
这是一个有趣的问题,我认为它不应该被关闭。 - nonamelive
3个回答

3

首先申明一下:块是很奇怪的。

当你开始时,你声明了一个变量x,并在前面加上了__block。那么__block到底是什么呢?对于在块的词法范围内捕获的对象,变量会被-retain以确保它们在块执行时存在。但是对于原始类型的变量,块通过强制按const值传递而不是按引用传递来保护它们的值。通过添加__block,你已经让编译器自由地将你的变量“神奇地”从堆栈移动到堆中,当块被复制时。明确地说,__block变量实际上是在堆栈上分配的,但是它们在块被复制时被移动到堆(通过malloc())。

那么x位置的奇怪变化是怎么回事呢?再次回到__block。因为你没有像普通变量一样使用const引用x,所以块使用了一个(稍微有点烦人的)技巧:块创建了一个指向任何__block变量的指针,如果该变量被改变,它就会被解引用。大功告成!你的变量没有从堆栈移动到堆中,块只是解引用了指向它的指针并在内存中移动了它!

所以,实际上你对变量在何时何地移动感到困惑。你的示例记录了正确的值。


但对于原始变量,块通过强制按const值而不是按引用传递来保护它们的值。这对所有类型都是正确的。无论类型如何,非__block变量在创建块时都会被按值捕获。 - newacct

2
您期望的输出是基于您假设块不会在步骤3-4之前被复制。但是,Blocks规范中没有任何保证这种情况的内容。
是的,最迟当您显式调用-copy时,该块将被复制。但为什么不能更早地复制呢?更早地复制块从来都不会是错误的。因此,块被复制的确切时间是未定义的,您不应该依赖它。
某些最近的ARC编译器版本可能会保守地在创建块后立即复制它。那样做也没有问题。同样,如果它这样做了,那将是一项实现细节,其他编译器或将来的版本可能会采取不同的做法。

0

引用其他帖子时,请确保包含正确的引用。 - Nikolai Ruhe
是的,这是由ARC引起的。 - jcccn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接