Delphi 2009 - Bug?将所谓的无效值添加到集合中

Soc*_*cob 6 delphi set delphi-2009

首先,我不是一个非常有经验的程序员.我正在使用Delphi 2009并且一直在处理集合,这些集合似乎表现得非常奇怪甚至不一致.我想这可能是我,但以下看起来显然有些不对劲:

unit test;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
  Button1: TButton;
  Edit1: TEdit;
  procedure Button1Click(Sender: TObject);
private
    test: set of 1..2;
end;

var Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  test := [3];
  if 3 in test then
    Edit1.Text := '3';
end;

end.
Run Code Online (Sandbox Code Playgroud)

如果您运行该程序并单击该按钮,那么,当然,它将在文本字段中显示字符串"3".但是,如果您使用100之类的数字尝试相同的事情,则不会显示任何内容(在我看来应该如此).我错过了什么或者这是某种错误吗?建议将不胜感激!

编辑:到目前为止,我的观察似乎并不孤单.如果有人对此有一些了解,我会很高兴听到这个消息.此外,如果有人使用Delphi 2010(甚至是Delphi XE),如果你能对这个甚至是一般设置行为(例如"test:set of 256..257")进行一些测试,我将不胜感激.有趣的是看看在新版本中是否有任何变化.

Cos*_*und 12

我很好奇地看看生成的已编译代码,我想到了以下关于如何在Delphi 2010中使用集合的内容.它解释了为什么你可以在什么test := [8]时候做test: set of 1..2,以及为什么Assert(8 in test)在之后立即失败.

实际使用了多少空间?

一个set of byte具有用于每一个可能的字节值的一个比特,256个比特中的所有32个字节.一个set of 1..2需要1个字节,但令人惊讶的set of 100..101还需要一个字节,所以德尔福的编译器是非常聪明的内存分配.在另一方面,set of 7..8需要2个字节,并根据只包含值的枚举进行设置,0101要求(喘气)13个字节!

测试代码:

TTestEnumeration = (te0=0, te101=101);
TTestEnumeration2 = (tex58=58, tex101=101);

procedure Test;
var A: set of 1..2;
    B: set of 7..8;
    C: set of 100..101;
    D: set of TTestEnumeration;
    E: set of TTestEnumeration2;
begin
  ShowMessage(IntToStr(SizeOf(A))); // => 1
  ShowMessage(IntToStr(SizeOf(B))); // => 2
  ShowMessage(IntToStr(SizeOf(C))); // => 1
  ShowMessage(IntToStr(SizeOf(D))); // => 13
  ShowMessage(IntToStr(SizeOf(E))); // => 6
end;
Run Code Online (Sandbox Code Playgroud)

结论:

  • 该集合背后的基本模型是set of byte256个可能的位,32个字节.
  • Delphi确定总32字节范围所需的连续子范围并使用它.对于这种情况,set of 1..2它可能只使用第一个字节,所以SizeOf()返回1.因为set of 100.101它可能只使用第13个字节,所以SizeOf()返回1.因为set of 7..8它可能使用前两个字节,所以我们得到SizeOf()=2.这是一个特别有趣的情况,因为它向我们显示位不向左或向右移位以优化存储.另一个有趣的例子是set of TTestEnumeration2:它使用6个字节,即使那些周围有很多不可用的位.

编译器生成什么样的代码?

测试1,两组,均使用"第一个字节".

procedure Test;
var A: set of 1..2;
    B: set of 2..3;
begin
  A := [1];
  B := [1];
end;
Run Code Online (Sandbox Code Playgroud)

对于那些了解Assembler的人,请亲自查看生成的代码.对于那些不了解汇编程序的人,生成的代码相当于:

begin
  A := CompilerGeneratedArray[1];
  B := CompilerGeneratedArray[1];
end;
Run Code Online (Sandbox Code Playgroud)

这不是拼写错误,编译器对两个赋值都使用相同的预编译值.CompiledGeneratedArray[1] = 2.

这是另一个测试:

procedure Test2;
var A: set of 1..2;
    B: set of 100..101;
begin
  A := [1];
  B := [1];
end;
Run Code Online (Sandbox Code Playgroud)

同样,在伪代码中,编译后的代码如下所示:

begin
  A := CompilerGeneratedArray1[1];
  B := CompilerGeneratedArray2[1];
end;
Run Code Online (Sandbox Code Playgroud)

同样,没有拼写错误:这次编译器为两个赋值使用不同的预编译值.CompilerGeneratedArray1[1]=2同时CompilerGeneratedArray2[1]=0; 编译器生成的代码足够智能,不会用无效值覆盖"B"中的位(因为B保存有关位96..103的信息),但它对两个赋值使用非常相似的代码.

结论

  • 如果使用基本集中的值进行测试,则所有设置操作都能很好地工作.对于set of 1..2,用1和测试2.对于set of 7..8唯一的测试用78.我不认为这set会被打破.它在整个VCL中都很好地服务于它(并且它在我自己的代码中也占有一席之地).
  • 在我看来,编译器为集合分配生成次优代码.我不认为表查找是必需的,编译器可以生成内联值,代码将具有相同的大小但更好的位置.
  • 我的观点是,具有set of 1..2相同行为的set of 0..7副作用是编译器中先前缺乏优化的副作用.
  • 在OP的case(var test: set of 1..2; test := [7])中,编译器应该生成错误.我不会将此归类为错误,因为我不认为编译器的行为应该根据"程序员在错误代码上做什么"来定义,而是根据"程序员如何处理好代码" "; 尽管如此,编译器应该生成的内容Constant expression violates subrange bounds正如您尝试此代码时所做的那样:

(代码示例)

procedure Test;
var t: 1..2;
begin
  t := 3;
end;
Run Code Online (Sandbox Code Playgroud)
  • 在运行时,如果使用编译代码{$R+},错误的赋值应该引发错误,就像尝试此代码时一样:

(代码示例)

procedure Test;
var t: 1..2;
    i: Integer;
begin
  {$R+}
  for i:=1 to 3 do
    t := i;
  {$R-}
end;
Run Code Online (Sandbox Code Playgroud)