根据bbum:
2)在堆栈上创建块。小心。
考虑:
typedef int(^Blocky)(void); Blocky b[3]; for (int i=0; i<3; i++) b[i] = ^{ return i;}; for (int i=0; i<3; i++) printf("b %d\n", b[i]());
您可能会合理地期望以上输出:
0
1
2但是,相反,你得到:
2
2
2由于块是在堆栈上分配的,因此代码是无意义的。它 只输出它所做的事情,因为在词法中创建了Block for()循环体的范围还没有被重用 编译器的其他内容。
我不明白这个解释。如果在堆栈上创建了块,那么在for循环完成后,堆栈看起来不会像这样:
stack:
---------
^{ return i;} #3rd block
^{ return i;} #2nd block
^{ return i;} #1st block
但是bbum似乎在说,当for循环的每个循环完成时,块会从堆栈中弹出;然后在最后一次弹出后,第3块恰好坐在无人认领的记忆中。然后以某种方式调用块时,指针都引用第3个块??
答案 0 :(得分:2)
你完全误解了“堆叠”的含义。
没有“堆栈变量”这样的东西。 “堆栈”指的是“调用堆栈”,即调用帧的堆栈。每个调用帧存储该函数调用的局部变量的当前状态。示例中的所有代码都在一个函数内,因此这里只有一个与之相关的调用框架。调用帧的“堆栈”无关紧要。
提到“堆栈”仅表示块在调用帧内分配,就像局部变量一样。 “在堆栈上”意味着它具有类似于局部变量的生命周期,即具有“自动存储持续时间”,并且其生命周期范围限定为声明它的范围。
这意味着块在创建它的for循环的迭代结束后无效。而你对块的指针现在指向一个无效的东西,取消引用指针是未定义的行为。由于块的生命周期已经结束,并且它所使用的空间未被使用,因此编译器可以在调用帧中随意使用该位置。
很幸运,编译器决定将后一个块放在同一个地方,这样当您尝试以块的形式访问该位置时,它会产生一个有意义的结果。但这实际上只是未定义的行为。如果需要,编译器可以在该空间的一部分中放置一个整数,在另一个部分中放置另一个变量,也可以在该空间的另一部分中放置一个块,这样当您尝试以块的形式访问该位置时,它将执行各种各样的坏事,也许会崩溃。
块的生命周期与在同一范围内声明的局部变量完全类似。您可以在一个更简单的示例中看到相同的结果,该示例使用一个局部变量来重现正在发生的事情:
int *b[3];
for (int i=0; i<3; i++) {
int j = i;
b[i] = &j;
}
for (int i=0; i<3; i++)
printf("b %d\n", *b[i]);
打印(可能):
b 2
b 2
b 2
这里,与块的情况一样,您还存储指向循环迭代内部范围的东西的指针,并在循环之后使用它。而且,再次,因为你很幸运,该变量的空间恰好从循环的后续迭代中分配给同一个变量,所以它似乎给出了一个有意义的结果,即使它只是未定义的行为。
现在,如果您正在使用ARC,您可能看不到引用文本的内容,因为ARC要求在块指针类型的变量中存储某些内容(并且b[i]
具有块指针类型),制作副本而不是保留,而是存储副本。复制堆栈块时,它会被移动到堆中(即它是动态分配的,具有动态生存期并且像其他对象一样进行内存管理),并返回指向堆块的指针。这可以在范围之后安全使用。
答案 1 :(得分:1)
是的,这确实有道理,但你真的要考虑它。当b [0]给出其值时,&#34; ^ {return 0;}&#34;从未再次使用过。 b [0]只是它的地址。编译器继续覆盖堆栈上的那些临时函数,因此&#34; 2&#34;只是在那个空间写的最后一个函数。如果您在创建这三个地址时打印出来,我敢打赌它们都是一样的。
另一方面,如果你展开你的赋值循环,并添加其他引用到&#34; ^ {return 0;}&#34;,就像将它分配给ac [0]一样,你就是&#l; ll可能见b [0]!= b [1]!= b [2]:
b[0] = ^{ return 0;};
b[1] = ^{ return 1;};
b[2] = ^{ return 2;};
c[0] = ^{ return 0;};
c[1] = ^{ return 1;};
c[2] = ^{ return 2;};
优化设置可能会影响结果。 顺便说一句,我不认为bbum说在流程完成之后流行音乐发生了 - 它在每次迭代后都会发生在关闭大括号(范围结束)之后发生。
答案 2 :(得分:0)
Mike Ash提供了answer:
块对象[在堆栈上分配]仅在其生命周期内有效 封闭范围
在bbum的例子中,块的范围是for-loop's
括号(省略了bbum):
for (int i=0; i<3; i++) {#<------
b[i] = ^{ return i;};
}#<-----
因此,每次循环时,新创建的块都会被压入堆栈;然后当每个循环结束时,该块将从堆栈中弹出。
如果您在创建这三个地址时打印出来,我敢打赌它们都是 同样的。
是的,我认为这是它过去必须运作的方式。但是,现在看来循环不会导致块从堆栈中弹出。现在,它必须是确定块的封闭范围的方法大括号。 编辑:不。我构建了一个实验,我仍然为每个块获得不同的地址:
AppDelegate.h:
typedef int(^Blocky)(void); #******TYPEDEF HERE********
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end
AppDelegate.m:
#import "AppDelegate.h"
@interface AppDelegate ()
-(Blocky)blockTest:(int)i {
Blocky myBlock = ^{return i;}; #If the block is allocated on the stack, it should be popped off the stack at the end of this method.
NSLog(@"%p", myBlock);
return myBlock;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
Blocky b[3];
for (int i=0; i < 3; ++i) {
b[i] = [self blockTest:i];
}
for (int j=0; j < 3; ++j) {
NSLog(@"%d", b[j]() );
}
}
@end
--output:--
0x608000051820
0x608000051850
0x6080000517c0
0
1
2
在我看来,就像在堆上分配块一样。
好的,我上面的结果归因于ARC
。如果我关闭ARC
,那么我会得到不同的结果:
0x7fff5fbfe658
0x7fff5fbfe658
0x7fff5fbfe658
2
1606411952
1606411952
这看起来像堆栈分配。每个指针指向相同的内存区域,因为在从堆栈弹出一个块后,该内存区域将重新用于下一个块。
然后看起来当第一个块被调用时它恰好得到了正确的结果,但是当第二个块被调用时,系统覆盖了回收的内存导致了垃圾值?我还不清楚如何调用不存在的块会产生一个值??