[原]深析DELPHI in 操作实现
群友探讨到这个问题顺便有空分析了下delphi编译器的实现方式,有错误地方敬请指出。
function AnsiStrInCharSet(ch: char; CharSet: TSysCharSet): boolean;
begin
Result := ch in CharSet;
end;
测试调用:if AnsiStrInCharSet('a', ['a'..'z']) then ...
实际编译器翻译为以下代码:
0045BF7A |. F3:A5 rep movs dword ptr es:[edi], dword ptr [esi] ;
复制ESI( ['a'..'z']) 到BT指令标志判断dest操作数,也就是CharSet: TSysCharSet 的内容由编译器固定编译为一个位信息常量,该常量长度为32byte,定义结构为:
TBitInfoField =array [0..7] of dword;
暂称该常量为BitInfoField:TBitInfoField 具体规则后面讲述。
0045BF7C |. 0FB6C0 movzx eax, al ;
赋值到BT指令标志判断src操作数,也就是ch: char
0045BF7F |. 0FA345 E0 bt dword ptr [ebp-20], eax ;
[ebp-20] 实际指向BitInfoField,eax是位数,作用就是把BitInfoField的第eax位送CF标志
比如['a'..'z']编译器编译的BitInfoField为:
0012F588 00 00 00 00 00 00 00 00 00 00 00 00 FE FF FF 07 ............?
0012F598 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
[#0..#255]编译器编译的BitInfoField为:
0012F588 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
0012F598 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
为什么编译器会这么编译呢?因为bt指令是一个标志测试指令具体语法格式为:
BT r/m16,r16
将所选的位存储到 CF 标志
BT r/m32,r32
将所选的位存储到 CF 标志
BT r/m16,imm8
将所选的位存储到 CF 标志
BT r/m32,imm8
将所选的位存储到 CF 标志
这里有两个公式确定位值:
1. BIdx= BitOffset div 32
2. BBit= BitOffset mod 32
我们这里的in编译器使用的是 BT m32,r32格式,它的作用就是把m32内存中BitInfoField[BIdx(r32)]常量的数据的第BitInfoField[BBit(r32)]位送CF标志.
假设枚举范围是['a'..'z'] 则BitInfoField为:
00 00 00 00 00 00 00 00 00 00 00 00 FE FF FF 07
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
'a' in ['a'..'z'] 最后转换成的CPU指令应该是:
bt dword ptr [BitInfoField] , eax ; eax= $61
因为 'a'的Ascll码为:$61 根据公式: BIdx = $61 div 32 = 3 , BBit= BitOffset mod 32 =1
BitInfoField[BIdx=3 ] = $7FFFFFE 转换为二进制为:00000111111111111111111111111110
BBit = 1
00000111111111111111111111111110
31-------------------------------0
那么 bt dword ptr [BitInfoField] , $61 就是把下面标志传送到CF标志寄存器
000001111111111111111111111111[1]0
31-------------------------------0
所以CF =1
这样就完成了'a' in ['a'..'z'] 的判断。
根据此原理可以推导出 delphi的 char in ['a'..'z'] 和 char in ['a'..'z','1'..'9'] 执行效率是一样的
因为['a'..'z','1'..'9'] 的BitInfoField为:
00 00 00 00 00 00 FE 03 00 00 00 00 FE FF FF 07
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
当判断任意字符 bt dword ptr [BitInfoField] , BitOffset
通过公式:
1. BIdx= BitOffset div 32
2. BBit= BitOffset mod 32
计算出BIdx 和BBit 只需把相应标志位传送到CF即完成。
总结DELPHI in 操作效率比pos操作效率要高很多,并且效率不被枚举元素个数和多少影响,缺点是只能判断0-255范围的数据。