TwoDigitYearCenturyWindow — Переменная Delphi

Содержание

Delphi-Help

TwoDigitYearCenturyWindow

TwoDigitYearCenturyWindow

Описание

var TwoDigitYearCenturyWindow : Word;

Переменная TwoDigitYearCenturyWindow используется при преобразовании строки даты с 2-мя цифрами года в значение TDateTime. Значение TwoDigitYearCenturyWindow вычитается из текущей даты, чтобы установить конверсионный порог.

TwoDigitYearCenturyWindow: 50 (по умолчанию)
Текущий год: 2006
Тогда порог устанавливается в: 1956
100-летнее окно становится: 1956 — 2055

При преобразовании строки, где год состоит из 2 цифр, типа 75, эти 2 цифры сравниваются с 2-мя последними цифрами порога. Если больше, то дата находится в меньшем столетии, например 1975. Если ниже чем порог, то дата находится в более высоком столетии. Например, 44 дал бы 2044.

Пример кода

var
myDate : TDateTime;
formattedDate : string;

begin
// Установите дату с 2-мя цифрами года, используя порог заданный по умолчанию
myDate := StrToDate(’09/05/30′);
ShowMessage(’09/05/30 используя порог по умолчанию = ‘+DateToStr(myDate));

// Теперь изменяем порог заданный по умолчанию на 80:
// 2007 (на время написания) — 80 дает 1927
// 30 больше 22, так что выбирается 1900-ое столетие
TwoDigitYearCenturyWindow := 80;
myDate := StrToDate(’09/05/30′);
ShowMessage(’09/05/30 используя изменённый порог = ‘+DateToStr(myDate));
end;

09/05/30 используя порог по умолчанию = 09/05/2030
09/05/30 используя изменённый порог = 09/05/1930

Примечание

Если значение TwoDigitYearCenturyWindow является нулевым, то значение всегда устанавливается в текущем столетии, независимо от этих 2 значений цифр.

Переменные в среде программирования Delphi

В Delphi существуют два типа переменных: локальные и глобальные. Глобальные переменные — это переменные, объявленные за пределами функции или процедуры. Например, переменные, объявленные в главном файле проекта Delphi или в разделах интерфейса или реализации модуля, является глобальными. Глобальные переменные создаются во время запуска приложения, существуют, пока оно выполняется, и уничтожаются по завершении его работы. Эти переменные не только доступны в течение всего времени работы приложения, но и являются общедоступными.

Любая создаваемая процедура или функция может обращаться к общедоступным переменным. Единственное исключение из этого правила — глобальная переменная, объявленная в разделе реализации модуля. В этом случае переменная остается глобальной, но доступна только в том модуле, в котором она объявлена.

Компилятор автоматически инициализирует глобальные переменные как «пустые». Целочисленные значения устанавливаются равными О. строковые значения равными ‘ ‘. а булевские — равными False. Глобальные значения можно также инициализировать вручную при их объявлении: var х: Integer = 101;

Локальные переменные — это переменные, которые объявлены в процедуре или функции. Объявление локальной переменной выглядит подобно показанному ниже:

procedure Имя Процедуры; var

локальная Переменная_1: Тип Данных;

локальная Переменная_n: Тип Данных; begin end;

Локальные переменные существенно отличаются от глобальных. Локальные переменные существуют только в течение короткого периода времени. Они создаются при вызове процедуры или функции и уничтожаются немедленно по завершении ее выполнения. Локальные переменные могут использоваться только в процедуре или функции, в которой они объявлены. В отличие от глобальных переменных, локальные переменные не инициализируются компилятором автоматически, и они не могут быть инициализированы во время объявления. Их всегда нужно инициализировать вручную в теле процедуры до их использования, поскольку до инициализации они содержат случайные значения.

Если указать глобальную переменную в качестве счетчика цикла for, компилятор выведет предупреждение.

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

Наилучший механизм передачи определенных пользователем значений функциям и процедурам — список параметров. В процедурах можно использовать также глобальные переменные, но этого следует стараться избегать. Delphi поддерживает несколько типов параметров: значения, переменные, константы и параметры типа out.

Объявление параметров-значений выглядит следующим образом: procedure Имя Процедуры <Имя Параметра: Тип Данных);

При вызове процедуры и передаче ей значения как параметра-значения процедура получает только копию исходного значения.
4. Разработка программного продукта для предметной области «Работники образовательной организации» с применением языка программирования Delphi

Создаем форму и изменяем необходимые свойства главной формы в окне свойств:

Height — ширина формы;

Width — длина формы;

Caption — название формы.

Добавляем на главную форму необходимые элементы управления:

Button – запуск вычислений, открытие окна графика, выход из программы;

StringGrid – отображение таблицы значений x, y, s.

Label – отображение не редактируемого текста;

Edit – ввод данных для формирования массивов и вывода полученных числовых рядов.

Изменяем свойства добавленного элемента управления StringGrid:

Пишем для кнопок алгоритмы, которые будут выполнять различные действия.Прописываем действие на открытие формы на главной форме работы. Запускаем программу с целью проверки ее работоспособности. Сохраняем проект.

Понимание деклараций/инициализации переменных Delphi

3 le-a [2013-05-17 02:06:00]

Как это относится к Delphi.

Когда переменная объявляется определенного типа, она инициализируется OBJECT этого типа? Или нужно присвоить переменной выражение, которое возвращает объект этого типа?

Я исхожу из сильного фона Java. Я хочу спросить, это. В Java, скажем, вы объявляете переменную экземпляра определенного пользователем типа «оранжевый». Что будет выглядеть так:

Переменная _fruit по-прежнему содержит ссылку на null до фактического присвоения экземпляра класса Orange, например:

В Delphi, если я объявляю переменную типа TForm, например:

Открыта ли форма для объекта TForm? Или это все еще ноль?

Я спрашиваю, потому что я получаю сообщение об ошибке при попытке скомпилировать небольшой фрагмент кода, который показан ниже:

Вот главный блок:

и вот вторая единица:

Ошибка, которую я получаю, когда пытаюсь скомпилировать, в точности: «Нарушение доступа по адресу 005B17F9 в модуле» Multiple.exe «. Чтение адреса 00000000.» Я думал, что это потому, что я каким-то образом не инициализирую переменную SecondForm в модуле Main? Однако я попытался разместить «SecondForm.Create» в процедуре ShowForm2Click, и я получаю ту же ошибку. Я получаю эту ошибку, потому что SecondForm не назначен? Нужно ли его инициализировать? Или это?

Примечание: Я три дня новичок в Delphi. Пожалуйста, будьте внимательны.

variable-assignment delphi variable-initialization

3 ответа

2 Решение J. [2013-05-17 03:17:00]

также обратите внимание, что unit Second и unit Main объявляют глобальную переменную

В случае основного блока ваш основной блок будет скрывать переменную SecondForm , объявленную в unit Second (даже если она перечисляет ее в предложении uses ). В случае приложения Delphi VCL Forms, если SecondForm является формой автоматического создания, то SecondForm , объявленный в unit Second , не будет nil и на самом деле уже имеет экземпляр TSecondForm , созданный и присваивается ему, но это будет недоступно unit Main , потому что объявляет глобальное одноименное имя (которое, как и все ссылочные типы, будет nil , пока вы что-нибудь с ним не сделаете).

Короче говоря, лучше всего не объявлять глобальный SecondForm : TSecondForm в unit Main — называть его чем-то другим или использовать глобальное объявление, объявленное в unit Second . Если SecondForm является формой автоматического создания (поведение по умолчанию), то приведенный выше код будет работать, если вы просто не повторно объявили SecondForm в unit Main — если бы вам не пришлось создавать экземпляр SecondForm .

Формы VCL автоматически автоматически создают формы, если не указано иное. Контрольное меню:

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

SecondForm.Create — неправильный синтаксис. Конструкторы являются специальными в Delphi. Вы можете думать о них более или менее как о методах класса. Способ их вызова выглядит следующим образом:

Хотя можно вызвать конструктор как метод экземпляра ( variable.Create ), это относится к одному конкретному варианту использования и не должно выполняться в общем коде. Причина вызова конструктора для объекта, а не типа, заключается в том, что вы уже находитесь внутри конструктора для этого объекта. (т.е. если у вас есть более одного конструктора объекта, а один из них вызывает другой, или инициализировать члены класса предков, вызывая конструктор родительского класса с inherited Create(arguments); )

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

TwoDigitYearCenturyWindow — Переменная Delphi

В течение своей профессиональной деятельности программист вырабатывает систему правил, которая позволяет ему не совершать допущенных ранее ошибок и избегать потенциально опасных ситуаций. Ценность правил заключается в том, что они ограждают программиста от не всегда очевидных проблем, дают возможность писать единообразный код и дают возможность поступать формально, тем самым, освобождая «мыслительные» ресурсы на решение поставленной задачи.

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

Выработанные правила направлены на:

  • Повышение надежности работы программы, т.е. уменьшение вероятности возникновения ошибки. Вероятность возникновения ошибки существует всегда, никто не безгрешен (включая операционную систему). Задача программиста — свести эту вероятность к минимуму.
  • Увеличение устойчивости программы — свойства, при котором она возвращается в стабильное состояние после возникновения возмущения (ошибки) (а не зависает, исчезает или уваливает операционную систему).
  • Написание единообразного и легко поддерживаемого кода.

Warnings and Hints

Компилятор Delphi снабжен «анализатором» качества кода. Он может предупреждать о потенциально опасных или бессмысленных ситуациях. Не пренебрегайте его услугами.

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

Использование констант

Используйте именованные константы. Это увеличивает «настраиваемость» исходного кода. А также избавляет от проблем связанных с изменением значения константы в случае ее множественного вхождения.

Range Check и Integer Overflow Check

К сожалению, эти опции компилятора по умолчанию отключены в Delphi, и многие разработчики не пользуются их услугами, а зря. Появления этих ошибок говорит о наличии в программе семантических ошибок, таких как неправильная индексация массива или использование несоответствующего целочисленного типа. Последствия этих ошибок могут быть весьма коварны. Я советую оставлять эти флаги всегда включенными, независимо от того — это отладочная или «финальная» версия программы. Лучше иметь неработающую программу (или ее часть), чем программу работающую неправильно (IMHO).

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

Будьте недоверчивы

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

  • Проверяйте значения переменных на допустимость. Особенно это касается переменных типа указатель, процедурных переменных и объектов.
  • Защищайте пары выделение-освобождение ресурсов блоками try/finally. Предполагайте, что исключение может произойти в любом операторе.
  • Используйте процедуру Assert для проверки условий, которые всегда должны быть истинными.

Объем кода, добавленный для проверок и обработки ошибок, может достигать порядка «полезного» кода! Но, такой стиль программирования является необходимым условием при написании сложных систем. Что поделаешь, из бревен небоскреб не построишь

Значения по умолчанию и «неопределенные» значения

В логике распределения значений для переменных всегда необходимо предусматривать «неопределенное» значение и значение по умолчанию. Отсутствие таких значений достаточно часто приводят к семантическим ошибкам.

Правило №1:

  • Для указателей и объектов пустым значением должно являться значение nil.
  • Для числовых типов лучше всего резервировать значение ноль.
  • Для строковых переменных — пустая строка
  • Для перечислимых типов необходимо предусмотреть специальное значение.

Пример:
TDayOfWeek = (dwNone,dwSun,dwMon,dwTue,dwWen,dwThu,dwFri,dwSat);

Правило №2:
«Неопределенными» значениями лучше всего выбирать такие, чье двоичное представление соответствует нулю (нулям). Это увеличивает устойчивость, когда не выполнена начальная инициализация переменной, но произведена инициализация блока памяти, в котором она размещается.

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

Инициализация переменных и полей

Неинициализированные переменные часто становятся причинами возникновения ошибок-фантомов. Обычно они имеют нерегулярную природу, и их трудно выявить в процессе отладки. Особенно катастрофичными могут быть последствия при таком обращении с указателями.

Правило:

  • Для глобальных переменных: использовать типизированные константы, инициализированные переменные или присваивать начальные значения переменным в секции инициализации модуля.
  • Для локальных переменных: присваивать начальные значения в первых строках процедуры или функции.
  • Для полей объектов: присваивать начальные значения полям в конструкторе и не полагаться на то, что память, выделенная под объект, инициализируется нулями.
  • Массивы, записи и выделенные блоки памяти очень удобно инициализировать при помощи функции FillChar. Но, с появлением в Delphi «управляемых» (manageable) типов (длинные строки, динамические массивы, варианты и интерфейсы), пользоваться ей необходимо с четким пониманием.

В данном примере вызов процедуры FillChar проинициализирует строки пустыми значениями, такой подход был нормальным в ранних версиях Delphi и Borland Pascal, но недопустим в последних версиях, в которых тип string по умолчанию соответствует типу LongString и суть указатель. Если значения строк перед инициализацией были не пусты, то мы получим утечку памяти.

Передача параметров

В Delphi параметры функций и процедур по умолчанию передаются по значению. Т.е. для них выделяется область памяти в стеке или куче, куда копируются оригинальные значения. При передаче параметров сложных типов (запись, массив, строка, вариант) это сопряжено со значительными расходами ресурсов, поэтому параметры этих типов желательно передавать по ссылке, т.е. с использованием ключевых слов var или const. Замечено, что наиболее типична эта ошибка при передаче параметра типа string.

Функции, процедуры и состояния

Для начала словарь терминов: Функция — это подпрограмма, задачей которой является получение (извлечение, вычисление и т.д.) определенного значения на основании входных параметров и текущего состояния системы. Процедура — это подпрограмма, которая предназначена для выполнения каких либо действий над системой, и соответственно изменяет состояние системы. Просьба не путать эти определения с ключевыми словами function и procedure.

Правило:
Подпрограмма должна быть либо функцией, либо процедурой. Не совмещайте эти две задачи в одной подпрограмме, разделите ее на несколько подпрограмм.

Контроль достижения предела

Довольно часто встречаются случаи, когда контроль достижения предела цикла осуществляется условием равенства.
Пример

Что произойдет, если в результате ошибки (или просто модификации алгоритма) переменная I перескочит через значение Limit? Правильно — ничего хорошего. Более устойчивой будет конструкция с использованием условия отсечения диапазона, т.е. I >= Limit.

Частота выделения-освобождения ресурсов

Очевидно, что скорость потери ресурсов (памяти, дескрипторов и т.д.) пропорциональна частоте их выделения. Рассмотрите варианты реализации, в которых ресурсы выделяются наиболее редко. Таким образом, вы сможете отсрочить крах программы, и некоторые пользователи могут даже и не узнать, что с ней что-то не так.

Пример:
Допустим, в объекте есть метод DoSomething. В процессе работы он выделяет и освобождает память, которая нужна только ему. С точки зрения «выделения ресурсов по месту их использования» — все корректно, но при многократном обращении к этому методу и в случае наличия ошибки при освобождении памяти вы можете получить достаточно интенсивную утечку памяти. В данной ситуации имеет смысл рассмотреть одноразовое выделение памяти при создании объекта и освобождении при его разрушении. В данной ситуации при наличии ошибки скорость утечки будет гораздо меньше. Естественно, что данное решение необходимо рассматривать в комплексе с другими задачами (производительность, минимизация расхода ресурсов и т.д.)

Область использования переменных

Много сказано и написано на эту тему. Но еще раз повторюсь:

  • Объявляйте переменные по месту их использования.
  • Избегайте использования глобальных переменных. Если все же без них не обходиться, то не забывайте, что ваша библиотека может быть использована в многопотоковом приложении.

Интерфейсы объектов

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

«Просачивание» исключений в библиотеках

При написании библиотеки функций или классов не закрывайте просачивание исключений наружу, если это конечно не предусмотрено логикой библиотеки.

Обработка исключений, возникших в библиотеке — это задача приложения, которое использует данную библиотеку.

Определение и использование классов

Любой модуль можно логически разделить на две части:

  • определения — в которой определяются переменные, функции, классы, их методы и т.д.
  • использования — создание экземпляров классов в секции инициализации модуля.

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

Модуль Forms содержит определения классов, вспомогательных функций и создает экземпляры глобальных переменных (Application, Screen и т.д.). Допустим, в вашем консольном приложении, не использующем графический интерфейс нужна какая-то константа из модуля Forms. Включив его в свой проект, вы получите за бесплатно довесок в несколько сотен килобайт абсолютно ненужного вам кода. В чем причина? Линковщик не может определить, какие виртуальные методы будут вызваны, так как теоретически все они могут быть вызваны косвенно. По этому достаточно одного «упоминания» класса, как весь код его виртуальных методов (а также виртуальных методов других классов, на которые он ссылается) будет влинкован в ваше приложение, тут же. Во избежание подобной проблемы модуль Forms надо было бы разделить на две части: в одной — только определения, а в другой — создания экземпляров, выше указанных, глобальных переменных.

Я столкнулся с описанной проблемой при написании серверного приложения без GUI, которое взаимодействует с базой данных. Где-то в недрах DBxxx компонент есть ссылка на модуль Forms. Эта «особенность» была замечена в Delphi 5, скорее всего эта проблема имела место и в предыдущих версиях. Справедливости ради надо отметить, что в Delphi 7 эта особенность устранена.

Циклические ссылки модулей и «осведомленность» сущностей

Технически, Object Pascal позволят создать циклические ссылки между модулями. Их наличие в программе или библиотеке свидетельствует о не очень удачной декомпозиции (IMHO). Негативными последствиями их использования есть:

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

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

Предположим, мы разрабатываем приложение, в котором должны производиться некоторые вычисления и процесс этих вычислений, должен представляться пользователю. Грубо говоря, в данной ситуации мы имеем две сущности: «интерфейс пользователя» и «вычислительный механизм». Они оба должны взаимодействовать друг с другом: «интерфейс пользователя» должен настраивать «вычислитель» и запускать его на выполнение, а «вычислитель» должен выдавать информацию о ходе расчетов. Можно предложить следующий вариант решения:

Модули интерфейса пользователя и вычислителя работают непосредственно друг с другом, т.е. «интерфейс пользователя» вызываем методы «вычислителя» и наоборот. Все будет работать великолепно, пока не окажется, что «вычислитель» необходимо использовать в другой задаче с другим интерфейсом пользователя (или без оного вообще). Обойти данную проблему можно, если в «вычислителе» задачу общения с «внешним миром» (в данном случае — интерфейс пользователя) возложить на функции обратного вызова (callback functions). При таком подходе, заинтересованная сторона регистрируется у «вычислителя», и он будет вызывать ее функции, не подозревая, с кем имеет дело.

Анализ:
В первом случае мы имели двунаправленную «осведомленность» сущностей друг о друге, что привело к проблемам с повторным использованием кода «вычислителя». Во втором случае у нас только однонаправленная «осведомленность» сущностей, т.е. «интерфейс пользователя» знает о вычислителе, но не наоборот. Если необходимо повторно использовать код «интерфейса пользователя», можно пойти дальше — сущность «приложение» знает о существовании сущностей «интерфейс пользователя» и «вычислитель», но последние ничего не знают друг о друге и взаимодействуют через сущность «приложение», исполняющую роль посредника.

Исключения в обработчике события OnTimer

При написания обработчика события OnTimer компонента TTimer необходимо учитывать, что возникновение исключения в нем для обычного Delphi приложения без специализированной обработки исключений приведет к выскакиванию диалога с сообщением об ошибке. Но это не останавливает работу таймера. И если причина возникновения исключения устойчива, то скоро вы увидите следующее сообщения и т.д., пока у системы не закончатся какие-нибудь ресурсы.

Решить данную проблему можно несколькими способами:

  • Обрабатывать исключения непосредственно в обработчике события
  • Использовать централизованный обработчик исключений, который фиксирует их в протоколе или журнале, но не выдает никаких сообщений об ошибке.

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

Заключение

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

Инициализируются ли переменные delphi значением по умолчанию?

Я новичок в Delphi, и я запускал некоторые тесты, чтобы увидеть, какие переменные объекта и переменные стека инициализируются по умолчанию:

Это поведение, к которому я привык с других языков, но мне интересно, безопасно ли полагаться на него в Delphi? Например, мне интересно, может ли это зависеть от параметра компилятора или, возможно, работать по-разному на разных машинах. Нормально ли полагаться на инициализированные значения по умолчанию для объектов или вы явно задаете все переменные экземпляра в конструкторе?

что касается переменных стека (уровня процедуры), мои тесты показывают, что unitialized booleans истинны, unitialized integers-2129993264, а uninialized objects-это просто недопустимые указатели (т. е. не nil). Я предполагаю, что норма-всегда устанавливать переменные уровня процедуры перед доступом к ним?

9 ответов

Да, это задокументированное поведение:

поля объекта всегда инициализируются до 0, 0.0,», False, nil или что-либо еще.

глобальные переменные всегда инициализируются в 0 и т. д.;

локальные подсчитанные ссылки * переменные всегда инициализируются до нуля или «;

локальные не подсчитанные ссылки * переменные неинициализированы, поэтому вам нужно назначить значение, прежде чем вы сможете использовать их.

Я помню Бэрри Келли где-то написал определение для «reference-counted», но больше не может его найти, поэтому это должно сделать тем временем:

reference-counted == которые сами являются ссылочными, или прямо или косвенно содержат поля (для записей) или элементы (для массивы), которые подсчитываются как ссылки: string, variant, interface или динамический массив или статический массив содержащие такие типы.

  • record самого по себе недостаточно, чтобы стать reference-counted
  • я еще не пробовал это с дженериками

глобальные переменные, которые не имеют явного инициализатора, выделяются в разделе BSS в исполняемом файле. Они фактически не занимают места в EXE; раздел BSS-это специальный раздел, который ОС выделяет и очищает до нуля. В других операционных системах существуют аналогичные механизмы.

вы можете зависеть от нулевой инициализации глобальных переменных.

поля класса по умолчанию равны нулю. Это задокументировано, так что вы можете положиться на него. Локальный стек varaiables не определены, если строка или интерфейс, они установлены в ноль.

как Примечание (поскольку вы новичок в Delphi): глобальные переменные могут быть инициализированы непосредственно при их объявлении:

вот цитата из Ray Lischners Delphi в двух словах Глава 2

» когда Delphi сначала создает объект, все поля начинаются пустыми, то есть указатели инициализируются в ноль, строки и динамические массивы пусты, числа имеют значение ноль, логические поля ложны, а варианты установлены в неназначенные. (Подробнее см. NewInstance и InitInstance в главе 5.)»

Это правда, что переменные local-in-scope должны будьте инициализированы. Я бы рассматривал комментарий выше, что «глобальные переменные инициализируются» как сомнительные, пока не будет предоставлена ссылка — я не верю в это.

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

глобальные переменные и данные экземпляра объекта (поля) всегда инициализируется нулями. Локальные переменные в процедурах и методах не инициализируются в Win32 Delphi; их содержимое не определено, пока им не будет присвоено значение в коде.

из файла справки Delphi 2007:

ms-help: / / borland.bds5 / devcommon / variables_xml.HTML-код

«Если вы явно не инициализируете глобальную переменную, компилятор инициализирует его до 0.»

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

У меня есть одна небольшая проблема с ответами. Delphi нули из пространства памяти глобалов и вновь созданных объектов. Пока это нормально означает, что они инициализированы есть один случай, когда они не являются: перечисляемые типы с определенными значениями. Что, если ноль не является законной ценностью??

TwoDigitYearCenturyWindow — Переменная Delphi

Чему будут равны при запуске программы переменные?
В хелпе, в пункте initializing variables написано: «If you don’t explicitly initialize a global variable, compiler initialize it in 0.»(перевод: «Если вы явно не инициализируете глобальную переменную, компилятор инициализирует её как 0»)
Т.е насколько я понимаю все переменные должны быть изначально нулями и переменная N тоже. Проэкпериментировав, я увидел, что оно может некоторые переменные не обнулять. Причём массивы обнуляло всегда, на 6 делфи на 2 компах не обнуляло только 1 переменную (как назно ту, что как раз надо было бы), на 7 делфи обнуляло всё, но при запуске именно экзешника выдавало ошибку. В фри паскале точно обнуляет всё нормально, и программа корректно работает.

У меня возникла такая ошибка (т.е я писал программу, забыл обнулить переменную n в коде, компилятор сам обнулял, и у меня программа работало нормально, в результате я не заметил этого момента). На другом копьютере при программной компиляции моего ПАСА и запуска Екзешника программа не работала (получался Runtime error при обращении к a[n+1]).

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

Блог GunSmoker-а (переводы)

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

вторник, 1 сентября 2009 г.

Работа с указателями

Pointers are like jumps, leading wildly from one part of the data structure to another. Their introduction into high-level languages has been a step backwards from which we may never recover. — Charles Hoare

Указатели, вероятно, являются самыми плохо понимаемыми и страшными типами данных. Поэтому много программистов пытаются их избегать.

Но указатели очень важны. Даже в языках, которые явно не поддерживают указатели, или в которых использование указателей затруднено, указатели являются важными факторами «под капотом» языка. Я считаю, что для программиста понимание указателей является весьма важной вещью. Существует несколько подходов к пониманию указателей.

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

Содержание

Память (Memory)

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

Указатели — это переменные, которые указывают на другие переменные. Чтобы объяснить это, необходимо понять концепцию адреса памяти и концепцию переменной. Для этого я сначала грубо опишу компьютерную память (*).

Кратко говоря, компьютерная память может рассматриваться как один очень-очень длинный ряд байтов. Байт — это единица измерения количества информации, в стандартном виде байт считается равным восьми битам и может хранить одно из 256 различных значений (от 0 до 255 ). В текущей 32 -х битной версии Delphi на память можно смотреть (за редкими исключениями) как на массив байт максимальным размером в 2 Гб ( 2^31 байт). Что именно содержат эти байты — зависит от того, как интерпретировать их содержимое, т.е. от того, как их используют. Значение 97 может означать число 97 , или же букву ‘a’ . Если вы рассматриваете вместе несколько байт, то вы можете хранить и большие значения. Например, в 2 -х байтах вы можете хранить одно из 256*256 = 65536 различных значений и т.д.

Чтобы обратиться к конкретному байту в памяти (адресовать его), можно присвоить каждому байту номер, пронумеровав их числами от 0 и до 2147483647 (в предположении, что у вас есть 2 Гб — а даже если у вас их нет, то Windows попытается сделать так, чтобы вам казалось, что они у вас есть). Индекс байта в этом огромном массиве и называется его адресом.

Кто-то может сказать: байт — это наименьший кусок памяти, который можно адресовать.

В действительности, память устроена сложнее. Например, существуют компьютеры, байт в которых не равен 8-ми битам, что означает, что они могут содержать больше или меньше 256 значений. Впрочем, для тех машин, на которых работает Delphi для Win32, байт всегда равен 8-ми битам. Память управляется и железом и программами, так что не вся видимая вам память может существовать (менеджеры памяти скрывают это от вас, выгружая память частями на жёсткий диск), но для целей этой статьи мы можем смотреть на память как на один большой блок памяти, разделённый для использования несколькими программами (прим.пер.: для более подробного ознакомления с архитектурой памяти в Windows — рекомендую эту короткую серию статей).

Переменные (Variables)

Переменная — это место из одного или нескольких байт в этом гигантском «массиве», из которого (места) вы можете читать или писать. Это место идентифицируется по имени, но также характеризуется своим типом, значением и адресом.

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

Тип переменной определяет, как будет использоваться место в памяти. Тип определяет размер, т.е. сколько байт занимает место в памяти, а также структуру памяти. Например, на следующей диаграмме показан кусок памяти в 4 байта, начинающихся по адресу $00012344 . Байты содержат значения $4D , $65 , $6D и $00 , соответственно.

Заметьте, что хотя я использовал адрес типа $00012344 , в большинстве диаграмм это просто числа, взятые «от балды», которые просто помогают отличить одно место в памяти от другого. Они не отражают настоящие адреса памяти, т.к. эти адреса зависят от множества факторов и их нельзя предсказать заранее.

Тип определяет, как используются эти байты. Например, это может быть число типа Integer со значением 7169357 (что есть $006D654D ), или же массив символов array[0..3] of AnsiChar , формирующий C-строку (т.е. PChar ) ‘Mem’ , или что-то совершенно иное, как множество, массив из отдельных байт, небольшая запись, Single , часть Double и т.д. Другими словами, смысл куска памяти переменной не известен, если только вы не знаете, какого типа (или типов) эта переменная (или переменные).

Адрес переменной — это адрес первого байта места хранения. Например, в диаграмме выше, в предположении, что у нас переменная типа Integer , её адрес будет $00012344 .

Неинициализированные переменные

Память для переменных может использоваться многократно. Память для переменных обычно резервируется только на время, пока программа может обращаться к ним. Т.е. локальные переменные в процедуре или функции (я буду называть их одним словом: «подпрограммы») доступны только в то время, пока выполняется код подпрограммы. Поля объекта (которые тоже переменные) также доступны только пока объект «существует».

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

Первое отображаемое значение (значение неинициализированной переменной A ) зависит от уже существующего содержания памяти, зарезервированной под A . В моём случае каждый раз значение было 2147319808 ( $7FFD8000 ), но это число может быть совершенно другим на вашей машине. Значение не определено, потому что переменная не была инициализирована. В более сложных программах, особенно (но не только) с участием указателей, это частая причина для вылета программы или вывода неверных результатов. Присваивание инициализирует переменную A значением 12345 ( $00003039 ), что и будет вторым выведенным значением.

Указатели (Pointers)

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

Например, пусть у нас есть следующие объявления и инициализация:

Предположим, что это дало нам такую компоновку памяти:

Теперь, после выполнения этого кода (предполагая что P — это указатель):

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

Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end. — Henry Spencer

nil — это специальное значение указателя. Оно может быть присвоено любому указателю. nil означает пустой указатель (nil — это сокращение от Лат. nihil, что означает ничего или ноль; кое-кто расшифровывает NIL как аббревиатуру от Not In Listне в списке). Это означает, что указатель был определён (инициализирован, присвоен), но вы не должны пытаться получить значение, на которое он указывает (в языке C, nil называется NULL — см. цитату в начале секции).

Nil никогда не указывает на допустимую память, но т.к. это вполне конкретное значение, то подпрограммы могут сравнивать указатели с этим значением (например, используя функцию Assigned() ). Нельзя проверить, является ли любое другое (не- nil ) значение указателя допустимым. Мусорный или неинициализированный указатель ничем не отличается от допустимого указателя (см. ниже). Не существует способа их отличать друг от друга. Программная логика всегда должна гарантировать, что указатель либо допустим, либо равен nil (**).

В Delphi, nil имеет значение 0 (прим. пер.: т.е. nil = Pointer(0) или 0 = Integer(nil)) — т.е. он указывает на самый первый байт в памяти. Очевидно, что этот байт никогда не будет доступен для Delphi кода. Но обычно вы не должны рассчитывать на то, что указатель nil будет равен 0 , если только вы точно не знаете, что вы делаете. Числовое значение nil может быть изменено в следующих версиях Delphi по какой-то причине (***).

Типизированные указатели

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

Предположим, что у нас есть ещё один указатель, Q :

Q типа ^Integer , что читается как «указатель на Integer» (мне сказали, что ^Integer расшифровывается как ↑Integer ). Это означает, что Q — это не Integer , но вместо этого указывает на память, которая может быть использована как Integer . Если вы присвоите адрес J в Q , используя оператор взятия адреса @ или эквивалентную функциональность псевдофункции Addr ,

то тогда Q будет указывать на место по адресу $00012348 ( Q ссылается (references) на место памяти, занимаемое J ). Но поскольку Q является типизированным указателем, то компилятор будет трактовать память, на которую указывает Q , как число типа Integer . Integer является базовым типом Q .

Хотя вы навряд ли увидите псевдофункцию Addr в реальном коде, она полностью эквивалентна @ . Однако у @ есть недостаток: если его применять к сложному выражению, то не всегда ясно, указатель чего берётся. Addr же, используя синтаксис функции, получается намного более читабельным, поскольку целевое выражение заключается в скобки:

Прим. пер.: поэтому неплохо использовать скобки вместе с @, хотя это и не обязательно:

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

Это записывает число 98765 ( $000181CD ) в место памяти, занимаемое переменной J . Но чтобы получить доступ к этой памяти через указатель Q , вы должны работать косвенно, используя оператор ^ :

Это называется разыменованием указателя. Вы должны следовать по воображаемой «стрелочке» до места, на которое указывает Q (другими словами, до Integer по адресу $00012348 ) и сохранить там значение.

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

Обычно полезно определять типы для используемых в программе указателей. Например, вы не можете использовать ^Integer при указании типа параметра подпрограммы, так что вам придётся объявить новый тип:

Фактически, тип PInteger и некоторые другие часто используемые типы уже определены в библиотеке Delphi (модули System , SysUtils и Types ). Начинать имя типов указателей с заглавной буквы P и следующим за ней именем типа, на переменную которого указывает указатель, является традицией, рекомендованной к выполнению. Если базовый тип указателя начинается с заглавной T, то T обычно опускается. Например:

Анонимные переменные

(прим. пер.: не путать с захваченными переменными в анонимных методах)

В предыдущих примерах переменные объявлялись только там, где они были необходимы. Иногда вы не знаете, понадобится ли вам переменная или как много переменных. Используя указатели, вы можете создавать так называемые анонимные переменные. Вы можете попросить библиотеку языка выделить вам кусок памяти и вернуть указатель на него, используя псевдофункцию New() :

New() — это псевдофункция компилятора. Она резервирует память для базового типа PI и записывает адрес на эту память в указатель PI . У самой переменной здесь нет имени (т.е. она анонимная) — имя есть только у указателя на переменную. Получить доступ к такой переменной можно только используя указатель. Теперь вы можете присваивать ей значения, передавать её в подпрограммы, избавиться от неё, когда она станет вам не нужна, используя вызов Dispose ( PI ):

Вместо использования New и Dispose вы можете спуститься на уровень пониже и использовать GetMem и FreeMem . Но подпрограммы New и Dispose имеют несколько преимуществ: они осведомлены о типе указателя (прим. пер.: поэтому автоматически определяют размер памяти), а также инициализируют и освобождают содержимое участка памяти, если это необходимо. Так что рекомендуется всегда использовать New и Dispose , вместо GetMem и FreeMem , там, где это возможно.

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

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

Конечно же, вам нужно будет потом вызывать Dispose() для каждого значения, когда список не будет больше нужен.

Используя анонимные переменные, легко показать, что типизированные указатели могут определять, как используется память. Два указателя разных типов, указывающие на одно и то же место в памяти, будут показывать разные значения:

PI заполняет память значением $006D654D ( 7169357 ). На диаграмме (напомню, что все адреса были придуманы мной):

PC указывает на ту же самую память (поскольку базовые типы указателей несовместимы, вы не можете просто так присвоить один указатель другому — вам нужно использовать преобразование типов). Но PC является указателем на AnsiChar , так что если вы берёте PC^ , то вы получаете AnsiChar — один символ с ASCII значением $4D или ‘M’ .

Вообще-то, PC — это особый случай, поскольку тип PAnsiChar , хотя он формально указывает на символ AnsiChar , трактуется специальным образом, немного иначе, чем остальные типы указателей. Я объясняю это в другой статье. PC , если он не разыменовывается, обычно трактуется как указатель на текст, заканчивающийся нулевым символом #0 , поэтому Writeln(PC) покажет текст, сформированный байтами $4D $65 $6D $00 , т.е. ‘Mem’ .

Когда мне нужно подумать об указателях, и особенно о сложных ситуациях с ними, я обычно беру бумажку и ручку и рисую диаграммки типа тех, что вы видели выше. Я также даю переменным выдуманные адреса, если это нужно (не обязательно использовать все 32 бита, адреса типа 30000 , 40000 , 40004 , 40008 и 50000 тоже вполне подойдут).

Плохие указатели

Если использовать их правильно, указатели являются очень мощным и гибким средством. Но если вы сделаете ошибку — они могут стать большой проблемой. Это ещё одна причина, почему некоторые люди стараются избегать указателей. Некоторые из частых ошибок перечислены ниже.

Неинициализированные указатели

Указатели являются переменными, и как любые другие переменные они должны быть инициализированы перед использованием, либо присваиванием им другого указателя, либо используя подпрограммы типа New или GetMem , например:

Если вы просто объявите, скажем, указатель типа PInteger , но не проинициализируете его, то указатель, вероятно, будет содержать какие-то случайные байты, т.е. он фактически будет указывать на случайное место в памяти.

Если вы попробуете получить доступ к памяти по такому указателю, будут происходить плохие вещи. Если эта память не будет зарезервирована вашим приложением, то вы получите исключение Access Violation — вылет программы (program crash). Но если эта память будет частью вашей программы, то вы можете переписать данные, которые не должны меняться. Если эти данные используются в другой части вашей программы, то чуть позже, получаемые результаты выполнения вашей программы будут ошибочны. Такие ошибки чрезвычайно тяжело искать.

Поэтому, если вы вдруг увидели сообщение об ошибке типа Access Violation или другое сообщение об очевидном вылете (типа Invalid Pointer), вы, на самом деле, должны быть благодарны (ну если только эта ошибка не испортила ваш жёсткий диск ;) . Ваша программа вылетела, да, это плохо, но такие ошибки хотя бы легко отлаживать и исправлять. Но если ваша программа просто втихую выводит неправильные данные, проблема может быть намного хуже, и вы даже можете не заметить её, пока не станет слишком поздно. Вот почему вы должны использовать указатели с особым вниманием. Всегда дотошно проверяйте код на неинициализированные указатели.

Мусорные указатели

Мусорные указатели (stale pointers) — это указатели, которые когда-то были допустимыми, но теперь уже нет. Это может произойти, если память, на которую указывает указатель, была освобождена и/или повторно использована.

Один из частых случаев мусорного указателя — это когда память освобождается, но сам указатель ещё используется после этого. Для предотвращения этого некоторые программисты всегда устанавливают указатели в nil , после освобождения памяти. Они также проверяют на nil , прежде чем пытаться получить доступ к памяти. Другими словами, nil используется как своего рода флаг, отметка о недопустимости указателя. Это один из подходов, но не всегда самый лучший.

Другая частая ошибка: иметь более чем один указатель на один и тот же кусок памяти. Вы можете освободить память по одному указателю и даже об- nil -ить его, но другой указатель всё также будет содержать старое значение, указывающее на уже освобождённую память. Если вам повезёт, вы получите ошибку типа «Access Violation» или «Invalid pointer», но реальное поведение часто неопределено.

Третья, похожая проблема, — это указание на непостоянные (volatile) данные, т.е. данные, которые могут исчезнуть в любой момент. Например, частой грубой ошибкой является функция, возвращающая указатель на свои локальные переменные. Ведь как только мы выходим из подпрограммы, её локальные данные больше не существуют, а возвращённый вызывающей стороне указатель оказывается указывающим в никуда. Классический пример:

V будет помещена в процессорном стеке. Это специальная часть памяти, которая используется для хранения локальных переменных и параметров вызываемых процедур, а также содержит важные данные типа адресов возврата для каждой вызванной подпрограммы. Результат функции указывает на V ( PChar может указывать прямо на массив — см. статью, что я упоминал). Как только VersionData завершает выполнение, стек изменяется следующей вызванной подпрограммой, так что данные, вычисленные в CalculateVersion оказываются перезаписанными, а указатель указывает на это новое содержимое по всё тому же старому адресу.

Похожая проблема: указывание PChar на строку String , но это также обсуждалось в статье про PChar . Или использование указателя на элемент динамического массива (динамические массивы могут перемещаться по памяти, если их размер меняется вызовами SetLength ) — вместо этого надо использовать просто индекс.

Использование неверного базового типа

Тот факт, что указатели могут указывать на любое место в памяти и что два указателя разных типов могут указывать на одно и то же место, фактически означает, что вы можете обращаться к одной памяти разными способами. Используя указатель на Byte ( ^Byte ), вы можете изменять индивидуальные байты Integer -а или любого другого типа.

Но вы также можете что-то ошибочно записать или прочитать. Например, если вы получаете доступ к месту, которое хранит только Byte , с помощью указателя на Integer , вы можете записать 4 байта, среди которых только 1 байт является допустимым, а остальные три — просто смежные с ним, поскольку компилятор будет трактовать эти 4 байта подряд, как одно число типа Integer . Также, если вы читаете что-то с места расположения байта, вы можете прочитать слишком много:

J будет иметь правильное значение, потому что компилятор добавит код расширения одного байта до ( 4 -х байтового) Integer с помощью обнуления старшей части Integer -а. Но I не будет иметь верного значения. Она будет содержать наш байт и ещё 3 каких-то байта, которые следуют за B , формируя этим некоторое неопределённое (мусорное) значение.

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

Владельцы и забытая память (Owners and orphans)

Указатели не только могут иметь различные базовые типы, но и различную семантику владения. Если вы выделяете память, используя New или GetMem или любую другую более специализированную подпрограмму, вы являетесь владельцем этой памяти. Лучше всего, если вы будете держаться за эту память, заныкав указатель на неё в надёжное место. Указатель — это ваш единственный способ получить доступ к этой памяти, и если он будет утерян — вы не сможете ни прочитать данные, ни освободить их. Одно из общих правил: тот, кто выделяет память, обязан её и освободить, так что это ваша обязанность. Хорошо спроектированные программы всегда следуют этому правилу.

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

Частая ошибка: использовать указатель для выделения памяти, а затем снова использовать этот же указатель для выделения другой памяти или присвоить ему другой указатель. Указатель, который содержал адрес старого выделенного блока памяти, начинает указывать на новый блок памяти, а старый адрес оказывается потерян навсегда. Не существует никакого разумного способа получить местоположение первого выделенного блока памяти. Память оказывается забыта. Никто не может получить к ней доступ и никто не может её очистить. Это приводит к образованию так называемых утечек памяти.

Вот простой пример, взятый (с разрешения автора) из групп обсуждений Borland:

Ну, вообще-то этот код делает несколько странных вещей. SetLength выделяет байты для bitdata . По какой-то причине программист потом использует GetMem для выделения такого же количества байт для pbBitmap . Но затем он немедленно присваивает pbBitmap другой адрес, что приводит к тому, что только что выделенная GetMem -ом память становится недоступной любому коду (единственным способом добраться до неё была pbBitmap , но он больше на неё не указывает). Другими словами, у нас есть утечка памяти.

Фактически тут есть и другие ошибки. bitdata — это динамический массив, и взятие адреса bitdata берёт адрес указателя на данные массива, вместо адреса самих данных (см. ниже, динамические массивы). Также, поскольку pbBitmap уже является указателем, неправильно использовать на него оператор @ .

Более правильный код выглядел бы так:

Это может показаться тривиальной проблемой, но в более сложном коде легко допустить подобную ошибку.

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

Арифметика указателей и массивы

You can either have software quality or you can have pointer arithmetic, but you cannot have both at the same time. — Bertrand Meyer

Delphi позволяет производить над указателями несколько простых действий. Во-первых, конечно же, вы можете присваивать им значения и сравнивать их на равенство ( if P1 = P2 then ) или неравенство. Но вы также можете увеличивать или уменьшать их, используя псевдофункции Inc и Dec . Приятным моментом при этом является то, что эти функции учитывают размер базового типа указателя. Пример (заметьте, что я руками присвоил указателю фальшивый адрес. Пока я не попытаюсь получить по нему доступ, всё будет в порядке):

Применение этого: это способ предоставить последовательный доступ к элементам массивов. Поскольку (одномерные) массивы содержат последовательные элементы одного типа (т.е. если элемент расположен по адресу N , тогда следующий элемент расположен по адресу N + SizeOf(element) ), то можно использовать этот сценарий для прохода по массиву в цикле (****). Вы начинаете с адреса первого элемента массива, что-то с ним делаете. На следующей итерации цикла вы увеличиваете указатель, так что он начинает указывать на второй элемент и т.д.:

Увеличение указателя немного быстрее, чем умножение индекса на размер элемента и суммирование с базовым адресом на каждой итерации.

В реальности же, разница между обоими способами незначительна, если вообще заметна. Во-первых, у современных процессоров есть специальные способы для адресования при типичных случаях использования индекса. Во-вторых, компилятор всё равно обычно оптимизирует использование индекса в использование указателя, если это возможно. А в примере выше, незначительная разница и вовсе съедается выполнением такой сложной функции как Write().

Как вы можете видеть в примере выше, вы можете легко забыть увеличить указатель в цикле. И вы всё равно должны использовать либо цикл for-to-do , либо какой-то другой способ для организации цикла и условия выхода (с ручным сдвигом и сравнением). Код с использованием указателей обычно труднее поддерживать. И поскольку подход с указателями ничуть не быстрее (ну за исключением очень маленьких циклов), я бы избегал подобного кода в Delphi. Делайте так только если вы прогнали программу под профайлером и считаете, что доступ с указателем действительно будет выигрышным.

Указатели на массивы

Но иногда у вас нет массива, а есть только указатель для доступа к памяти. Функции Windows API часто возвращают данные в буферах, которые содержат массивы записей определённого размера. Но даже в этом случае, вероятно, будет проще преобразованием типа сделать буфер указателем на массив, чем использовать Inc или Dec. Пример:

Delphi 2009

В Delphi 2009 и выше арифметика указателей, помимо типа PChar (и PAnsiChar и PWideChar ), также применима и для других типов указателей. Где и когда это становится возможным — контролируется директивой компилятора $POINTERMATH .

По умолчанию, арифметика указателей выключена, но она может быть включена для участка кода, используя директиву <$POINTERMATH ON>, и выключена после него с помощью <$POINTERMATH OFF>. Директива также может применяться при объявлении типа указателя. При этом арифметика с указателями будет доступна для этого типа в любом коде без предварительной обёртки в директивы $POINTERMATH . Помимо операций инкремента/декремента, новая арифметика указателей в Delphi 2009 также допускает индексацию.

Сейчас, кроме типов PChar , PAnsiChar и PWideChar , единственным типом с поддержкой арифметики указателей по умолчанию является PByte . Но вы можете включить её для любого типа, как например, PInteger . Это значительно упростит код из примера выше:

Так что теперь у нас нет необходимости объявлять специальный тип PIntegerArray . Также вместо PInt[I] можно использовать синтаксис (PInt + I)^ , который приводит к тому же результату.

Кажется, в Delphi 2009 новая арифметика указателей не работает, как ожидается, для указателей на обобщённые типы (generics). С каким бы параметрическим типом вы не инстанциировали тип, индексы не масштабируются на SizeOf(T) , как это ожидается.

Ссылки (References)

Многие типы в Delphi фактически являются указателями, но притворяются простыми типами. Я называю такие типы ссылочными. Примерами таких типов являются динамические массивы, строки, объекты и интерфейсы. Все они являются указателями «под капотом» языка, но с некоторой дополнительной семантикой и часто со скрытым содержанием.

Что отличает ссылки (references) от указателей (pointers):

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

Не путайте такие ссылки со ссылочными типами (reference types) в C++. Они во многом отличаются.

Динамические массивы (dynamic arrays)

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

Динамические массивы (например, array of Integer ) в Delphi работают точно так же. Но библиотека runtime добавляет специальный код, который управляет доступом и присваиваниями. В участке памяти ниже адреса, на который указывает ссылка массива, располагаются служебные данные массива: два поля — число выделенных элементов и счётчик ссылок (reference count).

Если, как на диаграмме выше, N — это адрес в переменной динамического массива, то счётчик ссылок массива лежит по адресу N — 8 , а число выделенных элементов (указатель длины) лежит по адресу N — 4 . Первый элемент массива (сами данные) лежит по адресу N .

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

Доступ к данным динамических массивов с помощью низкоуровневых процедур типа Move или FillChar , или любых других подпрограмм, получающих доступ сразу ко всему массиву, наподобие TStream.Write , часто выполняется неправильно. Для обычного массива (его часто называют также статическим массивом — в противоположность динамическому массиву) переменная массива тождественна его данным. Для динамического массива это не так (см. диаграмму выше). Так что если вы хотите получить доступ к данным массива — вы не должны использовать саму переменную массива, а использовать вместо неё первый элемент массива.

Заметьте, что в примере выше Stream.Write использует нетипизированный var параметр, который также является ссылочным типом. Мы ещё обсудим их ниже.

Многомерные динамические массивы

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

Предположим, что у нас есть такие объявления:

Ну, это выглядит как многомерный динамический массив и, действительно, мы можем обращаться к нему как MyIntegers[0, 3] . Но на самом деле это объявление следует скорее читать как (тут я позволяю себе некоторые вольности с синтаксисом):

Или, чтобы сделать совсем явным, это фактически означает следующее:

Как вы можете видеть, TMultiIntegerArray фактически является одномерным массивом из указателей TSingleIntegerArray. Это означает, что данные TMultiIntegerArray не хранятся в одном большом непрерывном участке памяти по строкам и столбцам, но является, скорее, разорванным массивом, т.е. каждый элемент — просто указатель на другой массив, и каждый из этих подмассивов имеет свой размер. Так что вместо

(что создаст 10 TSingleIntegerArrays по 20 Integer в каждом — т.е. прямоугольный массив), вы можете получить доступ к любому из подмассивов и установить его длину индивидуально:

Строки (Strings)

Should array indices start at 0 or 1? My compromise of 0.5 was rejected without, I thought, proper consideration. — Stan Kelly-Bootle

Строки во многом похожи на динамические массивы. Они также имеют счётчик ссылок, у них похожая внутренняя структура (со счётчиком ссылок и указателем длины ниже самих данных, по тем же смещениям).

Но есть и различия между ними в синтаксисе и семантике. Вы не можете присвоить строке nil — вместо этого вы присваиваете ей » (пустую строку). Строки могут быть константами (со счётчиком ссылок равным -1 , что является специальным указанием для библиотеки runtime: она не будет пытаться увеличивать или уменьшать его или удалять строку). Первый элемент строки имеет индекс 1 , в то время как в массиве это 0 .

Больше информации о строках вы можете узнать из моей статьи о PChar и строках.

Объекты (Objects)

Объекты — или, более точно, экземпляры классов (class instances) — не имеют автоматического управления временем жизни. Их внутренняя структура проста. Каждый экземпляр класса содержит по смещению 0 (т.е. ровно по адресу, на который указывает ссылка переменной объекта) указатель на так называемую таблицу VMT. Она представляет собой таблицу с указателями на каждый виртуальный метод класса. По отрицательным смещениям от начала таблицы лежит много другой полезной информации о классе. Я не буду вдаваться в подробности в этой статье. Для каждого класса есть только одна VMT таблица (а не для каждого объекта!).

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

После указателей на VMT и все таблицы интерфейсов, идут обычные поля объекта, которые хранятся ровно как в записях (record).

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

Ниже следует пример. Предположим, что у нас есть такое объявление:

Тогда раскладка объекта в памяти будет выглядеть примерно вот так:

Интерфейсы (Interfaces)

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

Тогда отношения между интерфейсом, реализующим его объектом и классом, а также методами будет выглядеть вот так:

Переменная MyEditable указывает на указатель IEditable в объекте, созданном TMyClass.Create . Заметим, что MyEditable не указывает на начало объекта, а куда-то в его середину. Далее, указатель MyEditable в объекте указывает на таблицу указателей — по одному указателю на каждый метод в интерфейсе. Каждая из таких записей указывает на кусочек-кода: заглушку (stub). Этот служебный код корректирует указатель Self (который к моменту вызова фактически равен MyEditable ), чтобы он указывал на начало объекта, с помощью отнимания смещения указателя IEditable в объекте от переданного в код указателя, а затем вызывает настоящий метод. Эта заглушка вводится для каждой реализации метода каждого интерфейса, реализуемого классом.

Пример: предположим у нас есть экземпляр по адресу 50000 , а указатель на реализацию IEditable классом TWhatsit лежит по смещению 16 в каждом экземпляре. Тогда переменная MyEditable будет содержать 50016 . Указатель IEditable по адресу 50016 будет указывать на таблицу интерфейса для нашего класса (которая лежит, скажем, по адресу 30000 ), элементы которой указывают на заглушку (скажем, по адресу 60000 ). Заглушка увидит значение 50016 (которое передаётся ей как параметр Self ), вычтет из него смещение 16 и получит 50000 . Это и будет настоящий адрес реализующего интерфейс объекта. Затем заглушка вызывает настоящий метод, передавая ему 50000 в качестве параметра Self .

В диаграмме я, для ясности, опустил заглушки для методов QueryInterface, _AddRef и _Release

Ну, теперь вы видите, почему иногда я люблю использовать бумагу и ручку? ;-)

Ссылочные параметры

Ссылочные параметры часто называются var -параметрами, а также out -параметрами или параметрами, передаваемыми по ссылке.

Ссылочные параметры — это такие параметры подпрограммы, для которых не само значение параметра передаётся и/или возвращается из подпрограммы, а только указатель на него. Пример:

Это более или менее эквивалентно следующему:

Хотя, тут есть несколько различий:

  • Вы не используете синтаксис указателей. Когда вы пишете имя параметра, вы автоматически разыменовываете указатель, т.е. использование имени параметра означает работу со значением, а не с указателем.
  • Ссылочные параметры не могут быть изменены (имеется ввиду сам параметр, а не его значение). Использование имени параметра даёт вам значение параметра — вы не можете изменить указатель или инкрементировать/декрементировать его.
  • Вы должны передать что-то, что имеет адрес — т.е. реальную память, если только вы не используете трюк с приведением типов. Так что, имея ссылочный параметр типа Integer, вы не можете передать, например, 17 , 98765 или Abs(MyInteger) . Передаваемый параметр должен быть переменной (это включает в себя также элементы массива, поля записей и объектов и т.д.).
  • Фактические параметры обязаны быть того же типа, как и параметры в объявлении подпрограммы, т.е. вы не можете передать TEdit , если вы объявили параметр как TObject . Чтобы избежать этого, вы можете использовать только нетипизированные ссылочные параметры (см. ниже). Прим. пер.: как это сделано в, например, FreeAndNil.

Синтаксически, наверное, кажется, что проще использовать ссылочные параметры, чем явные указатели. Но вы должны быть в курсе некоторых особенностей. Чтобы передавать указатели, вы должны увеличить уровень косвенности на единичку. Другими словами, если у вас есть указатель P на Integer , то чтобы передать его, вы должны синтаксически передавать P^ (хотя на деле будет передаваться сам P), например:

Прим. пер.: на самом деле по ссылке также передаются почти любые типы, размер которых больше SizeOf(Pointer), даже если вы не указали var или out. Например, запись из 8 байт будет передаваться по ссылке. Хотя с точки зрения синтаксиса никаких указателей вы опять не увидите (хотя без наличия модификатора const будет сделана локальная копия). Аналогично: если функция возвращает тип, размер которого больше SizeOf(Pointer), то на самом деле в функцию будет передан указатель на память, куда надо записать результат. Т.е. функция

На самом деле трактуется как:

Нетипизированные параметры

Нетипизированные параметры также являются ссылочными параметрами, но они могут быть либо var , либо const либо out . Вы можете передавать в этот параметр любой тип данных, что делает эти виды параметров пригодными для написания подпрограмм, которые принимают почти что угодно, любого размера и типа, но это также означает, что вы должны как-то указать подпрограмме на тип передаваемого аргумента — либо отдельным параметром, либо просто по соглашению, либо же тип параметра значения не имеет.

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

Второй пример — это метод TIntegerList — дочернего класса для TTypedList :

Как вы можете видеть, чтобы использовать указатель, вы должны явно взять адрес параметра, несмотря на тот факт, что фактически параметр внутренне уже является указателем. И опять-таки: уровень косвенности сдвинут на единицу.

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

Я уже упоминал уровень косвенности (level of indirection). Вы уже видели это в действии. Например, если вы хотите инициализировать динамический массив с помощью FillBytes, то вы не передаёте в FillBytes саму переменную массива, а только первый элемент массива. Фактически, вы также можете передать первый элемент статического массива для достижения такого же эффекта. Таким образом, если вы передаёте любой массив в подпрограмму с нетипизированным ссылочным параметром, то, по моему мнению, ваш лучший выбор — всегда передавать первый элемент массива, чтобы не зависеть от типа массива (динамический или статический) (тем более, что вы потом можете изменить объявление — и попробуйте тогда найти ошибку в коде).

Структуры данных

Указатели активно используются в структурах данных типа связанных списков, любых видов деревьев или иных иерархий. Я не буду подробно разбирать их здесь. Достаточно сказать, что такие продвинутые структуры данных не могут существовать без указателей или подобных видов ссылок (например, индекс массива в элементе массива), даже в языках, где формально нет указателей, типа Java (ну, по крайней мере я так думаю). Если вы хотите узнать больше об этих структурах данных — просто возьмите любую книжку по этой теме.

Я только дам простой пример диаграммы структуры данных, которая основательно зависит от указателей — связанный список:

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

Заключение

Я попытался дать вам своё видение указателей. Есть и другие подходы, но, по моему мнению, использование диаграмм (не важно, насколько примитивных) со стрелочками — это неплохой способ разобраться в сложных проблемах с указателями, или для понимания как же связаны вместе интерфейсы, объекты, классы и код. Это не означает, что я начинаю рисовать диаграммы для каждой проблемы. Только для сложных.

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

Я надеюсь, что мне удалось подсказать вам несколько полезных советов. Я уверен, что эта статья далеко не полна, и я бы хотел услышать пожелания по улучшению. Просто пишите мне на e-mail (прим. пер.: на английском, разумеется).

Примечания переводчика:

(*) Подробное описание компьютерной памяти вы можете прочитать у Рихтера и цикле статей Марка Руссиновича «Pushing the Limits of Windows» (часть 1, часть 2, часть 3).

(***) Хотя это и чертовски маловероятно. Писать код без такого предположения довольно тяжело, т.к. получается, что (к примеру) после ZeroMemory мы не получаем nil .

(****) На самом деле, формально такой массив должен быть объявлен с ключевым словом packed.

Функции и процедуры Delphi. Справочник.

Функция StrToDate ( const S: string ): TDateTime;

Описание
Функция преобразовывает строковое представление даты в значение типа TDateTime. Исходная строка должна состоять из двух или трех чисел, отделенных символами-разделителями. Символ, используемый в качестве разделителя, определяется значением глобальной переменной DateSeparator. Порядок следования дня месяца и года определяется глобальной переменной ShortDateFormat. Возможны следующие комбинации: месяц/день/год, день/месяц/год и год/месяц/день. Если строка содержит только два числа, то они воспринимаются, как день и месяц (месяц/день или день/месяц) текущего года. Если значение года использует только два символа, то век (XX или XXI), к которому будет отнесен данный год, будет определяться значением глобальной переменной TwoDigitYearCenturyWindow. Если значение данной переменной равно 0, то все значения года (0..99) будут отнесены к XX в. Если значение TwoDigitYearCenturyWindow>0, то вычисляется «базовый» год («базовый год»:= «текущий год»-TwoDigitYearCenturyWindow), и все года начиная с «базового» будут относиться к XX веку, а все года меньше «базового», к XXI веку.
Если строка содержит недопустимое значение даты, то возникает исключение EConvertError.
Изменяя значение переменной TwoDigitYearCenturyWindow. можно увеличить срок работоспособности приложений, в которых для значения года возможно использовать только две цифры. Наиболее удачным решением является использование четырехразрядной записи лет.

Пример
var
D: TDateTime;
S: string;
begin
D:= StrToDate(‘04.10.1999’);
S:= FormatDateTime(‘d mmm yy г.’,D);
MessageDlg( S, mtInformation, [mbOk], 0);
end;

Понимание объявлений / инициализаций переменных Delphi

Как это относится к Delphi .

Когда переменная объявляется определенного типа, инициализируется ли она объектом OBJECT этого типа? Или переменной нужно присвоить выражение, которое возвращает объект этого типа?

Я родом из Java. Я хочу спросить вот что . В Java, скажем, вы объявляете переменную экземпляра определенного пользователем типа с именем Orange. Который будет выглядеть так:

Переменная _fruit по-прежнему содержит ссылку на ноль до фактического присвоения экземпляра класса Orange, например:

В Delphi, если я объявляю переменную типа TForm, вот так:

Форма инициируется в объекте TForm? Или это все еще ноль?

Я спрашиваю, потому что я получаю сообщение об ошибке при попытке скомпилировать небольшой фрагмент кода, который показан ниже:

Вот основной блок:

и вот второй блок:

Ошибка, которую я получаю, когда пытаюсь скомпилировать, точно такова: «Нарушение прав доступа по адресу 005B17F9 в модуле« Multiple.exe ». Чтение адреса 00000000». Я думал, что это потому, что я как-то не инициализирую переменную SecondForm в модуле Main? Тем не менее, я попытался поместить SecondForm.Create в процедуру ShowForm2Click, и я получаю ту же ошибку. Я получаю эту ошибку, потому что SecondForm не назначен? Это нужно инициализировать? Либо это?

Примечание: я три дня новичок в Delphi. Пожалуйста, будьте внимательны к этому.

3 ответа

также обратите внимание, что unit Second и unit Main оба объявляют глобальную переменную

В случае основного модуля ваш основной модуль будет скрывать переменную SecondForm объявленную в unit Second (даже если она перечислена в предложении uses ). В случае приложения Delphi VCL Forms, если SecondForm является SecondForm автоматического создания, тогда SecondForm объявленный в unit Second , не будет иметь значение nil и фактически уже будет иметь экземпляр TSecondForm созданный и назначенный ему, но это будет недоступен unit Main потому что он объявляет глобал с тем же именем (который, как и все ссылочные типы, будет nil пока вы не сделаете что-то с ним).

Короче говоря, вероятно, лучше не объявлять глобальную SecondForm : TSecondForm в unit Main — называть это чем-то другим или использовать глобальную SecondForm : TSecondForm объявленную в unit Second . Если SecondForm является SecondForm автоматического создания (поведение по умолчанию), то приведенный выше код будет работать, если вы просто не будете повторно объявлять SecondForm в unit Main — если нет, вам все равно придется создавать экземпляр SecondForm .

Формы VCL автоматически создаются автоматически, если не указано иное. Проверьте меню:

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

SecondForm.Create неправильный синтаксис. Конструкторы особенные в Delphi. Вы можете думать о них более или менее как о методах класса. То, как вы их вызываете, выглядит так:

Хотя возможно вызвать конструктор, такой как метод экземпляра ( variable.Create ), это относится к одному конкретному случаю использования и не должно выполняться в общем коде. Причина для вызова конструктора объекта, а не типа, заключается в том, что вы уже находитесь внутри конструктора для этого объекта. (т. е. если у вас есть более одного конструктора на объекте, и один из них вызывает другой, или инициализировать члены класса предка, вызвав конструктор в родительском классе с inherited Create(arguments); )

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

Инициализируются ли переменные delphi значением по умолчанию?

Я новичок в Delphi, и я запускал некоторые тесты, чтобы увидеть, какие переменные объекта и переменные стека инициализируются по умолчанию:

Это поведение, к которому я привык с других языков, но мне интересно, безопасно ли полагаться на него в Delphi? Например, мне интересно, может ли это зависеть от параметра компилятора или, возможно, работать по-разному на разных машинах. Нормально ли полагаться на инициализированные значения по умолчанию для объектов или вы явно задаете все переменные экземпляра в конструкторе?

что касается переменных стека (уровня процедуры), мои тесты показывают, что unitialized booleans истинны, unitialized integers-2129993264, а uninialized objects-это просто недопустимые указатели (т. е. не nil). Я предполагаю, что норма-всегда устанавливать переменные уровня процедуры перед доступом к ним?

9 ответов

Да, это задокументированное поведение:

поля объекта всегда инициализируются до 0, 0.0,», False, nil или что-либо еще.

глобальные переменные всегда инициализируются в 0 и т. д.;

локальные подсчитанные ссылки * переменные всегда инициализируются до нуля или «;

локальные не подсчитанные ссылки * переменные неинициализированы, поэтому вам нужно назначить значение, прежде чем вы сможете использовать их.

Я помню Бэрри Келли где-то написал определение для «reference-counted», но больше не может его найти, поэтому это должно сделать тем временем:

reference-counted == которые сами являются ссылочными, или прямо или косвенно содержат поля (для записей) или элементы (для массивы), которые подсчитываются как ссылки: string, variant, interface или динамический массив или статический массив содержащие такие типы.

  • record самого по себе недостаточно, чтобы стать reference-counted
  • я еще не пробовал это с дженериками

глобальные переменные, которые не имеют явного инициализатора, выделяются в разделе BSS в исполняемом файле. Они фактически не занимают места в EXE; раздел BSS-это специальный раздел, который ОС выделяет и очищает до нуля. В других операционных системах существуют аналогичные механизмы.

вы можете зависеть от нулевой инициализации глобальных переменных.

поля класса по умолчанию равны нулю. Это задокументировано, так что вы можете положиться на него. Локальный стек varaiables не определены, если строка или интерфейс, они установлены в ноль.

как Примечание (поскольку вы новичок в Delphi): глобальные переменные могут быть инициализированы непосредственно при их объявлении:

вот цитата из Ray Lischners Delphi в двух словах Глава 2

» когда Delphi сначала создает объект, все поля начинаются пустыми, то есть указатели инициализируются в ноль, строки и динамические массивы пусты, числа имеют значение ноль, логические поля ложны, а варианты установлены в неназначенные. (Подробнее см. NewInstance и InitInstance в главе 5.)»

Это правда, что переменные local-in-scope должны будьте инициализированы. Я бы рассматривал комментарий выше, что «глобальные переменные инициализируются» как сомнительные, пока не будет предоставлена ссылка — я не верю в это.

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

глобальные переменные и данные экземпляра объекта (поля) всегда инициализируется нулями. Локальные переменные в процедурах и методах не инициализируются в Win32 Delphi; их содержимое не определено, пока им не будет присвоено значение в коде.

из файла справки Delphi 2007:

ms-help: / / borland.bds5 / devcommon / variables_xml.HTML-код

«Если вы явно не инициализируете глобальную переменную, компилятор инициализирует его до 0.»

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

У меня есть одна небольшая проблема с ответами. Delphi нули из пространства памяти глобалов и вновь созданных объектов. Пока это нормально означает, что они инициализированы есть один случай, когда они не являются: перечисляемые типы с определенными значениями. Что, если ноль не является законной ценностью??

Илон Маск рекомендует:  AppendStr - Процедура Delphi
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL