17.2 Specific bit setting/clearing

例如:

  1. #define IS_SET(flag, bit) ((flag) & (bit))
  2. #define SET_BIT(var, bit) ((var) |= (bit))
  3. #define REMOVE_BIT(var, bit) ((var) &= ~(bit))
  4. int f(int a)
  5. {
  6. int rt=a;
  7. SET_BIT (rt, 0x4000);
  8. REMOVE_BIT (rt, 0x200);
  9. return rt;
  10. };

17.2.1 x86

MSVC 2010: Listing 17.10: MSVC 2010

  1. _rt$ = -4 ; size = 4
  2. _a$ = 8 ; size = 4
  3. _f PROC
  4. push ebp
  5. mov ebp, esp
  6. push ecx
  7. mov eax, DWORD PTR _a$[ebp]
  8. mov DWORD PTR _rt$[ebp], eax
  9. mov ecx, DWORD PTR _rt$[ebp]
  10. or ecx, 16384 ; 00004000H
  11. mov DWORD PTR _rt$[ebp], ecx
  12. mov edx, DWORD PTR _rt$[ebp]
  13. and edx, -513 ; fffffdffH
  14. mov DWORD PTR _rt$[ebp], edx
  15. mov eax, DWORD PTR _rt$[ebp]
  16. mov esp, ebp
  17. pop ebp
  18. ret 0
  19. _f ENDP

OR指令添加一个或多个bit位而忽略了其余位。 AND用来重置一个bit位。 如果我们使用msvc编译,并且打开优化选项(/Ox),代码将会更短: Listing 17.11: Optimizing MSVC

  1. _a$ = 8 ; size = 4
  2. _f PROC
  3. mov eax, DWORD PTR _a$[esp-4]
  4. and eax, -513 ; fffffdffH
  5. or eax, 16384 ; 00004000H
  6. ret 0
  7. _f ENDP

我们来看GCC 4.4.1无优化的代码:

  1. public f
  2. f proc near
  3. var_4 = dword ptr -4
  4. arg_0 = dword ptr 8
  5. push ebp
  6. mov ebp, esp
  7. sub esp, 10h
  8. mov eax, [ebp+arg_0]
  9. mov [ebp+var_4], eax
  10. or [ebp+var_4], 4000h
  11. and [ebp+var_4], 0FFFFFDFFh
  12. mov eax, [ebp+var_4]
  13. leave
  14. retn
  15. f endp

MSVC未优化的代码有些冗余。 现在我们来看GCC打开优化选项-O3:

Listing 17.13: Optimizing GCC

  1. public f
  2. f proc near
  3. arg_0 = dword ptr 8
  4. push ebp
  5. mov ebp, esp
  6. mov eax, [ebp+arg_0]
  7. pop ebp
  8. or ah, 40h
  9. and ah, 0FDh
  10. retn
  11. f endp

代码更短。值得注意的是编译器使用了AH寄存器-EAX寄存器8bit-15bit部分。

17.2 Specific bit setting/clearing - 图1

8086 16位CPU累加器被称为AX,包含两个8位部分-AL(低字节)和AH(高字节)。在80386下所有寄存器被扩展为32位,累加器被命名为EAX,为了保持兼容性,它的老的部分仍可以作为AX/AH/AL寄存器来访问。 因为所有的x86 CPU都兼容于16位CPU,所以老的16位操作码比32位操作码更短。”or ah,40h”指令仅复制3个字节比“or eax,04000h”需要复制5个字节甚至6个字节(如果第一个操作码不是EAX)更合理。 编译时候开启-O3并且设置regram=3生成的代码会更短。

Listing 17.14: Optimizing GCC

  1. public f
  2. f proc near
  3. push ebp
  4. or ah, 40h
  5. mov ebp, esp
  6. and ah, 0FDh
  7. pop ebp
  8. retn
  9. f endp

事实上,第一个参数已经被加载到EAX了,所以可以直接使用了。值得注意的是,函数序言(push ebp/mov ebp,esp)和结语(pop ebp)很容易被忽略。GCC并没有优化掉这些代码。更短的代码可以使用内联函数(27)。

17.2.2 ARM + Optimizing Keil + ARM mode

Listing 17.15: Optimizing Keil + ARM mode

  1. 02 0C C0 E3 BIC R0, R0, #0x200
  2. 01 09 80 E3 ORR R0, R0, #0x4000
  3. 1E FF 2F E1 BX LR

BIC是“逻辑and“类似于x86下的AND。ORR是”逻辑or“类似于x86下的OR。

17.2.3 ARM + Optimizing Keil + thumb mode

Listing 17.16: Optimizing Keil + thumb mode

  1. 01 21 89 03 MOVS R1, 0x4000
  2. 08 43 ORRS R0, R1
  3. 49 11 ASRS R1, R1, #5 ; generate 0x200 and place to R1
  4. 88 43 BICS R0, R1
  5. 70 47 BX LR5

从0x4000右移生成0x200,采用移位使代码更简洁。

17.2.4 ARM + Optimizing Xcode (LLVM) + ARM mode

Listing 17.17: Optimizing Xcode (LLVM) + ARM mode

  1. 42 0C C0 E3 BIC R0, R0, #0x4200
  2. 01 09 80 E3 ORR R0, R0, #0x4000
  3. 1E FF 2F E1 BX LR

该代码由LLVM生成,从源码形式上看,看起来更像是:

  1. REMOVE_BIT (rt, 0x4200);
  2. SET_BIT (rt, 0x4000);

为什么是0x4200?可能是编译器构造的5,可能是编译器编译错误,但生成的代码是可用的。 更多编译器异常请参考相关资料(67)。 对于thumb模式,优化Xcode(LLVM)生成的代码相似。