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

Rol*_*son 9 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;
Run Code Online (Sandbox Code Playgroud)

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

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

Arn*_*hez 15

正如官方Delphi文档所述,结果是:

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

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

对于字符串,动态数组,方法指针或变体结果,效果与在声明的参数之后将函数结果声明为附加var参数的效果相同.换句话说,调用者传递一个额外的32位指针,该指针指向一个返回函数结果的变量.

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

也就是说,例如,如果您的结果是a 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;
Run Code Online (Sandbox Code Playgroud)

所以我的建议是:

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


Dav*_*nan 10

你说:

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

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

x := fn();
Run Code Online (Sandbox Code Playgroud)

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

  1. 呼叫 fn()
  2. 将返回值分配给 x

如果在第1行引发异常,则第2行永远不会发生,x永远不应该分配给.

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


你应该关注的是一个相关的问题.如果您分配给Result抛出一个异常?您分配的值是否可以Result在函数外传播?可悲的是答案是肯定的.

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

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

s := 'my string';
s := fn();
Run Code Online (Sandbox Code Playgroud)

当身体fn开始执行时,Result有价值'my string'.就像fn是这样声明:

procedure fn(var Result: string);
Run Code Online (Sandbox Code Playgroud)

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

function fn: string;
var
  s: string;
begin
  s := ...
  ... blah blah, maybe raise exception
  Result := s;
end;
Run Code Online (Sandbox Code Playgroud)

这里缺乏一种C风格的return陈述.


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


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


And*_*and 5

不,Result没有(保证)默认值.除非你给它一个值,否则它是未定义的.这是由文件暗示,其中说明了这一点

如果函数退出而没有为Result或函数名赋值,则函数的返回值是未定义的.

我刚试过

function test: integer;
begin
  ShowMessage(IntToStr(result));
end;
Run Code Online (Sandbox Code Playgroud)

并得到了一条带有文字的消息35531136.