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