块和堆栈

时间:2015-06-10 00:57:52

标签: cocoa objective-c-blocks

根据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个块??

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

这看起来像堆栈分配。每个指针指向相同的内存区域,因为在从堆栈弹出一个块后,该内存区域将重新用于下一个块。

然后看起来当第一个块被调用时它恰好得到了正确的结果,但是当第二个块被调用时,系统覆盖了回收的内存导致了垃圾值?我还不清楚如何调用不存在的块会产生一个值??