Подтвердить что ты не робот

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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