我使用像这样的简单循环缓冲区
var
Values: array [byte] of single;
ptr: byte;
在此测试示例中
for ptr:=0 to 10 do Values[Byte(ptr-5)]:=1;
我希望设置为1前5个值和最后5个值,但XE4 compiller产生错误的代码,它使用32位指针数学来计算数组索引:
for ptr:=0 to 10 do Values[Byte(ptr-5)]:=1;
005B94BB C645FB00 mov byte ptr [ebp-$05],$00
005B94BF 33C0 xor eax,eax
005B94C1 8A45FB mov al,[ebp-$05]
005B94C4 C78485E0FBFFFF0000803F mov [ebp+eax*4-$0420],$3f800000
005B94CF FE45FB inc byte ptr [ebp-$05]
005B94D2 807DFB0B cmp byte ptr [ebp-$05],$0b
005B94D6 75E7 jnz $005b94bf
这是我的错误代码和操作字节索引的正确方法吗?
答案 0 :(得分:4)
问题是:
预期在
Byte()
演员表中?
让我们将反汇编与开启/关闭溢出检查进行比较。
{$Q+}
Project71.dpr.21: for ptr:= 0 to 10 do Values[Byte(ptr-5)]:= 1;
0041D568 33DB xor ebx,ebx
0041D56A 0FB6C3 movzx eax,bl
0041D56D 83E805 sub eax,$05
0041D570 7105 jno $0041d577
0041D572 E82D8DFEFF call @IntOver
0041D577 0FB6C0 movzx eax,al
0041D57A C704870000803F mov [edi+eax*4],$3f800000
0041D581 43 inc ebx
0041D582 80FB0B cmp bl,$0b
0041D585 75E3 jnz $0041d56a
{$Q-}
Project71.dpr.21: for ptr:= 0 to 10 do Values[Byte(ptr-5)]:= 1;
0041D566 B30B mov bl,$0b
0041D568 B808584200 mov eax,$00425808
0041D56D C7000000803F mov [eax],$3f800000
0041D573 83C004 add eax,$04
0041D576 FECB dec bl
0041D578 75F3 jnz $0041d56d
使用{$Q+}
包装工作,而使用{$Q-}
包装不起作用,并且在设置{$R+}
时,编译器不会为错误的数组索引生成范围错误。
所以,对我来说结论是:由于range check on
没有为数组索引超出边界生成运行时错误,预期包装。
当溢出检查开启时,完成包装的事实进一步证明了这一点。
这应该被报告为编译器中的错误。
完成: https://quality.embarcadero.com/browse/RSP-15527“数组转换失败”
注意:@Rudy在答案中提供了一种解决方法。
<强>附录:强>
以下代码:
for ptr:= 0 to 10 do WriteLn(Byte(ptr-5));
产生
251
252
253
254
255
0
1
2
3
4
5
用于范围/溢出检查的所有组合。
同样Values[Byte(-1)] := 1;
为所有编译器选项的值[255]分配1。
Value Typecasts的文档说:
通过转换括号中的表达式获得结果值。如果指定类型的大小与表达式的大小不同,则可能涉及截断或扩展。表达式的符号始终保留。
答案 1 :(得分:3)
我的代码是用Delphi 10.1 Berlin编写的,但结果似乎是一样的。
让我们稍微扩展您的小代码:
node
这在CPU视图中提供以下代码:
procedure Test;
var
Values: array[Byte] of Single;
Ptr: byte;
begin
Values[0] := 1.0;
for Ptr := 0 to 10 do
Values[Byte(Ptr - 5)] := 1.0;
end;
正如我们所看到的,数组的第一个元素位于Project80.dpr.15: Values[0] := 1.0;
0041A1DD C785FCFBFFFF0000803F mov [ebp-$00000404],$3f800000
Project80.dpr.16: for Ptr := 0 to 10 do
0041A1E7 C645FF00 mov byte ptr [ebp-$01],$00
Project80.dpr.17: Values[Byte(Ptr-5)] := 1.0;
0041A1EB 33C0 xor eax,eax
0041A1ED 8A45FF mov al,[ebp-$01]
0041A1F0 C78485E8FBFFFF0000803F mov [ebp+eax*4-$0418],$3f800000
0041A1FB FE45FF inc byte ptr [ebp-$01]
Project80.dpr.16: for Ptr := 0 to 10 do
0041A1FE 807DFF0B cmp byte ptr [ebp-$01],$0b
0041A202 75E7 jnz $0041a1eb
,因此[ebp-$00000404]
确实低于数组(对于值0..4)。
这对我来说似乎是一个错误 ,因为对于[ebp+eax*4-$0418]
,Ptr = 0
应该回到Byte(Ptr - 5)
。生成的代码应该是:
$FB
好找!
但有一种解决方法:
mov byte ptr [ebp-$01],$00
xor eax,eax
@loop:
mov al,[ebp-$01]
sub al,5 // Byte(Ptr - 5)
mov [ebp+4*eax-$0404],$3f800000 // al = $FB, $FC, $FD, $FE, $FF, 00, etc..
inc byte ptr [ebp-$01]
cmp byte ptr [ebp-$01],$0b
jnz @loop
这会产生:
Values[Byte(Ptr - 5) + 0] := 1.0;
这很好用,虽然Project80.dpr.19: Values[Byte(Ptr - 5) + 0] := 1.0;
0040F16B 8A45FF mov al,[ebp-$01]
0040F16E 2C05 sub al,$05
0040F170 25FF000000 and eax,$000000ff
0040F175 C78485FCFBFFFF0000803F mov [ebp+eax*4-$0404],$3f800000
对我来说似乎没用。
FWIW,我还查看了优化生成的代码。在XE和柏林,错误都存在,并且解决方法也适用。
答案 2 :(得分:1)
听起来像是编译器的意外行为。但是我从不认为使用byte()
的转换整数总是围绕$ff
进行四舍五入。在大多数情况下,它确实例如如果你在变量之间分配值,但有些情况下它并没有 - 正如你所发现的那样。所以我从未在数组索引计算中使用过这个byte()
表达式。
我一直认为使用byte
变量是不值得的,你应该使用普通的integer
(或NativeInt
),以便它与CPU寄存器匹配,然后don&假设任何复杂的舍入。
在所有情况下,我宁愿将255舍入显式化,如下:
procedure test;
var
Values: array [byte] of single;
ptr: integer;
begin
for ptr:=0 to 10 do Values[(ptr-5) and high(Values)]:=1;
end;
如您所见,我做了一些修改:
for
循环索引定义为整数,以使用CPU寄存器; and
操作进行快速二进制舍入(写(ptr-5) mod 256
多更慢); high(Values)
代替固定$ff
常量,表示此舍入的来源。然后快速优化生成的代码:
TestAll.dpr.114: begin
0064810C 81C400FCFFFF add esp,$fffffc00
TestAll.dpr.115: for ptr:=0 to 10 do Values[(ptr-5) and high(Values)]:=1;
00648112 33C0 xor eax,eax
00648114 8BD0 mov edx,eax
00648116 83EA05 sub edx,$05
00648119 81E2FF000000 and edx,$000000ff
0064811F C704940000803F mov [esp+edx*4],$3f800000
00648126 40 inc eax
00648127 83F80B cmp eax,$0b
0064812A 75E8 jnz -$18
TestAll.dpr.116: end;
0064812C 81C400040000 add esp,$00000400
00648132 C3 ret