Данному образовательному сайту пришлось несколько раз менять свое имя. С 2022 года доступ к нему обеспечивается по URL
emc.orgfree.com

emc.km.ru (2001-2007) ==> educomp.org.ru (2007-2011) ==> educomp.runnet.ru (2011-2021) ==> emc.orgfree.com (2022-...)
Более подробно об истории сайта можно прочитать здесь.


Учебные модели компьютера



Модели (software):

"Е14" (parallel !!!)
"S9PU" (parallel)

Модели (hardware):






Награды сайта
Награды сайта 2005
(Продолжение. Начало см. здесь.
Можно также загрузить исходные файлы проектов.)

Изучение средствами Delphi способов хранения в компьютерной памяти различных данных
2. Данные: от Турбо Паскаля к Delphi

При переходе от Турбо Паскаля к Delphi с данными произошло довольно много изменений. Их основой, без сомнения, стали (как минимум) две важные причины: увеличение в 2-4 раза разрядности процессоров и существенное недовольство программистов некоторыми ограничениями Паскаля, например, принципиальной невозможностью иметь строку более 255 символов. В результате в Delphi не только появились новые типы данных, но и старые (INTEGER, STRING) получили большие возможности.

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

2.1. Целые числа

Центральное новшество в этой области состоит в том, что в Delphi тип INTEGER уже не двух-, а четырехбайтовый5, т.е. фактически неотличим от LONGINT в Турбо Паскале! Ради сохранения преемственности в Delphi введен тип SMALLINT, который полностью эквивалентен INTEGER в Турбо Паскале.

А еще для любителей вычислений с большими числами в Delphi добавлен 64-разрядный тип INT64. Полная таблица целочисленных типов приведена ниже.

типдиапазонналичие знакадлина (бит)
shortint-128 .. 127знаковый8
smallint-32768 .. 32767знаковый16
longint (integer)-2147483648 .. 2147483647знаковый32
int64-263 .. 263 – 1знаковый64
byte0 .. 255беззнаковый8
word0 .. 65535беззнаковый16
longword (cardinal)0 .. 4294967295беззнаковый32

Эксперимент 2. Количество байт в INTEGER

Постановка задачи. Убедиться, что тип INTEGER действительно занимает в памяти 4 байта.

Реализация эксперимента. Простейший способ проверить это – задать большое число, для убедительности – 2147483647 (см. таблицу).

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

Листинг 2
const Nb=4;
type  t=integer;
var   test: t;
      b: array [1..Nb] of byte absolute test;
      i: integer;
 
  test:=2147483647; {2^31 - 1}
  writeln(test);
  for i:=1 to Nb do write(b[i]:4);

  readln;

Результат выполнения эксперимента на экране будет выглядеть следующим образом:
2147483647
 255 255 255 127

Мы видим, что число «воспринимается» и сохраняется, а значит, значение уже не двухбайтовое, а минимум четырехбайтовое. Шестнадцатеричное представление числа (с учетом перестановки байт) – 7F FF FF FF (т.е. 127 255 255 255 в десятичном виде), подтверждает, что перед нами максимальное положительное значение 32-битного целого числа со знаком.

Стоит также провести еще одну дополнительную проверку: увеличить данное максимально допустимое число на единицу и записать test:=2147483648; При попытке трансляции в нижней части окна с текстом программы появится предупреждающее сообщение:

[Warning] int_len.dpr(17): Constant expression violates subrange bounds
(примерный перевод:
[Предупреждение] проект int_len.dpr(строка 17): значение константы нарушает границу допустимого диапазона)

Таким образом, Delphi подсказывает нам, что предел четырехбайтовой величины превышен. Тем не менее, задача запускается (предупреждение – это «небольшая» ошибка), а ответ, как и следовало ожидать, выдается отрицательным.

Наконец, есть и еще один (совсем простой) способ проверки – вывести на экран значение стандартной константы MAXINT и убедиться, что оно равно 2147483647, что соответствует именно четырехбайтовому целому7.

Еще рекомендую поэкспериментировать с другими типами целочисленных данных, указанных в таблице, например, LONGINT и SMALLINT.

Выводы. В Delphi значение типа INTEGER хранится не в двух, как в Турбо Паскале, а в четырех байтах; LONGINT в обоих системах программирования совпадают, следовательно, в Delphi INTEGER и LONGINT одно и то же. SMALLINT – новый двухбайтовый тип Delphi, введенный для совместимости с INTEGER в старых реализациях Паскаля.

2.2. Вещественные числа

А в этой области нас ждут еще более радикальные изменения. Тип REAL, к которому мы так привыкли в классическом Паскале, вообще объявлен устаревшим и не рекомендуется к употреблению (хотя и поддерживается ради совместимости под псевдонимом REAL48).

Вместо REAL предлагается использовать другие вещественные типы – SINGLE, DOUBLE и EXTENDED (они есть и в Турбо Паскале). Все они непосредственно опираются на одноименные форматы данных математического сопроцессора, поэтому фактически даже не являются чем-то «сугубо паскалевским». Поскольку все эти важные с практической точки зрения данные оказываются независимыми от языка программирования, есть смысл рассмотреть круг вопросов, которые связаны с вещественными числами, отдельно. Это будет сделано в разделе 3.

Напомню также читателям, что исторически тип REAL был реализован в виде специализированной математической библиотеки Паскаля и не имел аппаратной поддержки.

2.3. Строковые данные

История такова, что в классическом Паскале не было специального строкового типа, а для обработки текста создавался сложный тип данных ARRAY OF CHAR. В более поздних реализациях компиляторов появился тип STRING с большим ассортиментом специализированных процедур и функций для работы с текстами. Программисты по достоинству оценили новые возможности, хотя заложенное в Турбо Паскале принципиальное 255-символьное ограничение (объяснение его появления см., например, в [4]), конечно, создавало трудности для программирования реальных задач.

При переходе к Delphi максимальная длина текста было сдвинута с 255 байт до 2 Гбайт, что для большинства практических (и всех учебных!) задач позволяет вообще забыть о существовании этого ограничения.

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

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

Статические и динамические переменные
Рис. 6. Статические и динамические переменные

В первом случае при трансляции программы под переменную выделяется одна или несколько ячеек ОЗУ и в этой области памяти позднее будут храниться значения переменной. Фактически везде, где в тексте программы имеется ссылка на данную переменную, компилятор подставляет команды обращения к адресам зарезервированной области памяти. Выделенная под переменную память не может быть использована иначе, чем для хранения значений этой переменной8.

Динамический механизм работает совсем по-другому. В момент трансляции под переменную выделяется некоторое место в ОЗУ, которого достаточно только для хранения адреса (почувствуйте разницу: резервируется место не под сами данные, а под адрес, ссылающийся на участок памяти, где находятся данные). Память под данные в этот момент не выделяется – это делается при исполнении программы, когда переменной будет присваиваться конкретное значение. И в этом главный залог успеха: под значение будет отведено ровно столько динамической памяти, сколько требуется9.

Применительно к текстам это выглядит так. В Турбо Паскале заранее резервируется память под строку максимально возможной длины, даже если при выполнении программы окажется, что переменная реально будет хранить всего один символ. (Сравните с бланком, где ширина колонки под фамилию всегда рассчитана на самый длинный текст, тогда как некоторые фамилии вроде Ким или Усов можно было бы поместить и в более узкую колонку). В Delphi память под значение выделяется в процессе исполнения, поэтому переменная получает именно столько байтов, какова длина текста. Таким образом, в отличие от увеличения размера чисел INTEGER, описанного в 2.1, удлинение строк потребовало еще и изменения способа организации памяти.

Новый «расширенный» тип STRING называется в Delphi ANSISTRING. Для совместимости существуют также короткие строки SHORTSTRING, полностью воспроизводящие STRING в Турбо Паскале. Описание STRING по умолчанию трактуется как ANSISTRING10. Описание STRING[n], где n≤255, генерирует статическую форму SHORTSTRING.

Дополнительно в Delphi имеется еще тип WIDESTRING, созданный для поддержки 16-битной кодировки Unicode.

Обогащенные столь подробными теоретическими знаниями, поэкспериментируем со строками.

Эксперимент 3. Изучение хранения строк

Постановка задачи. Изучить статическое размещение в памяти строковой переменной. Посмотреть, как выглядит содержимое памяти с динамической переменной.

Реализация эксперимента. В листинге 3 показаны описательная и исполняемая часть программы для нашего эксперимента. Описание string[3] обеспечивает статическое выделение памяти под переменную test. В ходе выполнения программы ей присваивается значение 'A 1', а затем байты переменной выводятся на экран.

Листинг 3
const Nb=8;
type  t=string[3];
var   test: t;
      b: array [1..Nb] of byte absolute test;
      i: integer;
 
  test:='A 1';
  for i:=1 to Nb do write(b[i]:4);

  readln;

В результате выполнения эксперимента на экране появятся числа:
   3  65  32  49   0   0   0   0

Первое из них – это фактическая длина текста. Три следующих – это десятичные(!) коды символов, а остальные нули показывают, что мы вывели слишком много байт. Полученная картина «один к одному» повторяет результаты для Турбо Паскаля [4].

Очень поучительно взглянуть, что получится, если в переменную test попробовать занести более трех символов: результат не изменится, поскольку Паскаль просто игнорирует лишние символы.

Наконец, заменим описание string[3] на string. Тем самым, вместо статической строки получим динамическую, и результат эксперимента будет другим. На моем экране появились числа:
180   7 134   0   0   0   0   0

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

Если хватит терпения, можете присвоить динамической переменной test строковое значение, длина которого превышает 255 символов, и напечатать его; для этого добавьте в программу строку writeln(test);

Выводы. В Delphi в дополнение к Турбо Паскалю добавляется принципиально новый строковый тип практически неограниченной длины; он реализован не статическим, а динамическим образом.

2.4. Тип VARIANT

В Delphi появляется принципиально новый тип данных VARIANT, который может помещать внутрь переменной значения разного типа: например, сначала INTEGER, а затем STRING. В классическом Паскале такое вообще невозможно. Кроме того, действия над значениями типа VARIANT выполняются особым образом. Например, если V1 и V2 имеют тип вариант, и V1=12.5, V2='12', то V1+V2 будет равно 24.4, потому что строка '12' перед сложением будет автоматически преобразована в число.

Рассмотрим кратко, как же устроен это необычный тип данных. Каждая переменная типа VARIANT состоит из 16 байт, причем первая их половина хранит служебную информацию, а вторая – значение переменной или ссылку на это значение. Самые первые 2 байта переменной содержат целое число, характеризующее тип значения, которое в данный момент лежит в переменной. Полный список возможных типов содержит около 20 разных значений, начиная целыми и вещественными числами и кончая символьными значениями и даже массивами. Например, для числа типа INTEGER характеристическое значение равняется 3, для вещественного числа типа DOUBLE – 5, а для STRING – 10016 = 25610. Остальная часть служебной части пока не используется (зарезервирована) и заполняется нулями.

Эксперимент 4. Как устроен тип VARIANT?

Постановка задачи. Изучить, как тип VARIANT хранит в себе разные значения, например, INTEGER и STRING[n].

Реализация эксперимента. Программа для проведения эксперимента приведена в листинге 4.

Листинг 4
const Nb=20;
type  t=integer; {string[3];}
var   test: variant; e:t;
      b: array [1..Nb] of byte absolute test;
      i: integer;
 
  readln(e);
  test:=e;
{тип}
  writeln(b[2],' ',b[1]);
{резерв}
  for i:=3 to 8 do write(b[i],' '); writeln;
{данные}
  for i:=9 to Nb do writeln(b[i],' ');

  readln;

В описательной части при первом запуске будем ставить тип INTEGER, а при втором – STRING[3]. В программе по необходимости предусмотрена дополнительная переменная e, в которую вводится значение с клавиатуры. Дело в том, что непосредственно в переменную VARIANT вводить с клавиатуры нельзя, поскольку компьютер не всегда способен однозначно определить, какого именно типа набранное значение (попробуйте, в частности сами догадаться: введенная с клавиатуры последовательность символов «15 3» – это два целых числа, разделенных пробелом, или же строка?)

Хотя тип VARIANT хранится в 16 байтах, в описании указано значение 20. По результатам второго запуска мы увидим, как пригодится этот запас.

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

Итак, запустим программу для целочисленных данных. На экране получатся следующие результаты (для экономии места 7 последних нулей не показаны):
65536
0 3
0 0 0 0 0 0
0
0
1
0
0

Расшифруем приведенную выдачу на экран. Первая строка содержит число, которое я вводил. Далее выдается число 3, которое, как мы знаем, означает тип INTEGER. Пропуская строку резервных нулей обращаем внимание на следующие 4 байта, которые и есть собственно число. Выпишем его, традиционно переставляя байты, и получим 00 01 00 00; после перевода из шестнадцатеричного представления в десятичное убедимся, что перед нами введенное число.

Теперь заменим описание INTEGER на STRING[3] и запустим программу еще раз. «Протокол работы» выглядит следующим образом.
A 1
1 0
0 0 0 0 0 0
180
7
133
0
0
0
0
0
3
65
32
49

Прокомментируем содержимое выдачи. Текст «A 1» я набрал по аналогии с листингом 3. В следующей строке вывелась характерная для строкового типа константа 01 00 (байты уже переставлены программно!) Пропускаем резервные нули и видим странные числа. Вспомните предыдущий эксперимент: это не что иное, как ссылка на то место ОЗУ, где лежит строка. И на этом бы все и закончилось, но нам сильно повезло: это самое место ОЗУ оказалось сразу после переменной типа VARIANT и мы его «зацепили»! Отсчитайте 16 чисел, начиная с 1 во второй(!) строке (это 16 байт переменной test), и вы попадете на число 3, которое есть не что иное, как длина введенного текста. И далее знакомые нам десятичные коды введенных символов. Так что это то самое строковое значение, на которое ссылается тип VARIANT.

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

Таким образом, очень простые программки позволили нам познакомиться с интересным новым типом данных в Delphi, а также с новыми свойствами старых типов, пришедших из Турбо Паскаля.

{Уже после отправки статьи в редакцию, по совершенно другому поводу родился еще один эксперимент - посмотреть кодирование булевских переменных. Как оказалось, имеющиеся в современных версиях Паскаля логические типы данных могут кодировать TRUE и FALSE по-разному! В частности, в Delphi для boolean в качестве значения true выдается 1, а для longbool - минус 1 (зато во Free Pascal оба значения равны 1). Воистину классики были правы, когда учили, что никогда не следует сводить переменные boolean к числам! Упомянутый проект редакция успела включить в число примеров на диске; вы тоже можете его загрузить и посмотреть (ищите среди примеров раздела 2).}




5 факториал от 8 теперь уже не будет отрицательным, эта неприятность «отложится» до 17!

6 удобно взять за основу файл с листингом 1: тогда в описательной части достаточно заменить тип longint на integer

7 более «мощный» вариант – использовать конструкцию вроде high(smallint)

8 понимаете теперь, почему Delphi предупреждает вас, когда переменная описана, но ни разу не использовалась?

9 те читатели, кто пользовался в программах на Паскале типом «указатель», хорошо представляют данный механизм; возможно, преподаватели со стажем также вспомнили об области string space в языке MSX BASIC

10 если установить директиву компилятора $H–, то установится совместимость со строками Турбо Паскаля


© Е.А.Еремин, 2010
Публикация:
Еремин Е.А. Изучение средствами Delphi способов хранения в компьютерной памяти различных данных. Информатика, 2010, N 19, с.4-23.


Автор сайта - Евгений Александрович Еремин (Пермский государственный педагогический университет). e_eremin@yahoo.com


Free Web Hosting