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

Мне просто интересно, почему большинство примеров 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) - моя заметка на эту тему. ИМО, лучше объявить константу со значением по умолчанию.

Ответы [ 8 ]

36 голосов
/ 24 апреля 2009

Исторические причины, в основном. FillChar () восходит к дням Турбо Паскаля и использовался для таких целей. Название действительно немного неправильное, потому что, хотя оно говорит Fill Char (), на самом деле это Fill Byte (). Причина в том, что последний параметр может принимать символ или байт. Таким образом, 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 для переменной записи после значение было присвоено строковое поле, не рекомендуется. Использование вашей техники инициализированных констант является очень хорошим решением этой проблемы, потому что компилятор может сгенерировать правильный код, чтобы гарантировать, что существующие значения записей будут правильно завершены во время присваивания.

17 голосов
/ 17 июня 2012

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

Foo := Default(TFoo); 

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

Edit:

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

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

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

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

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

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

9 голосов
/ 24 апреля 2009

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

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

4 голосов
/ 24 апреля 2009

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

есть строки (или любые переменные с пересчетом ссылок).

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

2 голосов
/ 20 августа 2018

Вопрос также может задаваться:

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

memset(Destination, 0, Length);

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

Delphi эквивалент memset равен FillChar.

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

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

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

Бонус Чтение

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

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

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

2 голосов
/ 24 апреля 2009

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

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

1 голос
/ 24 апреля 2009

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

0 голосов
/ 12 июля 2011

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

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

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