Какие переменные инициализируются в Delphi? - PullRequest
32 голосов
/ 14 мая 2009

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

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

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

Что мне не хватает? Я на Delphi 2009 Обновление 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('S ',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 . . . .

Ответы [ 4 ]

45 голосов
/ 14 мая 2009

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

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

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

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

7 голосов
/ 24 апреля 2011

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

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

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

Ну, кроме вашего теста, использующего глобальные вместо локальных переменных: вам не хватает важного различия между переменными, которые по совпадению появляются для инициализации, и переменными, которые фактически инициализированы.
BTW : Это причина, по которой программисты, которые не проверяют свои предупреждения, делают общую ошибку, полагая, что их плохо написанный код ведет себя правильно при выполнении нескольких тестов; бывает 0 и ложные значения по умолчанию .... 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

Примечания

  • Пример работает лучше всего, если вы компилируете с отключенной оптимизацией. В противном случае, если у вас есть оптимизация:
    • Некоторые локальные переменные будут обрабатываться в регистрах ЦП.
    • И если вы просматриваете стек ЦП при пошаговом выполнении кода, вы заметите, например, что I := I + 1 даже не изменяет стек. Поэтому очевидно, что изменение не может быть осуществлено.
  • Вы можете поэкспериментировать с различными соглашениями о вызовах, чтобы увидеть, как это влияет на вещи.
  • Вы также можете проверить эффект установки локальных переменных на ноль вместо их увеличения.
  • Это показывает, как вы полностью зависите от того, что попало в стек * за 1047 * до вызова вашего метода.
1 голос
/ 14 мая 2009

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

1 голос
/ 14 мая 2009

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

FillChar(MyRecord, SizeOf(MyRecord), #0)
...