【问题标题】:Which variables are initialized when in Delphi?在 Delphi 中初始化了哪些变量?
【发布时间】:2010-10-26 01:15:37
【问题描述】:

所以我总是听说类字段(基于堆)已初始化,但基于堆栈的变量没有。我还听说记录成员(也是基于堆栈的)也没有初始化。编译器警告局部变量未初始化([DCC 警告] W1036 变量“x”可能尚未初始化),但不警告记录成员。所以我决定做一个测试。

对于所有记录成员,我总是从 Integers 中得到 0,从 Booleans 中得到 false

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

我错过了什么?我正在使用 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.

输出:

测试记录。 . . . 我1 0 i2 0 i3 0 i4 0 i5 0 小号 布尔值: FALSE FALSE FALSE FALSE FALSE 大批 0 0 0 0 0 0 0 0 0 0 0 完毕 。 . . .

【问题讨论】:

标签: delphi variables initialization delphi-2009


【解决方案1】:

我也有类似的情况,也是这么想的,但是当我在记录之前添加其他使用的变量时,值变成了垃圾,所以在我使用我的记录之前我必须使用初始化

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

【讨论】:

  • 我听说如果您的记录包含以前分配的托管成员(字符串等)或分配的对象引用,FillChar 可能会导致麻烦,但对于新记录,您是正确的。
  • @Jim:Allen前几天回答了一个问题,他告诉FillChar只用于初始化时不会影响,但是在访问一个refcount成员然后调用fillchar之后,你会得到内存泄漏。
  • 您可以在FillChar之前的记录上调用Finalize。这将确保首先清理所有引用计数字段。
【解决方案2】:

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

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

  • AnsiString
  • Unicode 字符串
  • 宽字符串
  • 接口类型(包括方法引用)
  • 动态数组类型
  • 变体

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

【讨论】:

  • 重要的是记住“初始化”“零填充”。例如,带有字符串和整数字段的初始化记录不能被零填充。当然,字符串字段会为零,但整数字段可以是 0.
  • 是 - Initialize 仅初始化托管类型的字段和数组元素。
  • 我认为应该将匿名方法添加到列表中
【解决方案3】:

对于所有记录成员,我总是从整数中得到 0,从布尔值中得到 false。

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

我错过了什么?

好吧,除了您使用全局变量而不是局部变量的测试:您缺少的重要一点是巧合地出现要初始化的变量和实际上是初始化的变量。
顺便说一句:这就是不检查警告的程序员犯下假设的常见错误的原因当他们进行少数测试时,他们写得不好的代码表现正确;碰巧有 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.

使用以下示例输出:

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

备注

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

【讨论】:

  • “不检查警告的程序员会犯常见的错误”——我在某处读到了一篇给 Delphi 程序员的小贴士,从那以后我把这个小贴士作为我的座右铭:“将编译器提示视为警告和警告作为错误”!
  • @Altar 我会更进一步,遵循零提示和警告政策。一旦你开始找借口允许一些提示,你就有可能在数百个旧提示中遗漏新提示。任何提示/警告都可以很容易地修复/避免,但修复数百个提示/警告需要大量无聊的工作。
  • @Altar 好的,现在我明白了...将所有提示提升为警告,并将所有警告(包括前提示)提升为错误。 :) 我的第一印象是你会接受一些提示。
  • 您的帖子应标记为已接受答案,因为它回答了实际问题:“为什么记录似乎要被初始化”!
  • 我想我会将“想要购买:用于调试构建的局部变量的随机初始化”作为我的徽标 :) :)
【解决方案4】:

请注意,在您提供的示例代码中,记录实际上是一个全局变量,因此它将被完全初始化。如果将所有代码移到一个函数中,它将是一个局部变量,因此,根据 Barry Kelly 给出的规则,只有它的字符串字段将被初始化(为 '')。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-20
    • 1970-01-01
    • 2010-10-07
    相关资源
    最近更新 更多