33

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

Jim McKeeth
  • 37,154
  • 23
  • 116
  • 187
  • also see: http://stackoverflow.com/questions/132725/are-delphi-variables-initialized-with-a-value-by-default – Z80 Jan 27 '17 at 10:48

4 Answers4

46

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.

Barry Kelly
  • 39,856
  • 4
  • 99
  • 180
  • 1
    The important thing is remember that "initialized" <> "zero-filled". For example, initialized record with string and integer fields can be not zero-filled. Of course, the string field will be nil, but integer field can be <> 0. – Alex May 14 '09 at 08:36
  • 1
    Yes - Initialize initializes fields and array elements of managed types only. – Barry Kelly May 14 '09 at 09:05
  • 1
    anonymous methods should be added to the list I think – David Heffernan Jun 20 '11 at 19:39
8

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.
Disillusioned
  • 13,820
  • 3
  • 39
  • 75
  • 4
    "programmers who don't check their warnings make the common mistake" - I read somewhere a tip for Delphi programmers and since then I made that tip my motto: "TREAT COMPILER HINTS AS WARNING AND THE WARNINGS AS ERRORS"! – Z80 May 01 '14 at 10:17
  • 2
    @Altar I'd go a step further and follow a zero hints&warnings policy. As soon as you start making any excuses to permit some hints, you create a possibility of missing new ones in amongst hundreds of old ones. Any hint/warning can very easily be fixed/avoided, but it takes a lot of boring work to fix hundreds of them. – Disillusioned May 01 '14 at 16:59
  • @Altar Ok, now I get it... elevate all hints to warnings, and all warnings (including ex-hints) to errors. :) My first impression was that you would accept some hints. – Disillusioned May 02 '14 at 15:48
  • Your post should be marked as Accepted Answer since it responds to the ACTUAL question: "why records SEEMS to be initialized"! – Z80 Jan 27 '17 at 10:53
  • I think I will make "Want To Buy: random initialisation of local variables for debug builds" my logo :) :) – Z80 Jan 27 '17 at 10:55
1

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)
bluish
  • 23,093
  • 23
  • 110
  • 171
Cesar Romero
  • 3,906
  • 1
  • 24
  • 43
  • 1
    I've heard that FillChar can cause troubles if your record contains previously allocated managed members (strings, etc.) or allocated object references, but for new records you are correct. – Jim McKeeth May 14 '09 at 05:12
  • 3
    @Jim: Allen answered a question about that few days ago, he told the FillChar will not affect when it is used only for initialization, but after access a refcount member and then call fillchar, you will get a memory leak. – Cesar Romero May 14 '09 at 15:08
  • 2
    You can call `Finalize` on the record before the `FillChar`. That will make sure that all reference counted fields will be cleaned up first. – dan-gph Jul 07 '15 at 05:31
1

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

Frederik Slijkerman
  • 6,246
  • 23
  • 38