在C/C++等高级编程语言中,对某个数值的某一个比特位的测试和置位,通常是通过位运算符号和比较运算符来实现,比如判断0x23的第0比特是否为1的语句是if(0x15 & 0x01)。if(0x15 & 0x01)经过编译器编译后的机器指令,包括转移、比特位测试等指令,计算机的CPU运行的语句是经过编译器翻译之后的转移、比特位测试等机器码。
C/C++等高级语言的便捷性屏蔽了计算机程序运行的许多细节,为了更加深刻的认识编程开发工作的本质和意义,有必要有结合深奥的二进制和机器码,对一句简单的"0x15 & 0x01"语句进行学习。下面结合汇编的比特位测试和设置指令,和一个比较复杂的比特位的操作示例:比特矩阵旋转,学习汇编的比特位操作。
1. 比特位测试和设置指令
汇编提供了位的测试和设置指令,包括BT、BTC、BTR、BTS,这4个指令都需要两个操作数,操作数1指定位串,操作数2指定位号。
1.1 BT
BT指令将被测试位的值送到CF。
1.2 BTC
BTC指令将被测试位的值送到CF,并把被测试位取反。
1.3 BTR
BTR指令将被测试位的值送到CF,并清零被测试位。
1.4 BTS
BTS指令将被测试位的值送到CF,并置位被测试位。
2. 旋转比特矩阵
这里以实现旋转一个含有8个元素的整数数组的比特位为例,编写一段汇编代码实现比特位位的旋转和输出,输出每个数的比特字符串,"."表示对应位为0,"*"表示对应位为1。
按照上面的规则,0x11的输出的比特位的字符串是". . . * . . . *", 数组{0x11, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}的比特位矩阵是:
顺时针旋转90°之后的比特位矩阵是:
下面是实现旋转的汇编代码,包括两个子程序:rotate和output,rotate无参数,output需传递待输出数组的地址和数组长度。rotate子程序通过BT指令测试ESI+EDX指定操作数的ECX指定的位,如果该位为1,则通过BTS指令置位EDI+EAX指定操作数的EDX指定的位,输出调用C库函数_printf,也可以使用中断实现字符的输出。代码如下图所示:
extern _printf
global buf
section .bss
buf resd 8
section .text
; rotate a bit array and output
rotate:
push ebp
mov ebp, esp
push 8
push origdata
call output
add esp, 8
xor eax, eax
xor edx, edx
mov ecx, 7
mov esi, origdata
mov edi, buf
.btr:
bt dword [esi + edx], ecx
jnb .bt
bts dword [edi + eax], edx
.bt:
inc edx
cmp edx, 8
jnz .btr
.next:
xor edx, edx
dec ecx
inc eax
cmp eax, 8
jnz .btr
.output:
push 8
push buf
call output
add esp, 8
pop ebp
ret
; output bit array:
; arg1: data ptr
; arg2: data length
output:
push ebp
mov ebp, esp
mov esi, dword [esp + 8]
push msg
call _printf
add esp, 4
xor eax, eax
xor ebx, ebx
.lb0:
bt [esi + ebx], eax
jnb .lb2 ; cf = 0
.lb1:
push '*'
jmp .lb3
.lb2:
push '.'
.lb3:
inc eax
cmp eax, 8
jnz .lb0
.lb4:
push aline
call _printf
add esp, 24h
xor eax, eax
inc ebx
cmp ebx, dword [esp + 12]
jnz .lb0
pop ebp
ret
section .data
origdata:
db 0x11, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
aline:
db 0x09, "%c %c %c %c %c %c %c %c", 0x0a, 0
msg:
db "Bit Array:", 0x0a, 0