Which variables are initialized when in Delphi?

北城余情 提交于 2019-12-17 03:55:19

问题


So I always heard that class fields (heap based) were initialized, but stack based variables were not. I also heard that record members (also being stack based) were also not initialized. The compiler warns that local variables are not initialized ([DCC Warning] W1036 Variable 'x' might not have been initialized), but does not warn for record members. So I decided to run a test.

I always get 0 from Integers and false from Booleans for all record members.

I tried turning various compiler options (debugging, optimizations, etc.) on and off, but there was no difference. All my record members are being initialized.

What am I missing? I am on 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('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.

Output:

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 . . . .


回答1:


Global variables are zero-initialized. Variables used in the context of the main begin..end block of a program can be a special case; sometimes they are treated as local variables, particularly for-loop indexers. However, in your example, r is a global variable and allocated from the .bss section of the executable, which the Windows loader ensures is zero-filled.

Local variables are initialized as if they were passed to the Initialize routine. The Initialize routine uses runtime type-info (RTTI) to zero-out fields (recursively - if a field is of an array or record type) and arrays (recursively - if the element type is an array or a record) of a managed type, where a managed type is one of:

  • AnsiString
  • UnicodeString
  • WideString
  • an interface type (including method references)
  • dynamic array type
  • Variant

Allocations from the heap are not necessarily initialized; it depends on what mechanism was used to allocate memory. Allocations as part of instance object data are zero-filled by TObject.InitInstance. Allocations from AllocMem are zero-filled, while GetMem allocations are not zero-filled. Allocations from New are initialized as if they were passed to Initialize.




回答2:


I always get 0 from Integers and false from Booleans for all record members.

I tried turning various compiler options (debugging, optimizations, etc.) on and off, but there was no difference. All my record members are being initialized.

What am I missing?

Well, apart from your test using global instead of local variables: the important thing that you are missing is the distinction between variables that coincidentally appear to be initialised, and variables that actally are initialised.
BTW: This is the reason programmers who don't check their warnings make the common mistake of assuming their poorly written code is behaving correctly when the few tests they do; happen to have 0 and False defaults.... Want To Buy: random initialisation of local variables for debug builds.

Consider the following variation on your test code:

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.

With the following sample output:

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

Notes

  • The example works best if you compile with optimisation off. Otherwise, if you have optimisation on:
    • Some local vars will be manipulated in CPU registers.
    • And if you view the CPU stack while stepping through the code you'll note for example that I := I + 1 doesn't even modify the stack. So obviously the change cannot be carried through.
  • You could experiment with different calling conventions to see how that affects things.
  • You can also test the effect of setting the local vars to zero instead of incrementing them.
  • This illustrates how you are entirely dependent on what found its way onto the stack before your method was called.



回答3:


Note that in the example code you provided, the record is actually a global variable, so it will be completely initialized. If you move all that code to a function, it will be a local variable, and so, per the rules given by Barry Kelly, only its string field will be initialized (to '').




回答4:


I have a similar situation, and thought the same, but when I add other variables used before the record, the values become garbage, so before I use my record I had to initialize using

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


来源:https://stackoverflow.com/questions/861045/which-variables-are-initialized-when-in-delphi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!