Delphi 64位项目中in运算符存在问题

7
我正在将 Delphi 项目移植到 64 位,并且在代码中使用了 IN 运算符时遇到了问题。
编译器会报以下错误:

E2010 不兼容的类型:'Integer' 和 'Int64'

我编写了一个示例应用程序来复制此问题。
{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;


Var
 I : Integer;
 L : Array of string;
begin
  try
     if I in [0, High(L)] then


  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  readln;
end.

这段代码在32位系统中可以正常工作,但为什么在Delphi XE2 64位系统中无法编译?我该如何解决这个问题?

*更新 *

看起来我的帖子引起了很多困惑(对此感到抱歉),我要说明的是我正在移植的原始代码更加复杂,我只是写了这段代码作为示例来说明问题。原始代码使用in运算符来检查一个小于255的值是否属于一组所有小于或等于255的值,如下所示:

i in [0,1,3,50,60,70,80,127,High(LArray)] 

你可以考虑使用更快的 cardinal(I)<=cardinal(high(L)),这将只需要一个操作码进行比较,并解决类型转换问题。 - Arnaud Bouchez
你应该非常小心,确保 High(LArray) <= 255 - Kromster
1
@Arnaud,是什么让你认为性能如此重要?为什么你更喜欢使用难以阅读的加密代码,还存在隐藏的细微差错和范围错误?这对我来说毫无意义。看起来像是过早优化的典型案例。 - David Heffernan
1
如果您要以这种方式使用集,请确保您的代码包含一个断言,即 high(L)>=low(Byte)high(L)<=high(Byte),那么您就没问题了。 - David Heffernan
3个回答

8

这段代码无法编译,因为High函数返回的是8字节的值,而不是一个有序值。而In运算符只能在具有有序值的集合中使用。

需要注意的是,High函数返回的结果大小取决于传递的参数。

请查看此示例:

 Writeln(SizeOf(High(Byte)));
 Writeln(SizeOf(High(Char)));
 Writeln(SizeOf(High(Word)));
 Writeln(SizeOf(High(Integer)));
 Writeln(SizeOf(High(NativeInt)));
 Writeln(SizeOf(High(TBytes)));

最后,您可以将High函数的结果转换为整数来修复您的代码。
 if I in [0, Integer(High(L))] then

更新

请查看David提供的额外信息,并记住在使用in运算符检查具有变量值的集合中值的成员资格时要非常小心。该in运算符仅检查每个元素(在delphi 32位中)的最低有效字节。

请查看此示例:

  i:=257;
  Writeln( 1 in [i]); 

这将返回true,因为257的低字节是1。

在Delphi 64位中,大于255的值将从集合中移除。因此,此代码

  i:=257;
  Writeln( 1 in [i]); 

将返回false,因为“==="相当于

  Writeln( 1 in []); 

如果你使用了 High,也应该使用 Low。硬编码 0 可能会生效,但是使用 Low 总是可行的。 - HeartWare
@DavidHeffernan 没问题 :), 正如你所说,我会专注于解释问题并建议快速修复。个人而言,我更喜欢使用IN运算符而不是编写带有多个orif语句。 - RRUZ
@RRUZ:请看Serg的回答,对于XE2 64位仍然正确吗? - Kromster
1
@RRUZ请看我的最新更新。在这里,“in”运算符根本不适用。 - David Heffernan
或者更快的是 cardinal(I)<=cardinal(high(L)),这将只使用一个操作码进行比较,并解决问题。 - Arnaud Bouchez
显示剩余8条评论

5
< p >RRUZ所说的是非常正确的。

再解释一下,64位Delphi中,动态数组索引可以是64位宽。当使用大型TBytes内存块时,这显然是必需的。因此,high函数必须返回一个足以容纳所有可能索引的类型值。因此,应用于动态数组的high返回类型为Int64的值。

一旦开始编译64位代码,in运算符就不适用于您要解决的问题。虽然您可以使用RRUZ建议的转换,但可能更清晰的写法是这样的:

if (I=low(L)) or (I=high(L)) then

虽然in运算符使代码变得更易读,但我认为在这里对Integer进行转换是不可接受的。当你首次拥有超过high(Integer)个元素的数组时,这样只会给你设置一个陷阱。当这种情况发生时,带有类型转换的代码将停止工作。
但实际上,问题比这更深刻。这个使用in的版本的代码在达到high(Integer)个元素之前就已经失败了。事实证明你的代码虽然可以编译,但实际上并不能正常工作。例如,考虑以下程序:
program WeirdSets;
{$APPTYPE CONSOLE}
uses
  SysUtils;
var
  a: array of Integer;
begin
  SetLength(a, 257);
  Writeln(BoolToStr(Length(a) in [0, Length(a)], True));
end.

你会期望这个程序输出True,但实际上它输出的是False。如果你写成以下形式:
Writeln(BoolToStr(Length(a) in [0, 257], True));

然后编译器会报告:
[DCC Error] WeirdSets.dpr(9): E1012 Constant expression violates subrange bounds

这里的根本问题在于集合被限制为256个元素,所以一旦你有一个长度大于256的数组,你的代码就停止工作了。
不幸的是,Delphi对集合的支持非常不足,迫切需要关注。
我还想知道您是否真的打算写


if I in [0..High(L)] then

如果是这样,我建议您使用Math中的InRange函数。

if InRange(I, 0, High(L)) then

甚至更好
if InRange(I, low(L), High(L)) then

1
终于解决了这个问题真是太好了。你的回答非常正确和深入,应该得到更多的赞。 - Kromster
+1个好答案David,我也更新了我的答案,并提供了更多关于In运算符的信息。 - RRUZ

2
OP代码最严重的问题是in操作符仅限于set大小,即[0..255]。在任何32位版本的Delphi中尝试此操作以避免64位问题:
var
  I: Integer;
  L: array of Integer;

begin
  SetLength(L, 1000);
  I:= 999;
  Assert(I in [0, High(L)]);  // fails !
end;

如果Length(L) <= 256始终成立,那么OP很幸运,否则这可能是一个你从未想过的错误。

要查找此错误,请打开范围检查:

{$R+}
procedure TForm1.Button2Click(Sender: TObject);
var
  I: Integer;
  A: array of Integer;

begin
  SetLength(A, 1000);
  I:= 999;
  if I in [0, High(A)] then ShowMessage('OK!');  // Project .. raised exception
                          // class ERangeError with message 'Range check error'.
end;

@LU RD - 如果你是这个意思,那么没有拼写错误,Length(L) <= 256 ==> High(L) <= 255 - kludg
抱歉,混淆了高度和长度。 - LU RD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接