给出以下变量声明,
Foo* foo;
实际上是如何分配内存的?
这是我的猜测。实际上,这里分配了两个内存。
一个32位数字,代表一个存储在foo
[指针]中的存储器地址。
Foo
类型的内存的引用?
一个未初始化的连续内存段,其类型为Foo。它如何知道要分配多少内存?如何将内存段标记为Foo
类型?
答案 0 :(得分:2)
您提供的代码行不会分配任何内存。最多,它会充分移动堆栈指针以为一个字的值腾出空间(尽管在优化之后,它甚至可能没有这样做)。
一个32位数字,代表存储在foo [指针]上的内存地址。
“ 32位数字”是指“指针大小的整数”,在大多数现代处理器上为64位。如果未对其进行优化,则可能会将其别名为堆栈中的某个位置。
编译器如何准确地标记或表示指针实际上存储了对Foo类型的内存的引用?
它没有信号,也没有存储对任何类型的内存的引用。上面的代码行(最多)为指针留出了空间。在ObjC中,在运行时没有对象类型。指向对象的每个指针都被视为id
。 ObjC绝对不保证这代表“连续的内存段”(在许多情况下,您认为“数据”并不连续)。在更深层次上,处理器根本不关心“类型”。只有内存和指向内存的指针(void *
)。 (在更深的层次上,甚至没有“内存”。物理RAM,缓存,寄存器和许多其他东西通常都被抽象化了,甚至在C语言中也是如此。有关更多信息,请参见C is not a Low-Level Language )
一个未初始化并键入为Foo的连续内存段。它如何知道要分配多少内存?如何将内存段标记为Foo类型?
以上行根本没有分配。如果要为Foo
分配内存,则必须调用+[Foo alloc]
。由于它是Foo
上的类方法,因此它知道Foo
需要多少内存。没有任何东西表明存在任何类型的内存,而Objective-C实际上并不关心它是什么类型。它关心的只是在正确的位置有一个称为isa
的指针,它可用于查找如何通过objc_msgSend
分发消息。
在很多情况下,名为*Foo
的东西并不指向Foo
。通常所指的是免费电话桥接类型,它是完全不同类型的数据结构(CF结构),恰好在正确的位置具有isa
指针,因此它可以假装为ObjC对象。系统正常运行所需要做的就是使事物“足够接近”排列,以使objc_msgSend
可以正常工作。不需要(或机制)将内存标记为某种特定类型。
答案 1 :(得分:1)
Foo *
之类的指针的大小取决于目标平台。在当前的大多数Apple平台上,它都是64位,但是Series 4之前的Apple Watch是32位。
在几种情况下,您可以这样写:
Foo *foo;
您可以将其写为全局变量,在任何@interface
或@implementation
变量声明之外并且在任何函数之外。然后,每次启动程序时,它都会为一个指针分配空间并将该指针设置为nil。
您可以将其写在@implementation
变量声明中,如下所示:
@implementation MyObject {
Foo *foo;
}
在这种情况下,您已经声明了一个实例变量(或“ ivar”)。每次程序创建MyObject
实例时,该实例都包含一个指针的空间并将该指针设置为nil。
您可以将其写为函数或方法中的局部变量,如下所示:
- (void)doSomething {
Foo *foo;
}
在这种情况下,您已经声明了局部变量。每次调用该函数或方法时,它都会在其堆栈帧中分配一个指针,并且(假设您在启用了ARC的情况下进行编译,这是默认设置),它将指针初始化为nil。
请注意,在所有这些情况下,foo
不会不指向Foo
的实例。它指向零。要使foo
指向Foo
的实例,必须将其设置为引用从其他地方获得的Foo
。您可以通过调用函数或方法来获得该引用,如下所示:
- (void)doSomething {
Foo *foo;
// foo is nil here.
foo = [[Foo alloc] init];
// If [[Foo alloc] init] succeeded, then foo now points to an
// instance of Foo. If [[Foo alloc] init] returned nil, which
// indicates failure, then foo is still nil.
}
或者可以将Foo
引用作为函数参数传递给
- (void)doSomethingWithFoo:(Foo *)incomingFoo {
Foo *foo;
// foo is nil here.
foo = incomingFoo;
// foo now points to whatever incomingFoo points to, which should be
// either an instance of Foo, or nil.
}
或者您可以从其他全局变量,局部变量或实例变量中获取它。
关于“编译器如何准确地标记或表示指针实际上正在存储对Foo
类型的内存的引用”:否。在编译时,编译器知道foo
仅应指向Foo
(或nil),并试图阻止您将其分配给非指向{{1}的指针的对象。 }。例如,编译器将为此发出警告或错误:
Foo
因为编译器知道Foo *foo = @"hello";
不是NSString
。 (我假设您没有将Foo
设为Foo
或typedef
的子类。)
但是,您可以使用强制转换来覆盖编译器的类型问题:
NSString
或使用Foo *foo = (Foo *)@"hello";
类型:
id
这可以编译,并且可以正常运行,直到您尝试使用id something = @"hello";
Foo *foo = something;
做某事foo
不知道该怎么做。
因此,不是编译器知道“指针实际上正在存储对NSString
类型的内存的引用”。
Objective-C运行时知道指针实际上正在存储对Foo
的引用。要了解运行时如何跟踪对象的类型,首先需要了解Foo
类对象。
对于程序中的每个Objective-C类,在运行时都有一个称为“类对象”的特殊对象。因此,对于Foo
,有一个NSObject
类对象;对于NSObject
,有一个NSString
类对象;对于NSString
,有一个对象一个Foo
类对象。请注意,通常,类对象不是其自身的实例!也就是说,Foo
类对象本身不是NSString
的实例,而NSString
类对象本身不是Foo
的实例。
Foo
类对象知道由Foo
组成的实例:
Foo
的超类(也许是Foo
,也许还有其他东西)。NSObject
的每个实例变量的名称,类型和大小(从Foo
的超类继承的变量除外)。Foo
可以理解的每条消息的名称,类型签名和实现地址(从Foo
的超类继承的消息除外)。每个Objective-C对象的前一个字节包含一个指向类对象的指针。 1 该指针称为Foo
指针,它确定对象的类型。当您使用向消息(例如isa
)发送消息的语法时,编译器会生成对[foo length]
的调用。回想一下,在objc_msgSend
中,[foo length]
引用的对象称为 receiver 。 foo
函数使用接收者的objc_msgSend
指针查找接收者的类对象。它遍历类对象的消息表以查找isa
的实现并跳转到该实现。如果length
没有定义Foo
消息,则length
在objc_msgSend
的超类中查找消息,依此类推,在超类链中。 2
因此,此Foo
指针确定运行时对象的类型。
那么如何分配isa
对象?当您说Foo
时,这意味着“将[[Foo alloc] init]
消息发送到alloc
类对象,然后将Foo
消息发送给从init
返回的任何内容。 ”。因此alloc
类对象收到Foo
消息。但是alloc
类对象可能不会直接实现Foo
。它继承了alloc
的{{1}}实现。
因此NSObject
实际上为新的alloc
分配了内存。就像您说的那样,它分配了一个“连续的内存段”,但它不是“未初始化并键入为+[NSObject alloc]
”。它已已初始化,并键入为Foo
。 The +[NSObject alloc]
documentation说:
新实例的
Foo
实例变量被初始化为描述该类的数据结构;所有其他实例变量的内存都设置为0。
您可以查看实现here。这是Foo
函数。它使用标准的C库函数isa
分配内存,callAlloc
将内存填充为0。然后设置calloc
指针。
关于“它如何知道要分配多少内存”:请记住,每个类对象都知道其实例的所有实例变量。因此,要分配calloc
,isa
求和Foo
的所有实例变量的大小,再加上+[NSObject alloc]
的超类的所有实例变量的大小,递归到超类链的末尾。这告诉它要分配多少字节。除非每次分配一个对象都这样做太慢。因此,程序在启动时会为每个类对象预先计算实例大小,然后Foo
在Foo
类对象中查找+[NSObject alloc]
的预先计算大小。
除非该对象表示为tagged pointer,但不必担心。
如果您想知道Foo
到达超类链末尾而没有找到实现的情况,请阅读Objective-C Message Forwarding。