唔,写这篇博客的原因是给人解释了半天,觉得不复制出来整理下做成一篇blog 可惜了。。。。
1. 为什么对int 变量的操作比操作short 变量的指令快
如果你看过操作系统引导部分的资料的话,你就明白在描述符中D/B 位决定了CPU 指令的默认地址,如果置位则表示是32 位地址,复位就是8/16 位地址(我没看过64 位保护模式的资料原谅我吧)。一般来说我们的机器都是32 位的,所以32 位的机器只能操作32 位的地址。
但是这里有一个0x66 指令前缀(Prefixes ),这个前缀的作用就是切换默认操作数大小,所以当你在32 位系统上去操作一个8/16 位的变量的时候,那么就会在机器码前面增加一个0x66 前缀,这是就会多话费一个CPU 周期去执行。
但是对于char 呢?操作int 的指令比操作short 的快,并不代表操作int 的指令都要比操作长度小于sizeof (int) 变量的指令要快。
事实上操作int 的语句确实比操作short 的快,但是至少和操作char 的一样快。
2. 为什么操作int 类型的指令要快于操作short 的,却可能慢于操作char 的
程序:
short aShort = 0;
int aInt = 0;
objdump 之后:4004b4: 66 c7 45 fe 00 00 movw $0x0,-0x2(%rbp)
4004ba: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
程序:
char aChar = 0;
int aInt = 0;
objdump 之后:
4004b4: c6 45 ff 00 movb $0x0,-0x1(%rbp)
4004b8: c7 45 f8 00 00 00 00 movl $0x0,-0x8(%rbp)
对于short 类型的确实加了x66 前缀了,但是char 类型的mov 中就没加,差了下指令表:
MOVmi 1100011w oo000www disp data
蛋疼的是objdump 输出的att 格式的汇编,不顺眼写成MOVim 也行。
对于char 的操作,汇编器选择了直接复位w 位,直接换成对字节的操作了。
short 则不是,w 是置位的,对字操作,但是因为D/B 的关系,默认是操作双字,所以才加的x66 前缀改变了默认操作地址大小为字。
所以操作int 的语句确实比操作short 的快,但是至少和操作char 的一样快。
3. 对于0x66 Prefix 如何起作用的的进一步说明
对于0x66 Prefix,可以用一条x86_64 架构上的机器码手动反汇编过程来说明:
4004b4: 66 c7 45 fe 00 00
4004b4: 66 c7 45 fe 00 0066:Operand-Size Override prefix,用来更改默认Operand-Size(Descriptor 上的D/B 位)。
c7:Opcode。查找指令表110001(0xc7 的高6 位,因为最后2 位为后缀),发现有多条指令符合。再看ModR/M 位45,其中4、5、6 位为000,增加这个条件发现只有一条MOVmi 指令符合(MOVmi 1100011w oo000www disp data),因为指令的寻址方式是立即寻址,所以第2 位应该是s 位,该位置位说明立即数位8 位但要求扩展成16 位(下面会解释为什么会这样扩展)。其中第1 位w 置位,说明对字操作(16 位)。所以0xc7 是一条mov 指令,将大小为字的立即数送到一块内存。但是对于CPU 而言这条指令有Group 属性,需要配合ModR/M.reg 来配合定位(也就是0xc7 并不是完整的opcode,当然人工也没法确定,之前已经见识到了)。
45:0b0100_0101,ModR/M 位,为了直观写作0b01_000_101
ModR/M.mod = 0b01:寻址模式base+disp8。
ModR/M.reg = 0b000:MOVmi 中对应的ModR/M.reg 位是oo000www,由ModR/M.reg 和Opcode 共同确定了指令MOVmi。
ModR/M.r/m = 0b101:结合ModR/M.mod = 0b01,查表可得rbp+disp8
fe:disp8,8 位偏移量,-2
00 00:imm8,8 位立即数(实际上是16 位):0。之所以实际上是16 位,原因是该指令的目的操作数是16 位的。之所以目的操作数是16 位的,原因是原本目的操作数是8 位的,但是因为D/B 位被置位,目的操作数大小变为32 位,这里又加了0x66 前缀,所以目的操作数大小变为16 位。
结合0x66 前缀,操作数由双字变为字,对应指令就是
movw $0x0000, -0x2(%rbp)
这就是0x66 Prefix 起作用的一个例子,事实上手动反汇编的结果和objdump 之后得到的结果完全一致。
事实上假设你的编译环境是16 位以上的,那么立即数的位数如果被要求如下(至于怎么出来的自行反汇编):
立即数位数 | 决定立即数位数的原因 | 决定目的操作数位数的原因 |
8 | w 位复位,指令中存在8 位立即数 | D/B 位复位,无0x66 Prefix |
16 | w 位置位,指令中存在16 位立即数 | D/B 位置位,有0x66 Prefix |
32 | w 位置位,指令中存在32 位立即数 | D/B 位置位,无0x66 Prefix |
64 | w 位置位,指令中存在32 位立即数 | D/B 位置位,有0x48 Prefix |
没了,就是这样。