Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with in operator in Delphi 64 bits project

I'm porting a Delphi project to 64 bits and I have a problem with a line of code which has the IN operator.

The compiler raise this error

E2010 Incompatible types: 'Integer' and 'Int64'

I wrote this sample app to replicate the problem.

{$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.

This code works ok in 32 bits, but why doesn't it compile in Delphi XE2 64 bits? How I can fix this issue?

*UPDATE *

It seems which my post caused a lot of confusion (sorry for that) , just for explain the original code which i'm porting is more complex, and I just wrote this code as a sample to illustrate the issue. the original code uses an in operator to check if a value (minor than 255) belongs to a group of values (all minor or equal to 255) like so

i in [0,1,3,50,60,70,80,127,High(LArray)] 
like image 936
Salvador Avatar asked Dec 11 '11 03:12

Salvador


3 Answers

This code can't be compiled because the High function is returning a 8 byte value, which is not a ordinal value. and the In operator can be only used in sets with ordinal values.

FYI, the size of the results returned by the High function is different depending of the parameter passed as argument.

Check this sample

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

Finally, you can fix your code casting the result of High function to integer.

 if I in [0, Integer(High(L))] then

UPDATE

Check the additional info provided by David and remember to be very careful when you use the in operator to check the membership of a value in set with variable values. The in operator only checks the least significant byte of each element (in delphi 32 bits).

Check this sample

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

This return true because the low byte of 257 is 1.

And in Delphi 64 bits, the values greater than 255 are removed of the set. So this code

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

will return false because is equivalent to

  Writeln( 1 in []); 
like image 158
RRUZ Avatar answered Nov 16 '22 21:11

RRUZ


What RRUZ says is quite correct.

To add a little bit more explanation, in 64 bit Delphi, dynamic array indices can be 64 bits wide. This is clearly needed, for example, when working with a large TBytes memory block. And so the high function must return a value of a type wide enough to hold all possible indices. So, high when applied to a dynamic array, returns a value of type Int64.

Once you start compiling 64 bit code the in operator is unsuited to the problem you are trying to solve. Whilst you could use the cast that RRUZ suggests, it may be clearer to write the code like this

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

Whilst the in operator makes for quite readable code, it is my opinion that a cast to Integer is not acceptable here. That will simply set a trap for you to fall into when you first have an array with more than high(Integer) elements. When that happens the code with the cast will stop working.

But in fact the problems run far deeper than this. The in version of the code fails long before you reach high(Integer) elements. It turns out that your code, whilst it compiles, does not really work. For example, consider this program:

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.

You would expect this program to output True but in fact it outputs False. If instead you were to write

Writeln(BoolToStr(Length(a) in [0, 257], True));

then the compiler reports:

[DCC Error] WeirdSets.dpr(9): E1012 Constant expression violates subrange bounds

The fundamental issue here is that sets are limited to 256 elements so as soon as you have an array with length greater than that, your code stops working.

Sadly, Delphi's support for sets is simply inadequate and is in urgent need of attention.


I also wonder whether you actually meant to write

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

If so then I would recommend that you use the InRange function from Math.

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

or even better

if InRange(I, low(L), High(L)) then
like image 43
David Heffernan Avatar answered Nov 16 '22 21:11

David Heffernan


The most serious problem with the OP code is that in operator is limited to set size, i.e. [0..255]. Try this in any 32 bit version of Delphi to avoid the 64 bit issue:

var
  I: Integer;
  L: array of Integer;

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

The OP is lucky if Length(L) <= 256 always, otherwise it is a bug you probably never thought of.

To find this bug switch range checking on:

{$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;
like image 2
kludg Avatar answered Nov 16 '22 20:11

kludg