Почему большинство примеров Delphi используют FillChar() для инициализации записей?

StackOverflow https://stackoverflow.com/questions/783825

  •  13-09-2019
  •  | 
  •  

Вопрос

Мне просто интересно, почему большинство примеров Delphi используют FillChar() для инициализации записей.

type
  TFoo = record
    i: Integer;
    s: string; // not safe in record, better use PChar instead
  end;

const
  EmptyFoo: TFoo = (i: 0; s: '');

procedure Test;
var
  Foo: TFoo;
  s2: string;
begin
  Foo := EmptyFoo; // initialize a record

  // Danger code starts
  FillChar(Foo, SizeOf(Foo), #0);
  s2 := Copy("Leak Test", 1, MaxInt); // The refcount of the string buffer = 1
  Foo.s = s2; // The refcount of s2 = 2
  FillChar(Foo, SizeOf(Foo), #0); // The refcount is expected to be 1, but it is still 2
end;
// After exiting the procedure, the string buffer still has 1 reference. This string buffer is regarded as a memory leak.

Здесь (http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html) моя заметка на эту тему.ИМО, лучше объявить константу со значением по умолчанию.

Это было полезно?

Решение

Исторические причины, в основном.Функция FillChar() появилась еще во времена Turbo Pascal и использовалась для таких целей.Название на самом деле немного неправильное, потому что там написано «Заполнить».Чар(), это действительно FillБайт().Причина в том, что последний параметр может принимать символ. или байт.Таким образом, FillChar(Foo, SizeOf(Foo), #0) и FillChar(Foo, SizeOf(Foo), 0) эквивалентны.Другой источник путаницы заключается в том, что начиная с Delphi 2009 FillChar по-прежнему заполняет только байты, хотя Char эквивалентен WideChar.Изучая наиболее распространенные варианты использования FillChar, чтобы определить, используют ли большинство людей FillChar для фактического заполнения памяти символьными данными или просто используют его для инициализации памяти некоторым заданным значением байта, мы обнаружили, что именно последний случай преобладает в его использовании. а не прежний.При этом мы решили оставить FillChar байтоцентричным.

Это правда, что очистка записи с помощью FillChar, содержащей поле, объявленное с использованием одного из «управляемых» типов (строки, вариант, интерфейс, динамические массивы), может быть небезопасным, если не используется в правильном контексте.Однако в приведенном вами примере на самом деле безопасно вызывать FillChar для локально объявленной переменной записи. при условии, что это первое, что вы делаете с записью в этой области действия..Причина в том, что компилятор сгенерировал код для инициализации строкового поля в записи.Это уже установит для строкового поля значение 0 (ноль).Вызов FillChar(Foo, SizeOf(Foo), 0) просто перезапишет всю запись 0 байтами, включая строковое поле, которое уже равно 0.Использование FillChar для переменной записи после строковому полю было присвоено значение, не рекомендуется.Использование метода инициализации констант является очень хорошим решением этой проблемы, поскольку компилятор может сгенерировать правильный код, чтобы гарантировать правильное завершение существующих значений записей во время присваивания.

Другие советы

Если у вас Delphi 2009 и более поздние версии, используйте команду Default вызов для инициализации записи.

Foo := Default(TFoo); 

Видеть Ответ Дэвида на вопрос Как правильно освободить записи, содержащие разные типы в Delphi одновременно?.

Редактировать:

Преимущество использования Default(TSomeType) вызов, заключается в том, что запись завершается до ее очистки.Никаких утечек памяти и явных опасных низкоуровневых вызовов FillChar или ZeroMem.Когда записи сложные, возможно, содержат вложенные записи и т. д., риск ошибок исключается.

Ваш метод инициализации записей можно сделать еще проще:

const EmptyFoo : TFoo = ();
...
Foo := EmptyFoo; // Initialize Foo

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

const PresetFoo : TFoo = (s : 'Non-Default'); // Only s has a non-default value

Это позволит сэкономить время на наборе текста и сосредоточить внимание на важных вещах.

FillChar позволяет убедиться, что в файле нет мусора. новый, неинициализированный структура (запись, буфер, массив...).
Его не следует использовать для «сброса» значений, не зная, что именно вы сбрасываете.
Не более чем просто писать MyObject := nil и рассчитывая избежать утечки памяти.
В частности, необходимо внимательно следить за всеми управляемыми типами.
См. Завершить функция.

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

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

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

Также может быть задан вопрос:

Здесь нет Нулеваяпамять функция в Windows.В заголовочных файлах (winbase.h) это макрос, который в мире C разворачивается и вызывает memset:

memset(Destination, 0, Length);

ZeroMemory — это языково-нейтральный термин, обозначающий «функция вашей платформы, которую можно использовать для обнуления памяти»

Эквивалент Delphi memset является FillChar.

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

Таким образом, во многих отношениях вызов FillChar это микрооптимизация производительности, которой сейчас больше нет Нулеваяпамять встроен:

procedure ZeroMemory(Destination: Pointer; Length: NativeUInt); inline;

Бонусное чтение

Windows также содержит SecureZeroMemory функция.Он делает то же самое, что и Нулеваяпамять.Если он делает то же самое, что и Нулеваяпамять, почему оно существует?

Поскольку некоторые умные компиляторы C/C++ могут распознать, что установка памяти в 0 прежде чем избавиться от памяти, это пустая трата времени - и оптимизируйте вызов Нулеваяпамять.

Я не думаю, что компилятор Delphi настолько умен, как многие другие компиляторы;так что нет необходимости в SecureFillChar.

Традиционно символ представляет собой один байт (больше не актуально для Delphi 2009), поэтому использование fillchar с #0 инициализирует выделенную память так, чтобы она содержала только нули, или байт 0, или интервал 00000000.

Вместо этого вам следует использовать Нулеваяпамять функция для совместимости, которая имеет те же параметры вызова, что и старая функция fillchar.

Этот вопрос имеет более широкий смысл, который был у меня в голове уже много лет.Я тоже был воспитан на использовании FillChar для записей.Это хорошо, потому что мы часто добавляем новые поля в запись (данных) и, конечно же, FillChar( Rec, SizeOf( Rec), #0 ) заботится о таких новых полях.Если мы «сделаем это правильно», нам придется перебирать все поля записи, некоторые из которых являются перечислимыми типами, некоторые из них могут быть самими записями, и результирующий код будет менее читаемым, а также может оказаться ошибочным, если мы не добавим новые. записывайте поля в него старательно.Строковые поля являются обычным явлением, поэтому FillChar сейчас запрещен.Несколько месяцев назад я преобразовал все свои FillChars в записях со строковыми полями в итеративную очистку, но я не был доволен решением и задавался вопросом, есть ли аккуратный способ выполнить «Заполнить» для простых типов (порядковый / float) и «Завершить» для вариантов и строк?

Вот лучший способ инициализировать вещи без использования FillChar:

Запись в записи (невозможно инициализировать)
Как инициализировать статический массив?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top