编译器如何为类对象分配内存?

时间:2018-11-12 18:58:47

标签: objective-c oop memory-management

给出以下变量声明,

Foo* foo;

实际上是如何分配内存的?

这是我的猜测。实际上,这里分配了两个内存。

  1. 一个32位数字,代表一个存储在foo [指针]中的存储器地址。

  2. 编译器如何精确地标记或表示指针实际上正在存储对Foo类型的内存的引用?
  3. 一个未初始化的连续内存段,其类型为Foo。它如何知道要分配多少内存?如何将内存段标记为Foo类型?

2 个答案:

答案 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;
  1. 您可以将其写为全局变量,在任何@interface@implementation变量声明之外并且在任何函数之外。然后,每次启动程序时,它都会为一个指针分配空间并将该指针设置为nil。

  2. 您可以将其写在@implementation变量声明中,如下所示:

    @implementation MyObject {
        Foo *foo;
    }
    

    在这种情况下,您已经声明了一个实例变量(或“ ivar”)。每次程序创建MyObject实例时,该实例都包含一个指针的空间并将该指针设置为nil。

  3. 您可以将其写为函数或方法中的局部变量,如下所示:

    - (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设为Footypedef的子类。)

但是,您可以使用强制转换来覆盖编译器的类型问题:

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消息,则lengthobjc_msgSend的超类中查找消息,依此类推,在超类链中。 2

因此,此Foo指针确定运行时对象的类型。

那么如何分配isa对象?当您说Foo时,这意味着“将[[Foo alloc] init]消息发送到alloc类对象,然后将Foo消息发送给从init返回的任何内容。 ”。因此alloc类对象收到Foo消息。但是alloc类对象可能不会直接实现Foo。它继承了alloc的{​​{1}}实现。

因此NSObject实际上为新的alloc分配了内存。就像您说的那样,它分配了一个“连续的内存段”,但它不是“未初始化并键入为+[NSObject alloc]”。它已已初始化,并键入为FooThe +[NSObject alloc] documentation说:

  

新实例的Foo实例变量被初始化为描述该类的数据结构;所有其他实例变量的内存都设置为0。

您可以查看实现here。这是Foo函数。它使用标准的C库函数isa分配内存,callAlloc将内存填充为0。然后设置calloc指针。

关于“它如何知道要分配多少内存”:请记住,每个类对象都知道其实例的所有实例变量。因此,要分配callocisa求和Foo的所有实例变量的大小,再加上+[NSObject alloc]的超类的所有实例变量的大小,递归到超类链的末尾。这告诉它要分配多少字节。除非每次分配一个对象都这样做太慢。因此,程序在启动时会为每个类对象预先计算实例大小,然后FooFoo类对象中查找+[NSObject alloc]的预先计算大小。


  1. 除非该对象表示为tagged pointer,但不必担心。

  2. 如果您想知道Foo到达超类链末尾而没有找到实现的情况,请阅读Objective-C Message Forwarding