【问题标题】:Value used as both output and (non-reference) input in a chain of methods在一系列方法中用作输出和(非参考)输入的值
【发布时间】:2020-06-19 14:55:44
【问题描述】:

考虑以下方法链接的最小示例,其中一个浮点变量由早期方法设置(使用out 参数),然后(使用const 参数)传递给链中后面的方法:

program ChainedConundrum;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  ValueType = Double;
  TRec = record
    function GetValue(out AOutput: ValueType): TRec;
    procedure ShowValue(const AInput: ValueType);
  end;

function TRec.GetValue(out AOutput: ValueType): TRec;
begin
  AOutput := 394;
  Result := Self;
end;

procedure TRec.ShowValue(const AInput: ValueType);
begin
  Writeln(AInput);
end;

var
  R: TRec;
  Value: ValueType = 713;

begin
  R.GetValue(Value).ShowValue(Value);
  Readln;
end.

我最初希望这会打印浮点数394(以某种格式),但它没有(必然);当我使用 Delphi 10.3.2 的 32 位编译器构建程序时,程序打印出713。使用调试器单步执行程序可确认 Value 的初始预 GetValue 值已传递给 ShowValue

但是,如果我使用 64 位编译器构建它,则会打印 394。同样,如果我将ValueTypeDouble 更改为Int32,我在两个版本中都会得到394Int64 在 64 位中产生 394,在 32 位中产生 713。字符串产生更新的值。类就像记录一样工作。然而,与实例方法相反,类方法总是给我更新的值。当然,放弃方法链接 (R.GetValue(Value); R.ShowValue(Value)) 也是如此。

毫不奇怪,如果我将ShowValueAInput 参数从const(或未修饰的值)参数更改为var 参数,我总是会得到更新后的值。

我的结论是

  1. 不允许在这样的方法链中同时设置和传递变量,或者
  2. 编译器中存在错误。

我的问题是:它是什么?如果不允许,文档在哪里说明了这一点?到目前为止,我还没有找到相关的段落。 (“序列点”这个短语似乎很少出现在 WWW 上的“Delphi”短语附近。)

【问题讨论】:

  • 这对我来说就像一个编译器错误。编译器可能会将Value: ValueType = 713; 声明视为常量。它可能不认为GetValue() 正在使用out 参数,该参数可能会在调用ShowValue() 之前改变Value,因此它认为可以直接传递初始常量值而不是当它真的这样做是不行的。奇怪的是,一切都适用于var,因为out 的处理方式与非托管类型的var 完全相同。你应该report this to Embarcadero 让他们决定。
  • @RemyLebeau:感谢您的回复。我最初的感觉也是,这一定是一个错误。用GetValue 中的var 替换out 不会改变任何东西(仍然是旧的、错误的值)。 (但是从const 更改为ShowValue 中的var 会使所有操作都按预期工作。这很自然,因为@Value 根本不会更改。)
  • 您是否尝试禁用优化?它绝对看起来像一个优化器错误。
  • 所以这个“只”发生在 x64 上的 64 位数据类型上? CompCurrency 应该显示相同的行为。
  • @AmigoJack:我只在 32 位中看到过,Single 也有这种情况。

标签: delphi parameters undefined-behavior method-chaining


【解决方案1】:

在这里或其他地方对此问题发表评论的每个人都同意这“感觉像”或“显然”是编译器错误。

我在 Embarcadero Jira 创建了问题 RSP-29733

转向可能的解决方法,请注意问题似乎是编译器使用了变量的旧 。因此,当 value 更改接近变量的使用时,就会出现问题。

然而,变量的地址并没有改变,所以如果你通过引用而不是值传递变量,问题就消失了。一种方法是在第二次传递值时使用 var 参数,即使您不需要它,甚至在语义上也不需要它。

因此,一种更自然的方法似乎是使用const [Ref] 参数:

procedure ShowValue(const [Ref] AInput: ValueType);

这与未修饰的const 参数具有相同的语义,但强制编译器通过引用传递变量,从而避免了该错误。

【讨论】:

  • 问题似乎是你的常量值是在输入特定代码行时设置的。但是,由于您的代码行包含两个命令,其中一个命令更改分配常量的变量的值,因此您最终会得到旧值,而不是像您期望的那样更新一个值。但是你去把你的两个命令分成单独的行R.GetValue(Value).ShowValue(Value); R.GetValue(Value).ShowValue(Value);然后结果将是你所期望的。
  • @SilverWarior:是的,我在 Q 中说过这句话! “当然,放弃方法链接 (R.GetValue(Value); R.ShowValue(Value)) 也是如此。” :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-27
  • 2018-02-28
  • 1970-01-01
  • 1970-01-01
  • 2018-08-26
  • 2021-10-06
  • 1970-01-01
相关资源
最近更新 更多