Delphi XE字节数组索引
Delphi XE byte array index
我像这样使用简单的循环缓冲区
var
Values: array [byte] of single;
ptr: byte;
在这个测试例子中
for ptr:=0 to 10 do Values[Byte(ptr-5)]:=1;
我希望将前 5 个值和后 5 个值设置为 1,但 XE4 编译器生成的代码不正确,它使用 32 位指针数学计算数组索引:
for ptr:=0 to 10 do Values[Byte(ptr-5)]:=1;
005B94BB C645FB00 mov byte ptr [ebp-],[=13=]
005B94BF 33C0 xor eax,eax
005B94C1 8A45FB mov al,[ebp-]
005B94C4 C78485E0FBFFFF0000803F mov [ebp+eax*4-20],f800000
005B94CF FE45FB inc byte ptr [ebp-]
005B94D2 807DFB0B cmp byte ptr [ebp-],[=13=]b
005B94D6 75E7 jnz [=13=]5b94bf
是我的错误代码吗?操作字节索引的正确方法是什么?
我的代码是用Delphi10.1 Berlin写的,结果好像是一样的
让我们稍微扩展一下您的小代码片段:
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;
这会在 CPU 视图中给出以下代码:
Project80.dpr.15: Values[0] := 1.0;
0041A1DD C785FCFBFFFF0000803F mov [ebp-[=11=]000404],f800000
Project80.dpr.16: for Ptr := 0 to 10 do
0041A1E7 C645FF00 mov byte ptr [ebp-],[=11=]
Project80.dpr.17: Values[Byte(Ptr-5)] := 1.0;
0041A1EB 33C0 xor eax,eax
0041A1ED 8A45FF mov al,[ebp-]
0041A1F0 C78485E8FBFFFF0000803F mov [ebp+eax*4-18],f800000
0041A1FB FE45FF inc byte ptr [ebp-]
Project80.dpr.16: for Ptr := 0 to 10 do
0041A1FE 807DFF0B cmp byte ptr [ebp-],[=11=]b
0041A202 75E7 jnz [=11=]41a1eb
正如我们所见,数组的第一个元素位于 [ebp-[=15=]000404]
,因此 [ebp+eax*4-18]
确实 低于 数组(对于值 0. .4).
对我来说这看起来像是一个错误,因为对于 Ptr = 0
,Byte(Ptr - 5)
应该环绕到$FB
。生成的代码应该是这样的:
mov byte ptr [ebp-],[=12=]
xor eax,eax
@loop:
mov al,[ebp-]
sub al,5 // Byte(Ptr - 5)
mov [ebp+4*eax-04],f800000 // al = $FB, $FC, $FD, $FE, $FF, 00, etc..
inc byte ptr [ebp-]
cmp byte ptr [ebp-],[=12=]b
jnz @loop
好发现!
不过有一个解决方法:
Values[Byte(Ptr - 5) + 0] := 1.0;
这会产生:
Project80.dpr.19: Values[Byte(Ptr - 5) + 0] := 1.0;
0040F16B 8A45FF mov al,[ebp-]
0040F16E 2C05 sub al,
0040F170 25FF000000 and eax,[=14=]0000ff
0040F175 C78485FCFBFFFF0000803F mov [ebp+eax*4-04],f800000
这很好用,尽管 and eax,[=20=]0000ff
对我来说似乎没有必要。
FWIW,我还查看了优化生成的代码。在 XE 和 Berlin 中,错误也存在,解决方法也有效。
问题是:
Is a wrap expected within the Byte()
cast?
让我们将反汇编与溢出检查进行比较 on/off。
{$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,
0041D570 7105 jno [=10=]41d577
0041D572 E82D8DFEFF call @IntOver
0041D577 0FB6C0 movzx eax,al
0041D57A C704870000803F mov [edi+eax*4],f800000
0041D581 43 inc ebx
0041D582 80FB0B cmp bl,[=10=]b
0041D585 75E3 jnz [=10=]41d56a
{$Q-}
Project71.dpr.21: for ptr:= 0 to 10 do Values[Byte(ptr-5)]:= 1;
0041D566 B30B mov bl,[=10=]b
0041D568 B808584200 mov eax,[=10=]425808
0041D56D C7000000803F mov [eax],f800000
0041D573 83C004 add eax,
0041D576 FECB dec bl
0041D578 75F3 jnz [=10=]41d56d
使用 {$Q+}
换行有效,而使用 {$Q-}
换行不起作用,并且在设置 {$R+}
时编译器不会为错误的数组索引生成范围错误。
因此,对我来说结论是:由于 range check on
不会为数组索引越界生成 运行 时间错误,因此 需要换行.
溢出检查打开时进行换行这一事实进一步证明了这一点。
这应该被报告为编译器中的错误。
完成: https://quality.embarcadero.com/browse/RSP-15527 "Type cast fail within array indexing"
注意:@Rudy 在他的回答中给出了解决方法。
附录:
以下代码:
for ptr:= 0 to 10 do WriteLn(Byte(ptr-5));
生成:
251
252
253
254
255
0
1
2
3
4
5
对于 range/overflow 检查的所有组合。
同样,Values[Byte(-1)] := 1;
为所有编译器选项将 1 分配给 Values[255]。
Value Typecasts 的文档说:
The resulting value is obtained by converting the expression in parentheses. This may involve truncation or extension if the size of the specified type differs from that of the expression. The expression's sign is always preserved.
听起来像是编译器的意外行为。但我从不假设使用 byte()
转换整数总是会围绕 $ff
进行舍入。大多数时候它确实如此,例如如果您在变量之间分配值,但在某些情况下它不会 - 正如您所发现的那样。所以我永远不会在数组索引计算中使用这个 byte()
表达式。
我一直观察到使用 byte
变量是不值得的,你应该使用普通的 integer
(或 NativeInt
),以便它匹配 CPU 寄存器,然后不要假设任何复杂的舍入。
在所有情况下,我宁愿将 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,
00648119 81E2FF000000 and edx,[=11=]0000ff
0064811F C704940000803F mov [esp+edx*4],f800000
00648126 40 inc eax
00648127 83F80B cmp eax,[=11=]b
0064812A 75E8 jnz -
TestAll.dpr.116: end;
0064812C 81C400040000 add esp,[=11=]000400
00648132 C3 ret
我像这样使用简单的循环缓冲区
var
Values: array [byte] of single;
ptr: byte;
在这个测试例子中
for ptr:=0 to 10 do Values[Byte(ptr-5)]:=1;
我希望将前 5 个值和后 5 个值设置为 1,但 XE4 编译器生成的代码不正确,它使用 32 位指针数学计算数组索引:
for ptr:=0 to 10 do Values[Byte(ptr-5)]:=1;
005B94BB C645FB00 mov byte ptr [ebp-],[=13=]
005B94BF 33C0 xor eax,eax
005B94C1 8A45FB mov al,[ebp-]
005B94C4 C78485E0FBFFFF0000803F mov [ebp+eax*4-20],f800000
005B94CF FE45FB inc byte ptr [ebp-]
005B94D2 807DFB0B cmp byte ptr [ebp-],[=13=]b
005B94D6 75E7 jnz [=13=]5b94bf
是我的错误代码吗?操作字节索引的正确方法是什么?
我的代码是用Delphi10.1 Berlin写的,结果好像是一样的
让我们稍微扩展一下您的小代码片段:
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;
这会在 CPU 视图中给出以下代码:
Project80.dpr.15: Values[0] := 1.0;
0041A1DD C785FCFBFFFF0000803F mov [ebp-[=11=]000404],f800000
Project80.dpr.16: for Ptr := 0 to 10 do
0041A1E7 C645FF00 mov byte ptr [ebp-],[=11=]
Project80.dpr.17: Values[Byte(Ptr-5)] := 1.0;
0041A1EB 33C0 xor eax,eax
0041A1ED 8A45FF mov al,[ebp-]
0041A1F0 C78485E8FBFFFF0000803F mov [ebp+eax*4-18],f800000
0041A1FB FE45FF inc byte ptr [ebp-]
Project80.dpr.16: for Ptr := 0 to 10 do
0041A1FE 807DFF0B cmp byte ptr [ebp-],[=11=]b
0041A202 75E7 jnz [=11=]41a1eb
正如我们所见,数组的第一个元素位于 [ebp-[=15=]000404]
,因此 [ebp+eax*4-18]
确实 低于 数组(对于值 0. .4).
对我来说这看起来像是一个错误,因为对于 Ptr = 0
,Byte(Ptr - 5)
应该环绕到$FB
。生成的代码应该是这样的:
mov byte ptr [ebp-],[=12=]
xor eax,eax
@loop:
mov al,[ebp-]
sub al,5 // Byte(Ptr - 5)
mov [ebp+4*eax-04],f800000 // al = $FB, $FC, $FD, $FE, $FF, 00, etc..
inc byte ptr [ebp-]
cmp byte ptr [ebp-],[=12=]b
jnz @loop
好发现!
不过有一个解决方法:
Values[Byte(Ptr - 5) + 0] := 1.0;
这会产生:
Project80.dpr.19: Values[Byte(Ptr - 5) + 0] := 1.0;
0040F16B 8A45FF mov al,[ebp-]
0040F16E 2C05 sub al,
0040F170 25FF000000 and eax,[=14=]0000ff
0040F175 C78485FCFBFFFF0000803F mov [ebp+eax*4-04],f800000
这很好用,尽管 and eax,[=20=]0000ff
对我来说似乎没有必要。
FWIW,我还查看了优化生成的代码。在 XE 和 Berlin 中,错误也存在,解决方法也有效。
问题是:
Is a wrap expected within the
Byte()
cast?
让我们将反汇编与溢出检查进行比较 on/off。
{$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,
0041D570 7105 jno [=10=]41d577
0041D572 E82D8DFEFF call @IntOver
0041D577 0FB6C0 movzx eax,al
0041D57A C704870000803F mov [edi+eax*4],f800000
0041D581 43 inc ebx
0041D582 80FB0B cmp bl,[=10=]b
0041D585 75E3 jnz [=10=]41d56a
{$Q-}
Project71.dpr.21: for ptr:= 0 to 10 do Values[Byte(ptr-5)]:= 1;
0041D566 B30B mov bl,[=10=]b
0041D568 B808584200 mov eax,[=10=]425808
0041D56D C7000000803F mov [eax],f800000
0041D573 83C004 add eax,
0041D576 FECB dec bl
0041D578 75F3 jnz [=10=]41d56d
使用 {$Q+}
换行有效,而使用 {$Q-}
换行不起作用,并且在设置 {$R+}
时编译器不会为错误的数组索引生成范围错误。
因此,对我来说结论是:由于 range check on
不会为数组索引越界生成 运行 时间错误,因此 需要换行.
溢出检查打开时进行换行这一事实进一步证明了这一点。
这应该被报告为编译器中的错误。
完成: https://quality.embarcadero.com/browse/RSP-15527 "Type cast fail within array indexing"
注意:@Rudy 在他的回答中给出了解决方法。
附录:
以下代码:
for ptr:= 0 to 10 do WriteLn(Byte(ptr-5));
生成:
251
252
253
254
255
0
1
2
3
4
5
对于 range/overflow 检查的所有组合。
同样,Values[Byte(-1)] := 1;
为所有编译器选项将 1 分配给 Values[255]。
Value Typecasts 的文档说:
The resulting value is obtained by converting the expression in parentheses. This may involve truncation or extension if the size of the specified type differs from that of the expression. The expression's sign is always preserved.
听起来像是编译器的意外行为。但我从不假设使用 byte()
转换整数总是会围绕 $ff
进行舍入。大多数时候它确实如此,例如如果您在变量之间分配值,但在某些情况下它不会 - 正如您所发现的那样。所以我永远不会在数组索引计算中使用这个 byte()
表达式。
我一直观察到使用 byte
变量是不值得的,你应该使用普通的 integer
(或 NativeInt
),以便它匹配 CPU 寄存器,然后不要假设任何复杂的舍入。
在所有情况下,我宁愿将 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,
00648119 81E2FF000000 and edx,[=11=]0000ff
0064811F C704940000803F mov [esp+edx*4],f800000
00648126 40 inc eax
00648127 83F80B cmp eax,[=11=]b
0064812A 75E8 jnz -
TestAll.dpr.116: end;
0064812C 81C400040000 add esp,[=11=]000400
00648132 C3 ret