Какие переменные инициализируются, когда в Delphi?

29

Итак, я всегда слышал, что поля классов (на основе кучи) были инициализированы, но переменные на основе стека не были. Я также слышал, что элементы записи (также основанные на стеках) также не были инициализированы. Компилятор предупреждает, что локальные переменные не инициализированы ([Предупреждение DCC] W1036 Переменная "x", возможно, не была инициализирована), но не предупреждает о членах записи. Поэтому я решил провести тест.

Я всегда получаю 0 от целых чисел и false от Booleans для всех участников записи.

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

Что мне не хватает? Я на Delphi 2009 Update 2.

program TestInitialization;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TR = Record
  Public
    i1, i2, i3, i4, i5: Integer;
    a: array[0..10] of Integer;
    b1, b2, b3, b4, b5: Boolean;
    s: String;
  End;

var
  r: TR;
  x: Integer;

begin
  try
    WriteLn('Testing record. . . .');
    WriteLn('i1 ',R.i1);
    WriteLn('i2 ',R.i2);
    WriteLn('i3 ',R.i3);
    WriteLn('i4 ',R.i4);
    WriteLn('i5 ',R.i5);

    Writeln( ',R.s);

    Writeln('Booleans: ', R.b1, ' ', R.b2, ' ', R.b3, ' ', R.b4, ' ', R.b5);

    Writeln('Array ');
    for x := 0 to 10 do
      Write(R.a[x], ' ');
    WriteLn;

    WriteLn('Done . . . .');
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
  ReadLn;
end.

Вывод:

Testing record. . . .
i1 0
i2 0
i3 0
i4 0
i5 0
S
Booleans: FALSE FALSE FALSE FALSE FALSE
Array
0 0 0 0 0 0 0 0 0 0 0
Done . . . .

Теги:
variables
initialization
delphi-2009

4 ответа

40
Лучший ответ

Глобальные переменные инициализируются нулем. Переменные, используемые в контексте основного блока begin.. end программы, могут быть особым случаем; иногда они рассматриваются как локальные переменные, особенно for -loop indexers. Однако в вашем примере r является глобальной переменной и выделяется из секции .bss исполняемого файла, которую обеспечивает загрузчик Windows.

Локальные переменные инициализируются так, как если бы они были переданы в процедуру Initialize. В подпрограмме Initialize используется тип-info (RTTI) времени выполнения для нулевых полей (рекурсивно - если поле имеет массив или тип записи) и массивы (рекурсивно - если тип элемента - это массив или запись) управляемый тип, где управляемый тип является одним из:

  • AnsiString
  • UnicodeString
  • WideString
  • тип интерфейса (включая ссылки на методы)
  • тип динамического массива
  • Вариант

Выделения из кучи не обязательно инициализируются; это зависит от того, какой механизм был использован для выделения памяти. Выделения в качестве части данных объекта экземпляра заполняются нулями TObject.InitInstance. Выделения из AllocMem заполняются нулями, а GetMem распределения не заполняются нулями. Выделения из New инициализируются так, как если бы они были переданы в Initialize.

  • 1
    Важно помнить, что «инициализировано» <> «заполнено нулями». Например, инициализированная запись со строковыми и целочисленными полями может быть не заполнена нулями. Конечно, строковое поле будет нулевым, но целочисленное поле может быть <> 0.
  • 1
    Да - инициализация инициализирует поля и элементы массива только управляемых типов.
Показать ещё 1 комментарий
6

Я всегда получаю 0 из целых чисел и false из Booleans для всех элементов записи.

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

Что мне не хватает?

Хорошо, помимо вашего теста, используя глобальные вместо локальных переменных: важно, чтобы вы отсутствовали, - это различие между переменными, которые случайно появляются для инициализации, а переменные, которые действуют. BTW. Именно поэтому программисты, которые не проверяют свои предупреждения, делают распространенную ошибку, предполагая, что их плохо написанный код ведет себя правильно, когда выполняется несколько тестов; имеют 0 и False значения по умолчанию.... Want To Buy: random initialisation of local variables for debug builds.

Рассмотрим следующий вариант тестового кода:

program LocalVarInit;

{$APPTYPE CONSOLE}

procedure DoTest;
var
  I, J, K, L, M, N: Integer;
  S: string;
begin
  Writeln('Test default values');
  Writeln('Numbers: ', I:10, J:10, K:10, L:10, M:10, N:10);
  Writeln('S: ', S);
  I := I + 1;
  J := J + 2;
  K := K + 3;
  L := L + 5;
  M := M + 8;
  N := N + 13;
  S := 'Hello';
  Writeln('Test modified values');
  Writeln('Numbers: ', I:10, J:10, K:10, L:10, M:10, N:10);
  Writeln('S: ', S);
  Writeln('');
  Writeln('');
end;

begin
  DoTest;
  DoTest;
  Readln;
end.

Со следующим выходом:

Test default values
Numbers:    4212344   1638280   4239640   4239632         0         0
S:
Test modified values
Numbers:    4212345   1638282   4239643   4239637         8        13 //Local vars on stack at end of first call to DoTest
S: Hello


Test default values
Numbers:    4212345   1638282   4239643   4239637         8        13 //And the values are still there on the next call
S:
Test modified values
Numbers:    4212346   1638284   4239646   4239642        16        26
S: Hello

Примечания

  • Пример лучше всего подходит для компиляции с оптимизацией. В противном случае, если у вас есть оптимизация:
    • Некоторые локальные вары будут обрабатываться в регистре CPU.
    • И если вы просмотрите стек процессора, пройдя через код, вы заметите, например, что I := I + 1 даже не модифицирует стек. Поэтому очевидно, что изменение не может быть выполнено.
  • Вы можете экспериментировать с различными соглашениями о вызовах, чтобы увидеть, как это влияет на вещи.
  • Вы также можете проверить влияние установки локальных варов на ноль, а не увеличивать их.
  • Это иллюстрирует, как вы полностью зависите от того, что нашло свой путь в стек до того, как был вызван ваш метод.
  • 4
    «Программисты, которые не проверяют свои предупреждения, совершают общую ошибку» - я где-то читал совет для программистов на Delphi, и с тех пор я сделал этот совет своим девизом: «TREAT COMPILER НАПРАВЛЯЕТСЯ КАК ПРЕДУПРЕЖДЕНИЕ, а ПРЕДУПРЕЖДЕНИЯ КАК ОШИБКИ»!
  • 2
    @Altar Я бы пошел дальше и следовал политике нулевых подсказок и предупреждений. Как только вы начинаете делать какие-либо оправдания, чтобы разрешить некоторые подсказки, вы создаете возможность пропустить новые среди сотен старых. Любой намек / предупреждение можно очень легко исправить / избежать, но для исправления сотен из них требуется много скучной работы.
Показать ещё 4 комментария
1

Обратите внимание, что в приведенном вами примере кода запись на самом деле является глобальной переменной, поэтому она будет полностью инициализирована. Если вы переместите весь этот код в функцию, это будет локальная переменная, и поэтому в соответствии с правилами, данными Барри Келли, будет инициализировано только его строковое поле (до '').

  • 0
    Вы правы, я попробовал это после прочтения ответа Барри.
1

У меня такая же ситуация, и я думал то же самое, но когда я добавляю другие переменные, используемые до записи, значения становятся мусором, поэтому, прежде чем использовать свою запись, мне пришлось инициализировать с помощью

FillChar(MyRecord, SizeOf(MyRecord), #0)
  • 1
    Я слышал, что FillChar может вызвать проблемы, если ваша запись содержит ранее выделенные управляемые элементы (строки и т. Д.) Или выделенные ссылки на объекты, но для новых записей вы правы.
  • 3
    @Jim: Аллен ответил на вопрос о том, что несколько дней назад он сказал, что FillChar не повлияет, когда он используется только для инициализации, но после доступа к члену refcount и последующего вызова fillchar вы получите утечку памяти.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню