在Delphi中初始化哪些变量?

Jim*_*eth 32 delphi variables initialization delphi-2009

所以我总是听说类字段(基于堆)被初始化,但基于堆栈的变量不是.我还听说记录成员(也是基于堆栈的)也没有初始化.编译器警告局部变量未初始化([DCC警告] W1036变量'x'可能尚未初始化),但不会对记录成员发出警告.所以我决定进行测试.

对于所有记录成员,我总是从Integers得到0而从布尔都得到.

我尝试打开和关闭各种编译器选项(调试,优化等),但没有区别.我的所有记录成员都正在初始化.

我错过了什么?我正在使用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.
Run Code Online (Sandbox Code Playgroud)

输出:

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

Bar*_*lly 45

全局变量是零初始化的.在程序的主begin... end块的上下文中使用的变量可以是特殊情况; 有时它们被视为局部变量,特别是for-loop索引器.但是,在您的示例中,r是一个全局变量,并从可执行文件的.bss部分进行分配,Windows加载程序确保该部分为零填充.

初始化局部变量,就好像它们被传递给Initialize例程一样.该Initialize例程使用运行时类型信息(RTTI)来清零字段(递归 - 如果字段是数组或记录类型)和数组(递归地 - 如果元素类型是数组或记录)托管类型,托管类型是以下之一:

  • AnsiString类型
  • 的UnicodeString
  • WideString的
  • 接口类型(包括方法引用)
  • 动态数组类型
  • 变种

堆中的分配不一定是初始化的; 它取决于用于分配内存的机制.作为实例对象数据的一部分的分配是零填充的TObject.InitInstance.来自AllocMem的分配是零填充,而GetMem分配不是零填充.来自New的分配被初始化,就好像它们被传递给了Initialize.


Dis*_*ned 7

对于所有记录成员,我总是从Integers得到0而从布尔都得到假.

我尝试打开和关闭各种编译器选项(调试,优化等),但没有区别.我的所有记录成员都正在初始化.

我错过了什么?

那么,除了你的测试使用全局的而不是局部变量:您缺少的重要的事情是变量之间的区别巧合似乎被初始化,并且变量actally被初始化.
顺便说一句:这就是为什么不检查警告的程序员犯了一个常见的错误,就是假设他们编写的代码编写得很糟糕,而他们做的几次测试就是正确的.碰巧有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.
Run Code Online (Sandbox Code Playgroud)

使用以下示例输出:

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
Run Code Online (Sandbox Code Playgroud)

笔记

  • 如果您在优化关闭时进行编译,则该示例效果最佳 否则,如果您有优化:
    • 一些本地变量将在CPU寄存器中被操纵.
    • 如果您在单步执行代码时查看CPU堆栈,您将注意到,例如I := I + 1甚至不会修改堆栈.显然,这种变化是无法实现的.
  • 您可以尝试不同的调用约定,以了解它如何影响事物.
  • 您还可以测试将局部变量设置为零而不是递增它们的效果.
  • 这说明了在调用方法之前,如何完全依赖于在堆栈中找到的方式.

  • "不检查警告的程序员犯了常见的错误" - 我在某处读到Delphi程序员的提示,从那时起我就把这个提示作为我的座右铭:"TREAT编译器提示警告,警告为错误"! (4认同)
  • @Altar我会更进一步,遵循零提示和警告政策.一旦你开始找任何借口允许一些提示,你就有可能在数百个旧提示中遗漏新的.任何提示/警告都可以很容易地修复/避免,但修复数百个它需要很多无聊的工作. (2认同)