初始化字符串函数结果?

时间:2010-07-14 21:50:13

标签: delphi compiler-warnings delphi-2007

我刚刚调试了一个函数,该函数返回一个令我担心的字符串。我一直假设返回字符串的函数的隐式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;

7 个答案:

答案 0 :(得分:32)

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

  

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

即。你的

function TMyObject.GenerateInfo: string;

真的是这样:

procedure TMyObject.GenerateInfo(var Result: string);

注意“ var ”前缀(不是“ out ”,如您所料!)。

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

查看并投票赞成this request

答案 1 :(得分:4)

我们之前遇到过这种情况,我想可能早在Delphi 6或7中。是的,即使编译器没有给你一个警告,你也需要初始化你的字符串Result变量,for正是你遇到的原因。字符串变量 正在初始化 - 它不是作为垃圾引用启动的 - 但是当你期望它时,它似乎没有重新初始化。 / p>

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

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

答案 2 :(得分:2)

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

所以你的结果在第一次通话时是不会发现的,并且可以保留任何东西。如何在编译器中实现它是无关紧要的,并且您可以在不同的编译器中获得不同的结果。

答案 3 :(得分:1)

看起来您的功能应该像这样简化:

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;

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

亚历克斯的回答几乎总是正确的,它解释了为什么我看到了我的奇怪行为,但事实并非如此。

以下,在没有优化的情况下编译,产生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.