Cocoa 块和堆栈

Cocoa 块和堆栈,cocoa,objective-c-blocks,Cocoa,Objective C Blocks,根据: 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]()); 但bbum似乎在说,当for循环的每个循环完成时,块从堆栈中弹出;然后,在最后一次弹出之后,第三个块恰好位于无人认领的内存中。然后,当你调用块时

根据:

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]());

但bbum似乎在说,当for循环的每个循环完成时,块从堆栈中弹出;然后,在最后一次弹出之后,第三个块恰好位于无人认领的内存中。然后,当你调用块时,指针都指向第三个块???

是的,这是有道理的,但你真的需要考虑一下。当b[0]被赋予其值时,“^{return 0;}”将不再使用。b[0]只是它的地址。编译器一直在覆盖堆栈上的临时函数,因此“2”只是在该空间中编写的最后一个函数。如果在创建时打印这3个地址,我打赌它们都是一样的

另一方面,如果展开赋值循环,并向“^{return 0;}”添加其他引用,比如将其赋值给c[0],则可能会看到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并不是说pop发生在for循环完成之后——它发生在每次迭代到达结束括号(范围结束)之后

块对象[在堆栈上分配]仅在其生命周期内有效 封闭范围

在bbum的示例中,块的范围是循环的封闭大括号(bbum省略了该括号):

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


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

你完全误解了“在堆栈上”的含义

没有所谓的“变量堆栈”。“堆栈”是指“调用堆栈”,即调用帧的堆栈。每个调用帧存储该函数调用的局部变量的当前状态。示例中的所有代码都在单个函数中,因此这里只有一个相关的调用框架。调用帧的“堆栈”不相关

提到“stack”只意味着块是在调用帧内分配的,就像局部变量一样。“在堆栈上”意味着它的生存期类似于局部变量,即具有“自动存储持续时间”,并且它的生存期限定在声明它的范围内

这意味着在创建该块的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]);
在这里,与块的情况一样,您还存储了一个指向循环迭代内部范围内的对象的指针,并在循环结束后使用它。同样,因为幸运,该变量的空间恰好被分配给了循环后期迭代中的同一个变量,因此它似乎给出了一个有意义的结果,即使它只是未定义的行为


现在,如果您使用的是ARC,您可能看不到引用的文本所说的发生了什么,因为ARC要求在块指针类型的变量中存储某些内容时(并且
b[i]
具有块指针类型),会生成一个副本而不是retain,并存储副本。复制堆栈块时,它将被移动到堆中(即,它是动态分配的,具有动态生存期,并且与其他对象一样受内存管理),并返回指向堆块的指针。您可以在范围之后安全地使用它。

如果您在创建这3个地址时打印它们,我打赌它们都是相同的。
不。完全不同。如果它们都一样,我的下一个问题是:为什么堆栈会覆盖任何内容?这不是我对堆栈如何工作的理解。操作系统将内容推送到堆栈上,然后将其弹出——它不会覆盖堆栈上尚未弹出的内容。事实上,我的地址结果并不重要——因为bbum的代码输出0 1 2——所以在bbum写那篇文章到现在这段时间内发生了一些变化。不过,我想回答的问题是,“在堆栈上分配块”如何解释bbum得到的结果。据我所知,堆栈不会覆盖堆栈上已经存在的项,而是将新项推送到堆栈上。
我不认为bbum是说弹出发生在for循环完成之后——它发生在赋值之后。
为什么赋值会导致某些内容从堆栈中弹出?@7stud,不是字面上的赋值。但如前所述,后面紧跟着右大括号(范围结束)。对不起,我应该写得更好。我将进行编辑。该块正在通过ARC从堆栈中移出,因为您正在从方法返回它,以确保
for (int i=0; i<3; i++) {#<------
    b[i] = ^{ return i;};
}#<-----
typedef int(^Blocky)(void);  #******TYPEDEF HERE********

@interface AppDelegate : NSObject <NSApplicationDelegate> 

@end
#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
0x7fff5fbfe658
0x7fff5fbfe658
0x7fff5fbfe658
2
1606411952
1606411952
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