Типы значений

Типы значений в Delphi: взгляд профессионала
Когда речь заходит о типах значений (value types) в Delphi, многие разработчики ограничиваются знанием того, что Integer, Char, Boolean и Record хранятся на стеке. Однако на практике здесь скрывается масса нюансов, способных как ускорить, так и «уронить» ваше приложение. Как эксперт, я разберу ключевые моменты, на которые обращают внимание специалисты, избегая поверхностных объяснений.
Первое и главное: все простые типы (числа, символы, логические значения) — это типы значений. Но String в Delphi — это ссылочный тип, хотя для новичка это часто становится сюрпризом. Record — единственный составной тип значения, и именно он даёт максимальную гибкость. Однако, если в record есть поля-строки или динамические массивы, управление памятью становится сложнее: само значение лежит на стеке, а его длинные динамические части — в куче.
Распространённые заблуждения
Многие полагают, что все типы значений всегда быстрее ссылочных. Это не так. Например, копирование большого record (100+ полей) может быть значительно медленнее копирования ссылки на класс. Специалисты всегда балансируют между накладными расходами на копирование и временем на управление памятью (сборка мусора в куче).
- Заблуждение №1: «Record всегда хранится только на стеке». На самом деле, если record — поле класса, он хранится в куче вместе с объектом.
- Заблуждение №2: «Передача record по значению всегда безопасна». При передаче в функцию копируется весь экземпляр. Если record содержит array of byte размером 1 МБ — получите неожиданное падение производительности.
- Заблуждение №3: «Типы значений нельзя использовать с интерфейсами». Можно, но record должен поддерживать IInterface через вспомогательные методы — это редкий, но мощный приём.
Неочевидные нюансы производительности
Для опытных коллег: оптимизация на уровне типов значений — это не про «поставьте integer везде». Это про кэш-промахи. Когда record мал (до 64 байт), он эффективно помещается в строку кэша процессора. Но если вы начнёте активно копировать record размером 256 байт в цикле, вы рискуете забить кэш L1. Решение — передавать такие экземпляры через const или var параметры, избегая копирования.
- Совет по размеру: Держите record не более 32–64 байт. Для хранения сотен полей используйте классы с сериализацией или TArray
. - Совет по инициализации: Всегда обнуляйте record через Default(TMyRecord) или оператор := Default(). Это гарантирует нулевые указатели для управляемых полей.
- Совет по наследованию: Record не поддерживает наследование, но используйте class operator (Implicit, Explicit) для перегрузки присваивания — это даёт эмуляцию полиморфизма без накладных расходов классов.
Профессиональные приёмы
Angle рекомендует: для критичных по скорости участков (например, обработка сигналов, графика, парсинг) создавайте record-менеджеры. Пример — TArrayRecord
- Приём «Безопасное копирование»: Если record содержит string, копирование увеличивает счётчик ссылок. Для иммутабельных данных используйте TSyntax с короткими строками (string[32]) — они всегда на стеке.
- Приём «Умный конструктор»: Используйте class function Create для record, которая инициализирует поля и возвращает новый экземпляр. Но помните: это всё равно копирование.
- Приём для больших структур: Если record превышает 256 байт, замените его на TObject с property, но для сериализации оставьте record-обёртку с хранением буфера TBytes.
Где типы значений особенно полезны
Специалисты по высоконагруженным системам используют record в качестве DTO (Data Transfer Object) для передачи между компонентами одной программы. Например, при работе с COM или DLL, record с упакованными полями (packed record) гарантирует совместимость по битам без лишних маршаллинговых затрат. Однако будьте осторожны: packed record может нарушить выравнивание и замедлить доступ на современных процессорах. Рекомендую использовать packed только для совместимости с внешними API, а для внутреннего применения — обычный record с выравниванием по умолчанию.
Напоследок: не пренебрегайте использованием record operators (Add, Subtract, Equal). Это делает код читаемым и позволяет абстрагироваться от низкоуровневой работы с памятью. Например, TPointF в FMX работает именно так — все арифметические операции перегружены, но каждый вызов возвращает новый record на стеке, избегая создания лишних объектов.
Добавлено: 27.04.2026
