Delphi 2009 - Bug?将一些假定的无效值添加到一个集合 [英] Delphi 2009 - Bug? Adding supposedly invalid values to a set

查看:140
本文介绍了Delphi 2009 - Bug?将一些假定的无效值添加到一个集合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,我不是一个非常有经验的程序员。我正在使用Delphi 2009,并且一直在使用这些套件,这似乎表现得非常奇怪,甚至与我不一致。我想这可能是我,但以下看起来好像有明显的错误:

 单元测试; 

接口

使用
Windows,消息,SysUtils,类,图形,控件,窗体,
对话框,StdCtrls;

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

var Form1:TForm1;

实现

{$ R * .dfm}

程序TForm1.Button1Click(发件人:TObject);
begin
test:= [3];
如果3在测试然后
Edit1.Text:='3';
结束

end。

如果运行程序并点击按钮,那么,它将在文本字段中显示字符串3。但是,如果您尝试使用像100这样的数字相同的东西,就不会显示任何内容(在我看来应该是这样)。我错过了什么,还是这种bug?请注意!



编辑:到目前为止,似乎我并不孤单我的观察。如果有人对此有一些内在的了解,我会很高兴听到。另外,如果有Delphi 2010(甚至是Delphi XE)的人,如果你可以对这个甚至是一般的集合行为进行一些测试(比如test:set of 256..257),我会感激的,因为它会有趣的是看看新版本是否有变化。

解决方案

我很好奇,看看编译代码得到生成,并且我想出了如何在Delphi 2010中工作的以下内容。它解释了为什么你可以做 test:= [8] test :设置1..2 ,为什么 Assert(8在测试中)立即失败。



实际使用了多少空间?



一组字节字节值,全部256位,32字节。一个设置为1..2 需要1个字节,但令人惊讶的是设置为100..101 也需要一个字节,所以Delphi的编译器对于内存分配是非常聪明的。在另一方面,$ code> 7..8 的集合需要2个字节,并且基于仅包含值 0 101 require(gasp)13 bytes!



测试代码:

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

程序测试;
var A:set 1..2;
B:套7..8;
C:套100 ... 101;
D:一组TTestEnumeration;
E:一组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
结束;

结论:




  • 该集合的基本模型是字节集,256个可能的位,32个字节。

  • Delphi确定所需的连续子范围总共为32个字节的范围并使用。对于设置为1..2 的情况,它可能只使用第一个字节,因此 SizeOf()返回1。对于设置100.101 ,它可能只使用第13个字节,所以 SizeOf()返回1.对于 set of 7..8 它可能是使用前两个字节,所以我们得到 SizeOf()= 2 。这是一个特别有趣的情况,因为它向我们展示了位不向左或向右移动以优化存储。另一个有趣的情况是TTestEnumeration2 集合:它使用6个字节,即使是那里有很多不可用的位。



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



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

  
var A:set 1..2;
B:套2..3;
begin
A:= [1];
B:= [1];
结束

对于那些了解汇编器,请自行查看生成的代码。对于那些不了解汇编器的人,生成的代码相当于:

  begin 
A:= CompilerGeneratedArray [ 1];
B:= CompilerGeneratedArray [1];
结束

这不是打字错误,编译器对这两个作业使用相同的预编译值。 CompiledGeneratedArray [1] = 2



这是另一个测试:

  procedure Test2; 
var A:set 1..2;
B:一套100..101;
begin
A:= [1];
B:= [1];
结束

再次,在伪代码中,编译代码如下所示:

  begin 
A:= CompilerGeneratedArray1 [1];
B:= CompilerGeneratedArray2 [1];
结束

再次,没有打字错误:这次编译器对两个作业使用不同的预编译值。 CompilerGeneratedArray1 [1] = 2 CompilerGeneratedArray2 [1] = 0 ;编译器生成的代码足够聪明,不能用无效值覆盖B中的位(因为B保存有关位96..103的信息),而且它们对于这两个赋值使用非常相似的代码。



结论




  • 如果您使用基础设置中的值进行测试,则所有设置操作都能正常工作。对于设置1..2 ,使用 1 2 。对于集合7..8 仅测试 7 8 。我不认为集合被破坏。它的目的非常好,在整个VCL(而且它在我自己的代码中也有一个地方)。

  • 在我看来,编译器为集合分配生成次优代码。我不认为表查找是必需的,编译器可以内联生成值,代码的大小相同但位置更好。

  • 我的意见是副作用将设置为1..2 的行为与设置为0..7 是副作用以前在编译器中缺少优化。

  • 在OP的情况下( var test:set of 1..2; test:= [7] code>)编译器应该生成一个错误。我不会把它分类为一个bug,因为我不认为编译器的行为应该被定义为程序员在坏代码上做什么,而是在程序员如何处理好代码;编译器应该生成常量表达式违反子范围界限,因为如果您尝试此代码,那么编译器应该生成



(代码示例)

  
var t:1..2;
begin
t:= 3;
结束




  • 在运行时,如果代码使用 {$ R +} ,那么这个坏的作业应该会引发一个错误,就像你试试这个代码一样:



(代码示例)

  
var t:1..2;
i:整数;
begin
{$ R +}
for i:= 1 to 3 do
t:= i;
{$ R-}
end;


First of all, I'm not a very experienced programmer. I'm using Delphi 2009 and have been working with sets, which seem to behave very strangely and even inconsistently to me. I guess it might be me, but the following looks like there's clearly something wrong:

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.

If you run the program and click the button, then, sure enough, it will display the string "3" in the text field. However, if you try the same thing with a number like 100, nothing will be displayed (as it should, in my opinion). Am I missing something or is this some kind of bug? Advice would be appreciated!

EDIT: So far, it seems that I'm not alone with my observation. If someone has some inside knowledge of this, I'd be very glad to hear about it. Also, if there are people with Delphi 2010 (or even Delphi XE), I would appreciate it if you could do some tests on this or even general set behavior (such as "test: set of 256..257") as it would be interesting to see if anything has changed in newer versions.

解决方案

I was curious enough to take a look at the compiled code that gets produced, and I figured out the following about how sets work in Delphi 2010. It explains why you can do test := [8] when test: set of 1..2, and why Assert(8 in test) fails immediately after.

How much space is actually used?

An set of byte has one bit for every possible byte value, 256 bits in all, 32 bytes. An set of 1..2 requires 1 byte but surprisingly set of 100..101 also requires one byte, so Delphi's compiler is pretty smart about memory allocation. On the othter hand an set of 7..8 requires 2 bytes, and set based on a enumeration that only includes the values 0 and 101 requires (gasp) 13 bytes!

Test code:

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;

Conclusions:

  • The basic model behind the set is the set of byte, with 256 possible bits, 32 bytes.
  • Delphi determines the required continuous sub-range of the total 32 bytes range and uses that. For the case set of 1..2 it probably only uses the first byte, so SizeOf() returns 1. For the set of 100.101 it probably only uses the 13th byte, so SizeOf() returns 1. For the set of 7..8 it's probably using the first two bytes, so we get SizeOf()=2. This is an especially interesting case, because it shows us that bits are not shifted left or right to optimize storage. The other interesting case is the set of TTestEnumeration2: it uses 6 bytes, even those there are lots of unusable bits around there.

What kind of code is generated by the compiler?

Test 1, two sets, both using the "first byte".

procedure Test;
var A: set of 1..2;
    B: set of 2..3;
begin
  A := [1];
  B := [1];
end;

For those understand Assembler, have a look at the generated code yourself. For those that don't understand assembler, the generated code is equivalent to:

begin
  A := CompilerGeneratedArray[1];
  B := CompilerGeneratedArray[1];
end;

And that's not a typo, the compiler uses the same pre-compiled value for both assignments. CompiledGeneratedArray[1] = 2.

Here's an other test:

procedure Test2;
var A: set of 1..2;
    B: set of 100..101;
begin
  A := [1];
  B := [1];
end;

Again, in pseudo-code, the compiled code looks like this:

begin
  A := CompilerGeneratedArray1[1];
  B := CompilerGeneratedArray2[1];
end;

Again, no typo: This time the compiler uses different pre-compiled values for the two assignments. CompilerGeneratedArray1[1]=2 while CompilerGeneratedArray2[1]=0; The compiler generated code is smart enough not to overwrite the bits in "B" with invalid values (because B holds information about bits 96..103), yet it uses very similar code for both assignments.

Conclusions

  • All set operations work perfectly well IF you test with values that are in the base-set. For the set of 1..2, test with 1 and 2. For the set of 7..8 only test with 7 and 8. I don't consider the set to be broken. It serves it's purpose very well all over the VCL (and it has a place in my own code as well).
  • In my opinion the compiler generates sub-optimal code for set assignments. I don't think the table-lookups are required, the compiler could generate the values inline and the code would have the same size but better locality.
  • My opinion is that the side-effect of having the set of 1..2 behave the same as set of 0..7 is the side-effect of the previous lack of optimization in the compiler.
  • In the OP's case (var test: set of 1..2; test := [7]) the compiler should generate an error. I would not classify this as a bug because I don't think the compiler's behavior is supposed to be defined in terms of "what to do on bad code by the programmer" but in terms of "what to do with good code by the programmer"; None the less the compiler should generate the Constant expression violates subrange bounds, as it does if you try this code:

(code sample)

procedure Test;
var t: 1..2;
begin
  t := 3;
end;

  • At runtime, if the code is compiled with {$R+}, the bad assignment should raise an error, as it does if you try this code:

(code sample)

procedure Test;
var t: 1..2;
    i: Integer;
begin
  {$R+}
  for i:=1 to 3 do
    t := i;
  {$R-}
end;

这篇关于Delphi 2009 - Bug?将一些假定的无效值添加到一个集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆