【问题标题】:Initialise string function result?初始化字符串函数结果?
【发布时间】:2011-03-16 02:47:06
【问题描述】:

我刚刚调试了一个函数的问题,该函数返回一个让我担心的字符串。我一直假设返回字符串的函数的隐式 Result 变量在函数调用开始时为空,但以下(简化)代码产生了意外结果:

function TMyObject.GenerateInfo: string;

        procedure AppendInfo(const AppendStr: string);
        begin
          if(Result > '') then
            Result := Result + #13;
          Result := Result + AppendStr;
        end;

begin
  if(ACondition) then
    AppendInfo('Some Text');
end;

多次调用该函数导致:

"Some Text"

第一次,

"Some Text"
"Some Text"

第二次,

"Some Text"
"Some Text"
"Some Text"

第三次等

要修复它,我必须初始化结果:

begin
  Result := '';
  if(ACondition) then
    AppendInfo('Some Text');
end;

是否需要初始化字符串函数结果?为什么(技术上)?为什么编译器不会为字符串函数发出警告“W1035 函数 'xxx' 的返回值可能未定义”?我是否需要检查所有代码以确保设置了一个值,因为如果未明确设置结果,则期望函数中的空字符串是不可靠的?

我已经在一个新的测试应用程序中对此进行了测试,结果是一样的。

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
  S: string;
begin
  for i := 1 to 5 do
    S := GenerateInfo;
  ShowMessage(S); // 5 lines!
end;

【问题讨论】:

  • 我在返回接口的函数中遇到了类似的情况。需要初始化的数据类型似乎存在一个普遍问题。我没有尝试过动态数组,但我想这也会显示这个问题。

标签: delphi compiler-warnings delphi-2007


【解决方案1】:

这不是错误,而是“feature”:

对于字符串、动态数组、方法 指针或变体结果, 效果与 if 相同 函数结果被声明为 附加 var 参数 声明的参数。换一种说法, 调用者传递一个额外的 32 位 指向变量的指针 函数结果返回哪个。

即你的

function TMyObject.GenerateInfo: string;

真的是这样吗:

procedure TMyObject.GenerateInfo(var Result: string);

请注意“var”前缀(不是您所期望的“out”!)。

这是 SUCH 不直观的,因此会导致代码中的各种问题。有问题的代码 - 只是此功能结果的一个示例。

查看并为this request投票。

【讨论】:

  • 您不能真诚地将此称为功能。错误特征,也许是:catb.org/jargon/html/M/misfeature.html。但是对于文档的链接肯定是 +1。
  • 我不认为任何后果都是不直观的。我一直认为result 是一个局部变量,而这些(整数、实数、布尔值……)通常需要手动初始化。因此,我对缺少 result := '' 第一行的 OP 代码的反应相当强烈。
  • @Jørn E. Angeltveit 好的,所以您(和 Andreas)是在说“使用前初始化变量”,对吗?对我来说没问题,有道理。现在,尝试将此规则应用于函数,该函数返回带有字符串的记录。我们通常如何初始化记录?为什么,当然是使用 FillChar!你能猜到 FillChar 在这种情况下会做什么吗?由于 Result 实际上是一个 input 变量 - 您的“由良好规则编写的代码”将丢弃现有的字符串指针。不漂亮,对吧?因此,将 Result 视为除它本身之外的任何东西是不安全的:输入/输出参数。
  • P.S.请注意,在 real 局部变量 上使用 FillChar 会起作用。那是因为局部变量没有被初始化。结果不是局部变量,事实上,它已初始化并且可能包含非空值。这当然是出乎意料的。因此,尝试使用 FillChar 失败。
  • P.P.S.当谈到本地记录的“初始化”时,它可能有点模糊。本地记录没有被初始化,因为它们没有用零填充。本地记录在某种意义上是“初始化”的,它们包含托管类型(字符串、接口等)的零字段。希望,你看,我想说什么。
【解决方案2】:

我们之前遇到过这种情况,我想可能早在 Delphi 6 或 7 就已经遇到过。是的,即使编译器不会费心给你警告,你确实需要初始化你的字符串 Result 变量,因为正是你遇到的原因。字符串变量 is 正在初始化——它不是作为垃圾引用开始的——但它似乎并没有在您期望的时候被 重新初始化。

至于为什么会发生……不确定。这是一个错误,因此不一定需要理由。我们只有在循环中重复调用函数时才看到它发生;如果我们在循环之外调用它,它会按预期工作。看起来 caller 正在为 Result 变量分配空间(并在重复调用同一函数时重用它,从而导致错误),而不是 function 分配其自己的字符串(并在每次调用时分配一个新字符串)。

如果您使用的是短字符串,那么调用者会分配缓冲区——这是大值类型的长期行为。但这对 AnsiString 没有意义。也许编译器团队刚在 Delphi 2 中实现长字符串时忘记更改语义。

【讨论】:

【解决方案3】:

这不是错误。根据定义,函数内部没有变量被初始化,包括 Result。

因此,您的 Result 在第一次调用时是未定义的,并且可以保存任何内容。它在编译器中如何实现无关紧要,在不同的编译器中可以得到不同的结果。

【讨论】:

    【解决方案4】:

    看来你的函数应该这样简化:

    function TMyObject.GenerateInfo: string;
    begin
      if(ACondition) then
        Result := 'Some Text'
      else
        Result := '';
    end;
    

    您通常不希望在函数中的赋值右侧使用 Result。

    无论如何,仅出于说明目的,您也可以这样做,但不推荐:

    procedure TForm1.Button1Click(Sender: TObject);
    var
      i: integer;
      S: string;
    begin
      for i := 1 to 5 do
      begin
        S := ''; // Clear before you call
        S := GenerateInfo;
      end;
      ShowMessage(S); // 5 lines!
    end;
    

    【讨论】:

      【解决方案5】:

      这看起来像是 D2007 中的一个错误。我刚刚在 Delphi 2010 中对其进行了测试,并得到了预期的行为。 (1 行而不是 5 行。)

      【讨论】:

      • 我刚刚在 Delphi 2009 中测试过,得到了全部 5 行代码。
      • 这不是错误 - 请参阅我的回复。
      • 但这是 D2010 的错误吗?
      • @Sertac Akyuz:我认为 Mason Wheeler 只是没有测试是正确的。 (我第一次测试代码时犯了同样的错误——没有意识到我必须多次调用GenerateInfo。)
      • 是的,我同意:我认为 Mason Wheeler 没有正确测试它。此问题存在于所有 Delphi 版本中(已在 D3-D2010 中验证)。
      【解决方案6】:

      如果您认为某些字符串的自动管理可以让您的生活更轻松,那您只对了一部分。所有这些事情也是为了使字符串逻辑一致且无副作用。

      在很多地方都有按引用传递、按值传递的字符串,但是所有这些行都期望 VALID 字符串,其中的内存管理计数器是一些有效的,而不是垃圾值。因此,为了保持字符串有效,唯一可以确定的是它们应该在首次引入时进行初始化。例如,对于任何局部变量字符串,这是必要的,因为这是引入字符串的地方。包括 function(): string 在内的所有其他字符串用法(正如 Alexander 正确指出的那样,实际上是 procedure(var Result: string))只期望堆栈上的 valid 字符串,而不是 initialized .这里的有效性来自这样一个事实,即 (var Result: string) 构造表示“我正在等待一个之前明确引入的有效变量”。更新:因为 Result 的实际内容是出乎意料的,但是由于相同的逻辑,如果它是对该函数的唯一调用,在左侧有一个局部变量,那么在这种情况下字符串的空性是有保证的。

      【讨论】:

        【解决方案7】:

        Alex 的回答几乎总是正确的,它回答了为什么我会看到自己的奇怪行为,但这并不是全部。

        以下内容未经优化编译,产生 sTemp 为空字符串的预期结果。如果将函数换成过程调用,则会得到不同的结果。

        实际程序单元的规则似乎有所不同。

        诚然,这是一个极端情况。

        program Project1;
        
        {$APPTYPE CONSOLE}
        
        uses System.SysUtils;
        
          function PointlessFunction: string;
          begin
          end;
        
          procedure PointlessProcedure(var AString: string);
          begin
          end;
        
        var
          sTemp: string;
        begin
          sTemp := '1234';
          sTemp := PointlessFunction;
          //PointlessProcedure(sTemp);
          WriteLn('Result:' + sTemp);
          ReadLn;
        end.
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-12-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-20
          • 1970-01-01
          • 2021-05-10
          相关资源
          最近更新 更多