Навигация
|
|||||
Оглавление
|
|||||
Представление строк в памяти
В некоторых случаях динамическая память неявно используется программой, например для хранения строк. Длина строки может варьироваться от нескольких символов до миллионов и даже миллиардов (теоретический предел равен 2 ГБ). Тем не менее, работа со строками в программе осуществляется так же просто, как работа с переменными простых типов данных. Это возможно потому, что компилятор автоматически генерирует код для выделения и освобождения динамической памяти, в которой хранятся символы строки. Но что стоит за такой простотой? Не идет ли она в ущерб эффективности? С полной уверенностью можем ответить, что эффективность программы не только не снижается, но даже повышается. Физически переменная строкового типа представляет собой указатель на область динамической памяти, в которой размещаются символы. Например, переменная S на самом деле представляет собой указатель и занимает всего четыре байта памяти (SizeOf(S) = 4): var При объявлении этот указатель автоматически инициализируется значением nil. Оно показывет, что строка является пустой. Функция SetLength, устанавливающая размер строки, на самом деле резервирует необходимый по размеру блок динамической памяти и записывает его адрес в строковую переменную: SetLength(S, 100); // S получает адрес распределенного блока динамической памяти За оператором присваивания строковых переменных на самом деле кроется копирование значения указателя, а не копирование блока памяти, в котором хранятся символы. S2 := S1; // Копируются лишь адреса Такой подход весьма эффективен как с точки зрения производительности, так и с точки зрения экономного использования оперативной памяти. Его главная проблема состоит в том, чтобы обеспечить удаление блока памяти, содержащего символы строки, когда все адресующие его строковые переменные прекращают свое существование. Эта проблема эффективно решается с помощью механизма подсчета количества ссылок (reference counting). Для понимания его работы рассмотрим формат хранения строк в памяти подробнее. Пусть в программе объявлены две строковые переменные: И пусть в программе существует оператор, присваивающий переменной S1 значение некоторой функции: Readln(S1); // В S1 записывается адрес считанной строки Для хранения символов строки S1 по окончании ввода будет выделен блок динамической памяти. Формат этого блока после ввода значения 'Hello' показан на рисунке :
Если в программе встречается оператор присваивания значения одной строковой переменной другой строковой переменной, S2 := S1; // Теперь S2 указывает на тот же блок памяти, что и S1 то, как мы уже сказали, копия строки в памяти не создается. Копируется только адрес, хранящийся в строковой переменной, и на единицу увеличивается количество ссылок на строку .
S1 := '';
Интересно, а что происходит при изменении символов строки, с которой связано несколько строковых переменных? Правила семантики языка требуют, чтобы две строковые переменные были логически независимы, и изменение одной из них не влияло на другую. Это достигается с помощью механизма копирования при записи (copy-on-write). Например, в результате выполнения операторов получим следующую картину в памяти ):
Все, что было сказано выше о представлении в памяти строк, относится
только к строкам формата AnsiString. Строки формата WideString тоже
хранятся в динамической памяти, но для них не поддерживаются механизм
подсчета количества ссылок и механизм копирования по записи. Операция
присваивания строковых переменных формата WideString означает выделение
нового блока динамической памяти и полное копирование в него всех символов
исходной строки. Что же касается коротких строк, то они целиком хранятся
по месту объявления: или в области данных программы (если это глобальные
переменные), или на стеке (если это локальные переменные). Динамическая
память вообще не используется для хранения коротких строк. |