程序如下,第一种方式是强制指针转换,再取结构体成员;第二种方式是简单的移位。前面这种写法得考虑大小端序, 后者不用管.
#include <stdio.h>
#define hi8(x) ((( struct { unsigned char l; unsigned char h; } *)(&x))->h)
#define lo8(x) ((( struct { unsigned char l; unsigned char h; } *)(&x))->l)
#define hi8a(x) (((x) >> 8) & 0xff)
#define lo8a(x) ((x) & 0xff)
int main(void)
{
unsigned short x;
scanf("%4x", &x); // 不能用字面量测试, 否则-Os编译时会直接给求值了
printf("%2X %2Xn", hi8(x), lo8(x));
printf("%2X %2Xn", hi8a(x), lo8a(x));
return 0;
}
分别用mingw-gcc, arm-none-eabi-gcc, -O0和-Os选项编译,得到的汇编如下(只取scanf到第二次printf之间的部分)
mingw-gcc, -O0, 前者9条指令, 后者10条指令. 有点呆萌是吧...
call _scanf
lea eax, [esp+30]
movzx eax, BYTE PTR [eax]
movzx edx, al
lea eax, [esp+30]
movzx eax, BYTE PTR [eax+1] ; 这里错开1字节取值
movzx eax, al
mov DWORD PTR [esp+8], edx
mov DWORD PTR [esp+4], eax
mov DWORD PTR [esp], OFFSET FLAT:LC1 ; LC1是printf的格式字符串
call _printf
movzx eax, WORD PTR [esp+30]
movzx eax, ax
movzx edx, al
movzx eax, WORD PTR [esp+30]
shr ax, 8 ; 移位
movzx eax, ax
movzx eax, al
mov DWORD PTR [esp+8], edx
mov DWORD PTR [esp+4], eax
mov DWORD PTR [esp], OFFSET FLAT:LC1
call _printf
mingw-gcc, -Os, 前者5条指令, 后者6条指令.
call _scanf
movzx eax, BYTE PTR [esp+30]
mov DWORD PTR [esp], OFFSET FLAT:LC1 ; 常量入栈提到前面了
mov DWORD PTR [esp+8], eax
movzx eax, BYTE PTR [esp+31] ; 取值错开1字节
mov DWORD PTR [esp+4], eax
call _printf
mov ax, WORD PTR [esp+30]
mov DWORD PTR [esp], OFFSET FLAT:LC1
movzx edx, al
movzx eax, ah ; 这次把移位优化掉了, 直接利用ax寄存器的高低位.
mov DWORD PTR [esp+8], edx
mov DWORD PTR [esp+4], eax
call _printf
arm-none-eabi-gcc, -O0, 前者8条指令, 后者10条指令:
bl scanf
sub r3, fp, #6
ldrb r3, [r3, #1] @ zero_extendqisi2
mov r2, r3
sub r3, fp, #6
ldrb r3, [r3] @ zero_extendqisi2 ; 错开1字节
ldr r0, .L3+4 ; .L3+4是printf的格式字符串
mov r1, r2
mov r2, r3
bl printf
ldrh r3, [fp, #-6]
mov r3, r3, lsr #8
mov r3, r3, asl #16
mov r3, r3, lsr #16
and r2, r3, #255
ldrh r3, [fp, #-6]
and r3, r3, #255
ldr r0, .L3+4
mov r1, r2
mov r2, r3
bl printf
arm-none-eabi-gcc, -Os, 这个清爽多了, 前者3条指令, 后者4条指令.
bl scanf
ldrb r1, [sp, #7] @ zero_extendqisi2
ldrb r2, [sp, #6] @ zero_extendqisi2 ; 错开1字节
mov r0, r4 ; .L3+4被提前放进r4了
bl printf
ldrh r1, [sp, #6]
mov r0, r4
mov r1, r1, lsr #8 ; 移位
ldrb r2, [sp, #6] @ zero_extendqisi2
bl printf
avr-gcc呢, 太长就不贴了. 不过-Os时反倒是移位方式的指令更少.
总之以上四种情况都是前者好些. 不过实际运行时也不一定, 没准后者更快呢?
----------------2020.2.22补充-------------------
还有一个重要的区别:前者可以作为左值,后者不行。
如下程序:
#include <stdio.h>
#define hi8(x) ((( struct { unsigned char l; unsigned char h; } *)(&x))->h)
#define lo8(x) ((( struct { unsigned char l; unsigned char h; } *)(&x))->l)
int main(void)
{
unsigned short x = 0x1234;
hi8(x)--;
lo8(x)++;
printf("%xn", x);
return 0;
}
运行,结果是1135。