Указатель NSString: теоретические вопросы после изучения - PullRequest
2 голосов
/ 11 сентября 2011

У меня есть вопросы по поводу указателя NSString.Я хотел докопаться до сути и на самом деле попытался создать теорию, чтобы получить полное понимание, основанное на множественной информации, полученной из той сети.Пожалуйста, поверьте мне, когда я говорю, что я не ленивый, и я действительно много читаю, но я все еще оставался с неуверенностью и вопросами.Можете ли вы подтвердить / опровергнуть, когда хорошо / неправильно, и я поставил дополнительные вопросы и сомнения, которые обозначены (?).

Вот и мы: Если я рассмотрим этот очень простой пример:

NSString *sPointer = [[NSString alloc]initWithString:@"This is a pointer"]; 
[sPointer release];

Моя отправная точка была такой: Компилятор резервирует память ОЗУ для типа указателя и эту память (которая также имеет собственнуюадрес) содержит адрес памяти (шестнадцатеричный - двоичный) памяти, где хранится другая переменная (где она указывает).Фактический указатель занимал бы около 2 байтов:

1) Сначала возникнет некоторая общая проблема - необязательно связанная с целью C. Фактические вопросы о указателе NSString будут приведены в пункте 2. Строка - это «строкасимволы ", где 1 символ занимает фиксированный объем памяти, скажем, 2 байта.Это автоматически означает, что объем памяти, занимаемый строковой переменной, определяется длиной строки символов.Затем я прочитал это в Википедии: «В современных компьютерах с байтовой адресацией каждый адрес идентифицирует один байт хранилища; данные, которые слишком велики для хранения в одном байте, могут находиться в нескольких байтах, занимая последовательность последовательных адресов». Так что в этомВ этом случае строковое значение на самом деле содержится в нескольких адресах, а не в 1 (это уже отличается от того, что я читал везде) (?).Как эти множественные адреса содержатся в 1 указателе в реальности?Будет ли указатель разделен на несколько адресов?Знаете ли вы, какой компонент в компьютере фактически идентифицирует и присваивает фактический адрес «кодам»?

А теперь мои актуальные вопросы;

2) В моем примере код выполняет 2 вещи:

  • Создает указатель на адрес, где хранится строковая переменная.
  • Он также фактически сохраняет фактическую «строковую переменную»: @ «Это указатель», потому что в противном случае было бы нечего указывать;

ОК, мой вопрос;Мне было интересно, что на самом деле происходит, когда вы отпускаете указатель [sPointer release];вы на самом деле освобождаете указатель (содержащий адрес), или вы также освобождаете фактическую «строковую переменную» из памяти?Я узнал, что когда вы удаляете ссылку, память, в которой хранится фактическая переменная, будет просто перезаписываться в тот момент, когда компилятору нужна память, поэтому ее не нужно очищать в это время.Это неправильно?Если это правильно, почему они говорят, что действительно важно освободить указатель NSString по соображениям производительности, если вы просто освободите указатель, который в основном будет содержать всего несколько байтов?Или я ошибаюсь, и память, в которой хранится фактическая переменная, также очищается сразу же сообщением "release"?

И, наконец, также: примитивные типы данных не освобождаются, но они «занимают» пространство памяти в момент объявления (но не больше, чем общий указатель).Почему бы нам не отпустить их на самом деле?Что мешает нам делать что-то вроде: int i = 5, за которым следует [i release] ?;

Извините - много вопросов за 1 раз!На практике у меня никогда не было проблем с этим, но в теории я также очень хочу полностью понять это - и я надеюсь, что я не единственный.Можем ли мы обсудить тему?Спасибо и извините за беспокойство!

Ответы [ 5 ]

2 голосов
/ 11 сентября 2011

Может быть, я ошибаюсь, но я только что прочитал вчера, что указатели обычно занимают 4 байта. Это не отвечает ни на один из ваших вопросов, но вы, кажется, действительно заинтересованы в этом, поэтому я решил, что упомяну это.

Я думаю, что источник вашей путаницы в том, что вы путаете примитивы с классами Objective-C. Классы Objective-C (или, точнее, объекты, экземпляры классов) могут принимать сообщения (аналогично вызовам методов в других языках). retain является одним из таких сообщений. Вот почему объект Objective C NSString может получать сообщение retain, но не примитив, такой как целое число. Я думаю, что это еще одно из ваших заблуждений. retain и release и т. Д. Не являются языковыми конструкциями Objective-C, это фактические сообщения (методы мышления), которые вы отправляете объектам. Вот почему они применяются к объектам Objective-C, но не применяются к примитивам, таким как целые числа и числа с плавающей запятой.

Другая похожая путаница заключается в том, что то, что вы читали о том, как хранятся строки, имеет больше общего со строками в стиле C, например char *name = "john". Однако, когда вы создаете указатель на NSString, он указывает на NSString экземпляр , который сам решает, как обрабатывать сохранение фактических строковых байтов / символов. Который может или не может быть таким же образом, как хранятся строки C.

данные, которые слишком велики для хранения в одном байте, могут находиться в нескольких байтах, занимая последовательность последовательных адресов. «Таким образом, в этом случае строковое значение фактически содержится в нескольких адресах, а не в одной 1 (это уже отличается от того, что я читаю везде) (?). Как эти множественные адреса в действительности содержатся в 1 указателе?

Например, в C указатель будет указывать на адрес первого символа в строке.

ОК, мой вопрос; Мне было интересно, что на самом деле происходит, когда вы отпускаете указатель [sPointer release]; вы на самом деле освобождаете указатель (содержащий адрес), или вы также освобождаете фактическую «строковую переменную» из памяти?

Вы отправляете сообщение release экземпляру / объекту NSString. Это важно отметить, чтобы избежать дальнейшей путаницы. Вы действуете не на сам указатель, а на то, на что указывает указатель, который является NSString объектом. Таким образом, вы не отпускаете указатель. После отправки объекта методом release, если его счетчик ссылок достигнет 0, он будет обрабатывать освобождение себя, освобождая все, что он хранит, что, я думаю, включает в себя фактическую строку символов.

Если это правильно, почему они говорят, что действительно важно освободить указатель NSString по соображениям производительности, если вы просто отпустите указатель, который в основном будет содержать всего несколько байтов?

Так что да, вы на самом деле отправляете сообщение release в экземпляр строки, а it обрабатывает, как освободить себя, если это необходимо. Если бы вы просто стерли указатель, чтобы он больше не указывал на экземпляр строки, то вы просто больше не будете знать, где / как получить доступ к данным, хранящимся в этом месте, но это не заставит его волшебным образом исчезнуть, программа не будет автоматически знать, что он может использовать эту память. На что вы намекаете, это сборщик мусора , в котором, проще говоря, неиспользуемая память автоматически освобождается для последующего использования. В Objective-C 2.0 есть сборка мусора, но, насколько я знаю, она еще не включена на устройствах iOS. Вместо этого новая версия iOS будет поддерживать функцию, известную как Автоматический подсчет ссылок , в которой сам компилятор заботится о подсчете ссылок.

Извините, если я не ответил на все ваши вопросы, вы задали тонну: P Если какая-либо из моих данных неверна, пожалуйста, дайте мне знать! Я пытался ограничить свой ответ тем, что я чувствовал, я знал.

1 голос
/ 11 сентября 2011

Ради форума я сделаю краткое и упрощенное резюме ваших ответов в качестве заключения. Спасибо всем вам за это расширенное разъяснение, туман исчез! Не стесняйтесь реагировать, если хотите что-то добавить или исправить:

  • Исправление: указатель на Mac занимает 4 байта пространства памяти, а не 2.

  • Указатель * sPointer указывает на экземпляр класса NSString, а НЕ непосредственно на память, в которой сохраняются символы. Экземпляр NSString состоит из набора iVars, в котором есть указатель iVar, который указывает на память, выделенную для хранения переменных типа char, составляющих строку (определяется при использовании метода initWithString: instance).

  • [sPointer release]; Сообщение о выпуске отправляется не самому указателю, а экземпляру объекта NSString. Вы действуете не на сам указатель, а на то, на что указывает указатель (!).

  • При отправке сообщения alloc счетчик сохранения экземпляра объекта NSString увеличивается на 1. При отправке сообщения «release» это не означает, что соответствующая память буквально очищается, но это уменьшает сохранить счет на 1. Когда счет сохранения достигает нуля, компилятор знает, что ранее выделенная память снова доступна для повторного использования.

  • Способ представления адресов памяти определяется операционной системой. Адрес логической памяти, используемый в программах, отличается от того, что фактически использует базовая реализация (адрес физической памяти).

  • ЛОКАЛЬНЫЕ переменные (не обязательно примитивные переменные) хранятся в памяти стека (в отличие от экземпляров объектов, которые хранятся в памяти кучи). Это означает, что они будут автоматически уничтожены в конце функции (они автоматически удаляются из стека). Больше информации о стеках и куче конструкций памяти можно найти в нескольких потоках, которые по-своему разъясняют использование и различия. например, Что и где находится стек и куча? / http://ee.hawaii.edu/~tep/EE160/Book/chap14/subsection2.1.1.8.html

1 голос
/ 11 сентября 2011

Как эти множественные адреса в действительности содержатся в 1 указателе? Будет ли указатель разделен на несколько адресов? Вы знать, какой компонент в компьютере на самом деле идентифицирует и назначает фактический адрес "коды"?

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

Например, в C исходные строки использовали завершенные строки NULL (\ 0), чтобы определить, когда заканчивается строка (Попробуйте выполнить printf() на C с ненулевой строкой, и она напечатает все, что находится в память пока не найдет ноль). Это, конечно, довольно опасно, и нужно использовать такие функции, как strncpy (обратите внимание на «n»), указывающие, что вы должны вручную вводить число символов в смещении до его окончания.

Способ обойти это - сохранить используемое пространство в начале адреса памяти, что-то вроде

struct
{
    int size;
    char *string;
}string;

Это хранит размер, чтобы предотвратить любые проблемы. Objective-C и многие другие более абстрактные языки по-своему реализуют способы обработки памяти. NSString* - довольно абстрактный класс, чтобы знать, что происходит за кулисами, он, вероятно, наследует всю свою память, управляющую от NSObject.

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

Теперь то, как компьютер выдает эти адреса, полностью зависит от операционной системы, и ваш адрес логической памяти, который вы используете во всех своих программах, совершенно отличается от того, что использует базовая реализация (адрес физической памяти). Как правило, вы обнаружите, что память хранится в сегментированных единицах, называемых «кадрами», а используемый кадр называется « page ». А отображение между физическим и логическим осуществляется с помощью «[Page Table]» 2 .

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

Также, пожалуйста, примите мой ответ с крошкой соли, я давно изучал эти предметы.

ОК, мой вопрос; Я задавался вопросом, что действительно происходит, когда вы выпускаете указатель [sPointer release]; вы на самом деле отпускаете указатель (содержащий адрес), или вы также освобождает фактическую строку переменная "из памяти? Я узнал, что когда вы удаляете ссылка, память, где хранится фактическая переменная будет просто перезаписывается в тот момент, когда компилятору нужна память, поэтому он не нуждается быть очищенным в это время. Это неправильно? Если это правильно, зачем говорят, что очень важно освободить указатель NSString по соображениям производительности, если вы просто отпустите указатель, который будет в основном содержат только несколько байтов? Или я не прав, а это память где фактическая переменная хранится на самом деле также очищается сразу с сообщение "релиз"?

Когда вы отпускаете, вы просто уменьшаете количество памяти объекта. То, что вы имеете в виду, это то, что происходит, когда он освобожден (когда счет достигает нуля).

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

Память может быть может быть очищена, но есть без гарантий .

Я надеюсь, что это устранит все сомнения, так как все они порождаются вашей путаницей в отношении освобождения памяти.

И, наконец, также: примитивные типы данных не освобождаются, но они "занимают" место в памятина момент объявления (но не более общего указателя).Почему бы нам не отпустить их на самом деле?Что мешает нам делать что-то вроде: int i = 5, за которым следует [i release]?;

Дело в том, что у С есть две основные цели (на самом деле, намного больше): куча, котораяхранит память, которая была запрошена с помощью alloc (или malloc в C), и они требуют освобождения.И стек, который содержит локальные переменные, которые умирают, когда функция / блок завершается (в стеке появляется вызов функции).

В вашем примере переменная i была локально объявлена ​​в своей области видимости, иэто ограничено в стеке.Попытка выполнить dealloc / free (также переменная, которую я не буду отвечать на release, или dealloc, поскольку это не объект) не будет работать, так как это не тот тип памяти, который требуется освободить.

Я предлагаю вам вернуться к C, прежде чем пытаться разобраться с тем, что делает Objective-C, потому что трудно иметь четкое представление о том, как императивное программирование работает со всеми хорошими абстракциями, такими как release и dealloc.

1 голос
/ 11 сентября 2011

Прежде чем я отвечу на вопросы, вы начинаете с ложной предпосылки. Указатель занимает более 2 байтов в не 16-битной системе. На Mac это занимает 4 байта для 32-разрядного исполняемого файла и 8 байтов для 64-разрядного.

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

  1. NSString - это класс (и довольно сложный). Строка, то есть экземпляр этого класса, содержит несколько административных иваров, а другой является указателем, который указывает на часть выделенной памяти, достаточно большую, чтобы, по крайней мере, содержать байты / кодовые точки, составляющие строку. Ваш код (точнее, метод alloc ) резервирует достаточно памяти для размещения всех ivars объекта (включая указатель на буфер) и возвращает указатель на эту память. Это то, что вы храните в своем указателе (если initWithString: не меняет его - но я не буду вдаваться в это здесь, давайте предположим, что это не так). При необходимости initWithString: выделяет буфер, достаточно большой для размещения текста строки, и сохраняет его память в указателе для него внутри экземпляра NSString. Итак, это так:

     sPointer                            NSString instance        buffer
    +---------------------------+       +-----------------+       +------+
    | addr of NSString instance | ----> | ivar            |   +-> | char |
    +---------------------------+       | ivar            |   |   | char |
                                        | ivar (pointer)  | --+   | char |
                                        | ivar            |       | char |
                                        | etc...          |       | char |
                                        +-----------------+       | char |
                                                                  | etc. |
                                                                  +------+
    
  2. В случае жестко закодированной литеральной строки, такой как @"Hello", внутренний указатель указывает только на ту строку, которая уже сохранена в программе, в постоянной памяти. Для него не нужно выделять память, и память также не может быть освобождена.

Но давайте предположим, что у вас есть строка с выделенным содержимым. release (либо кодируется вручную, либо вызывается пулом автоматического выпуска) уменьшит счетчик ссылок строкового объекта (так называемый retainCount ). Если это число достигнет нуля, ваш экземпляр класса NSString будет освобожден, а в методе dealloc строки будет освобожден буфер, содержащий текст строки. Эта память никак не очищается, она помечается как свободная только менеджером памяти, что означает, что она может быть повторно использована для каких-то других целей.

1 голос
/ 11 сентября 2011

"Или я ошибаюсь, и память, в которой хранится действительная переменная, на самом деле также сразу очищается сообщением" release "?"Память НЕ ОЧИЩАЕТСЯ, но уходит в пул свободной памяти, так что это фактически уменьшает печать памяти программы.Если вы не отпустите указатель, вы продолжите «захватывать» память до тех пор, пока не будете использовать всю доступную виртуальную память и не потерпите крах не только вашей программы, но и потенциальной системы.

...