结果变量是从函数的第一行定义的吗?

时间:2012-04-10 06:22:48

标签: delphi

我需要澄清这个案子。

根据我的测试,Result变量定义为: 第一行中的Boolean = False,Integer = 0,String ='',Object = nil等。 但我从未见过这方面的官方参考。 这也是有意义的,因为这给出了提示。

[DCC警告] Unit1.pas(35):H2077分配给'TForm1.Test'的值从未使用过

function TForm1.Test: Boolean;
begin
  Result := False;
  // Some arbitrary code here

  Result := True;
end;

但是如果我注释掉第一行并且在最后一行之前的某个地方有异常会发生什么?结果=假吗?

如果Result未定义,这意味着我总是必须通过在以后异常的情况下定义Result来启动每个函数。这对我来说毫无意义。

3 个答案:

答案 0 :(得分:15)

正如official Delphi documentation所述,结果是:

  • CPU寄存器(AL / AX / EAX / RAX / EAX:EDX),用于序列值和寄存器中包含的元素;
  • FPU寄存器(st(0)/ XMM1);
  • 作为最新参数传递的附加变量。

一般规则是默认情况下不定义结果值。你必须设置它。编译器会警告您任何缺少的结果集。

  

对于字符串,动态数组,方法指针或变体结果,   效果与函数结果声明为一样   声明参数后面的附加var参数。其他   单词,调用者传递另一个指向a的32位指针   返回函数结果的变量。

准确地说,var参数不仅适用于托管类型,而且仅适用于recordobject结果,这些结果在调用之前在堆栈上分配,因此受制于同样的行为。

也就是说,例如,如果您的结果是string,则会将其作为额外的var参数传递。因此它默认包含调用前的值。它首先是'',然后如果你多次调用该函数,它将包含以前的值。

function GetString: string;
// is compiled as procedure GetString(var result: string);
begin
  if result='' then
    result := 'test' else
    writeln('result=',result);
end;

function GetRaise: string;
// is compiled as procedure GetRaise(var result: string);
begin
  result := 'toto';
  raise Exception.Create('Problem');
end;

var s: string;
begin
  // here s=''
  s := GetString; // called as GetString(s);
  // here s='test'
  s := GetString; // called as GetString(s);
  // will write 'result=test' on the console
  try
    s := GetRaise; // called as GetRaise(s);
  finally
    // here s='toto'
  end;
end;

所以我的建议是:

  • 修复有关未设置结果的所有编译器警告;
  • 不要假设结果字符串被初始化为''(它可能是第一次,但不是第二次调用) - 这是作为var参数传递的,而不是out参数;
  • 任何exception都会照常处理 ,也就是说,正在运行的流程会跳转到下一个finallyexcept块 - 但如果你有结果作为var参数传输,而某些内容已分配给result,则会设置该值;
  • 这不是因为在大多数情况下,未设置的结果序数值(例如布尔值)为0(因为在返回之前的asm代码中EAX = 0),它将是下一次(我见过随机问题)在客户方面,因为这些未设置的结果变量:它工作时间最长,有时代码失败...);
  • 您可以使用exit()语法在较新版本的Delphi上返回值。

答案 1 :(得分:10)

你说:

  

如果Result未定义,这意味着我总是必须通过在以后异常的情况下定义Result来启动每个函数。

如果函数引发异常,则担心函数的返回值是未定义的。但那应该不重要。请考虑以下代码:

x := fn();

如果函数fn的主体引发异常,则返回呼叫站点,不应分配x。从逻辑上讲,上面的单线可以被认为是一个双线:

  1. 致电fn()
  2. 将返回值指定给x
  3. 如果在第1行引发异常,则第2行永远不会发生,并且永远不应将x分配给。

    因此,如果在分配给Result之前引发异常,那么这根本不是问题,因为如果函数引发异常,则永远不应该使用函数的返回值。


    你应该关注的是一个相关的问题。如果您分配到Result然后会引发异常怎么办?您分配给Result的值是否可以在函数外传播?可悲的是答案是肯定的。

    对于许多结果类型(例如,整数,布尔等),如果该函数引发异常,则分配给Result的值不会传播到函数外部。到目前为止,非常好。

    但对于某些结果类型(字符串,动态数组,接口引用,变体等),有一个实现细节会使问题复杂化。返回值作为var参数传递给函数。事实证明,您可以从函数外部初始化返回值。像这样:

    s := 'my string';
    s := fn();
    

    fn的正文开始执行时,Result的值为'my string'。好像fn声明如下:

    procedure fn(var Result: string);
    

    这意味着您可以分配到Result变量并查看呼叫站点的修改,即使您的函数随后引发异常也是如此。没有干净的方法来解决它。您可以做的最好的事情是分配函数中的局部变量,并仅将Result指定为函数的最终行为。

    function fn: string;
    var
      s: string;
    begin
      s := ...
      ... blah blah, maybe raise exception
      Result := s;
    end;
    

    此处缺乏C风格return声明。


    很难准确地说明哪种类型的结果变量容易受到上述问题的影响。最初我认为问题只影响了托管类型。但Arnaud在评论中指出记录和对象也会受到影响。好吧,如果记录或对象是堆栈分配的,那就是这样。如果它是全局变量或堆分配(例如类的成员),则编译器会以不同方式对待它。对于堆分配的记录,使用隐式堆栈分配变量来返回函数结果。仅当函数返回时才将其复制到堆分配变量。因此,在调用站点分配函数结果变量的值会影响函数本身的语义!


    在我看来,这是一个非常明确的例子,说明为什么在语言设计中,函数返回值具有var语义而不是out语义是一个可怕的错误。< / p>

答案 2 :(得分:5)

不,Result没有(保证)默认值。除非你给它一个值,否则它是未定义的。 the documentation暗示了这一点,其中陈述

  

如果函数退出而没有为Result或者赋值   函数名,然后函数的返回值是未定义的。

我刚试过

function test: integer;
begin
  ShowMessage(IntToStr(result));
end;

并收到包含文字35531136的消息。