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

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

8 ответов


исторические причины, в основном. FillChar () восходит к Дням Turbo Pascal и использовался для таких целей. Имя действительно немного неправильное, потому что в то время как он говорит FillChar(), это действительно заполнитьбайт(). Причина в том, что последний параметр может принимать символ или байт. Таким образом, FillChar(Foo, SizeOf(Foo), #0) и FillChar(Foo, SizeOf(Foo), 0) эквивалентны. Другим источником путаницы является то, что по состоянию на Delphi 2009 FillChar по-прежнему заполняет только байты хотя Char эквивалентен WideChar. Рассматривая наиболее распространенные способы использования FillChar, чтобы определить, действительно ли большинство людей используют FillChar для заполнения памяти символьными данными или просто используют его для инициализации памяти с некоторым заданным значением байта, мы обнаружили, что именно последний случай доминировал в его использовании, а не первый. С этим мы решили сохранить FillChar byte-centric.

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


если у вас есть 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

Это сэкономит некоторую типизацию, и фокус будет установлен на важных вещах.


FillChar хорошо, чтобы убедиться, что вы не получите никакого мусора в новый, неинициализированный структура (запись, буфер, arrray...).
Он не должен использоваться для "сброса" значений, не зная, что вы сбрасываете.
Не больше, чем просто писать MyObject := nil и ожидая, чтобы избежать утечки памяти.
В particulart все управляемые типы должны быть внимательно наблюдал.
Вижу завершить.

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


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

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


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

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


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


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

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

memset(Destination, 0, Length);

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

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

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

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

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

Бонус Чтение

Windows также содержит SecureZeroMemory


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

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