Как компилятор выделяет память для объектов класса? - PullRequest
0 голосов
/ 12 ноября 2018

Учитывая следующее объявление переменной,

Foo* foo;

как на самом деле выделяется память?

Это мое предположение. На самом деле здесь выделяется две части памяти.

  1. 32-разрядное число, представляющее адрес памяти, сохраненный в foo [Указатель]. Как именно компилятор помечает или сигнализирует, что указатель фактически хранит ссылку на память типа Foo?

  2. Непрерывный сегмент памяти, который неинициализирован и напечатан как Foo. Откуда он знает, сколько памяти выделить? Как он помечает сегмент памяти как тип Foo?

Ответы [ 2 ]

0 голосов
/ 12 ноября 2018

Размер указателя типа Foo * зависит от целевой платформы.На большинстве современных платформ Apple это 64-битная версия, но Apple Watch до Series 4 32-битная.

Существует несколько контекстов, в которых вы можете написать это:

Foo *foo;
  1. Вы можете написать это как глобальную переменную вне любого объявления @interface или @implementation и вне какой-либо функции.Затем каждый раз, когда программа запускается, она выделяет место для одного указателя и устанавливает для этого указателя значение nil.

  2. Вы можете написать это в объявлении переменной @implementation, например:

    @implementation MyObject {
        Foo *foo;
    }
    

    В этом случае вы объявили переменную экземпляра (или «ivar»).Каждый раз, когда программа создает экземпляр MyObject, экземпляр включает в себя пространство для одного указателя и устанавливает указатель на ноль.

  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 (или ноль), и пытается помешать вам присвоить его вещам, которые не являются указателями на Foo.Например, компилятор выдаст предупреждение или ошибку для этого:

Foo *foo = @"hello";

, потому что компилятор знает, что NSString не является Foo.(Я предполагаю, что вы не сделали Foo typedef или подкласс NSString.)

Однако вы можете переопределить проблемы с типом компилятора, используя приведение:

Foo *foo = (Foo *)@"hello";

или с помощью типа id:

id something = @"hello";
Foo *foo = something;

Компилируется и работает нормально, пока вы не попытаетесь что-то сделать с foo, что NSString не знает, как это сделать.

Так что не компилятор знает, что «указатель действительно хранит ссылку на память типа Foo».

Среда выполнения Objective C знает, что указатель действительно хранитссылка на Foo.Чтобы понять, как среда выполнения отслеживает тип объекта, сначала необходимо узнать об объекте класса Foo.

Для каждого класса Objective-C в программе существует во время выполнения один специальный объектназывается «объект класса».Таким образом, для NSObject существует один объект класса NSObject, а для NSString - один объект класса NSString, а для Foo - один объект класса Foo.Обратите внимание, что в общем случае объект класса не является экземпляром самого себя!То есть объект класса NSString сам по себе не является экземпляром NSString, а объект класса Foo сам по себе не является экземпляром Foo.

Объект класса Foo знает, чтосоставляет экземпляр Foo:

  • Суперкласс Foo (может быть NSObject, может быть что-то еще).
  • Имя, тип и размер каждогопеременная экземпляра Foo (кроме тех, которые унаследованы от суперкласса Foo).
  • Имя, подпись типа и адрес реализации каждого сообщения, понятного для Foo (кроме тех, которые унаследованы отсуперкласс Foo).

Первыйбайты каждого объекта Objective-C содержат указатель на объект класса. 1 Этот указатель называется указателем isa и определяет тип объекта.Когда вы используете синтаксис, который отправляет сообщение объекту, например [foo length], компилятор генерирует вызов objc_msgSend.Напомним, что в [foo length] объект, на который ссылается foo, называется приёмник .Функция objc_msgSend использует указатель isa получателя, чтобы найти объект класса получателя.Он просматривает таблицу сообщений объекта класса, чтобы найти реализацию length и переходит к ней.Если Foo не определяет сообщение length, то objc_msgSend ищет сообщение в суперклассе Foo и т. Д. В цепочке суперклассов. 2

Итак, именно этот isa указатель определяет тип объекта во время выполнения.

Так как же выделяется Foo объект?Когда вы говорите [[Foo alloc] init], это означает, что «отправьте сообщение alloc объекту класса Foo, а затем отправьте сообщение init тому, что возвращается из alloc».Таким образом, объект класса Foo получает сообщение alloc.Но объект класса Foo, вероятно, не реализует alloc напрямую.Он наследует NSObject реализацию alloc.

Так что +[NSObject alloc] фактически выделяет память для нового Foo.Как вы говорите, он выделяет «непрерывный сегмент памяти», но он не «неинициализирован и напечатан как Foo».Это инициализированный и тип Foo. Документация +[NSObject alloc] гласит:

Переменная экземпляра isa нового экземпляра инициализируется в структуру данных, которая описывает класс;память для всех остальных переменных экземпляра установлена ​​в 0.

Вы можете посмотреть на реализацию здесь .Это функция callAlloc.Он использует стандартную библиотечную функцию C calloc для выделения памяти, а calloc заполняет память до 0. Затем он устанавливает указатель isa.

относительно того, «Как он знает, сколько памятивыделить »: помните, что каждый объект класса знает все переменные экземпляра своих экземпляров.Таким образом, чтобы выделить Foo, +[NSObject alloc] суммирует размеры всех переменных экземпляра Foo, плюс размеры всех переменных экземпляра суперкласса Foo, рекурсивно до конца суперклассацепь.Это говорит о том, сколько байтов выделить.За исключением того, что это будет слишком медленно, чтобы делать это каждый раз, когда он выделяет объект.Таким образом, программа предварительно вычисляет размер экземпляра для каждого объекта класса при запуске, и +[NSObject alloc] ищет предварительно вычисленный размер Foo в Foo объекте класса.


  1. Если объект не представлен в виде тегового указателя , но не беспокойтесь об этом.

  2. Если вы хотите знать, что происходит, когда objc_msgSendдостигнув конца цепочки суперкласса, не найдя реализации, прочитайте Пересылка сообщений Objective C .

0 голосов
/ 12 ноября 2018

Строка кода, которую вы дали, не выделяет никакой памяти.Самое большее, он перемещает указатель стека настолько, чтобы освободить место для значения из одного слова (хотя после оптимизации он может даже не делать этого).

32-разрядное число, представляющее адрес памяти, хранящийся вfoo [Указатель].

Под «32-битным числом» я предполагаю, что вы имеете в виду «целочисленное значение указателя», которое на большинстве современных процессоров составляет 64 бита.Это может, если он не оптимизирован, быть псевдонимом к некоторому месту в стеке.

Как именно компилятор отмечает или сигнализирует о том, что указатель фактически хранит ссылку на память типа Foo?

Это не сигнализирует об этом,и он не хранит ссылку на память какого-либо типа вообще.Вышеупомянутая строка кода просто (самое большее) освобождает место для указателя.В ObjC во время выполнения нет типов объектов.Каждый указатель на объект считается id.ObjC абсолютно не обещает, что это представляет собой «непрерывный сегмент памяти» (есть множество случаев, когда то, что вы считаете «данными», не является непрерывным).На более глубоком уровне процессор вообще не заботится о «типах».Есть только память и указатели (void *) на память.(На более глубоком уровне даже нет «памяти». Есть физическая RAM, кэши, регистры и многие другие вещи, которые обычно абстрагируются, даже в C. Для получения дополнительной информации см. Cне язык низкого уровня .)

Непрерывный сегмент памяти, который неинициализирован и напечатан как Foo.Откуда он знает, сколько памяти выделить?Как он помечает сегмент памяти как тип Foo?

Эта строка вообще не выделяется.Если вы хотите выделить память для Foo, вы должны позвонить +[Foo alloc].Поскольку это метод класса Foo, он знает, сколько памяти Foo требует.Ничто не помечает эту память о существовании любого типа, и Objective-C фактически не заботится о том, какой это тип.Все, что его волнует, - это указатель в нужном месте, называемый указателем isa, который он может использовать для поиска способов отправки сообщений через objc_msgSend.

. Существует множество случаев, когда что-то называется *Foo.не указывает на Foo.Очень часто фактический факт, на который указывают, это тип с бесплатными мостами, который представляет собой структуру данных для совершенно другого типа (структуры CF), в которой указатель isa находится в нужном месте, так что он можетпритворяться объектом ObjC.Все, что требуется для работы системы, - это чтобы элементы располагались «достаточно близко», чтобы objc_msgSend мог функционировать.Нет необходимости (или механизма) для маркировки памяти как определенного типа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...