唐凌 发表于 2018-4-2 19:25:44

随口扯几句操作位的事情

在C里操作位一般都是通过|来置位特定位,&来复位特定位,对应的在汇编里可以用or,and指令实现。
要移动位的话,在C里一般是用<<和>>来左右移位,在汇编里则通过shl和shr指令实现。对于128位数的移位,则通过shld和shrd指令实现。
旋转位的话会很蛋疼,在C里得把边界特定数量位移动到另一侧边界,再将另一部分移动过来。
左旋一个32位变量的话,大概会成这个样子:
#define rotleft(x,n)        (x<<(32-n))|(x>>n)
写成汇编可能会变成这个样子:
rotleft proc x:dword,n:byte
       
        mov eax,x
        mov edx,eax
        mov cl,32
        sub cl,n
        shr eax,cl
        mov cl,n
        shl edx,cl
        or eax,edx
        ret
       
rotleft endp
嗯。。我™执行了8条指令才实现,关键性的指令还有四条(shr,shl,sub,or)。。。
但如果我们自己用汇编指令实现的话,直接引入rol,ror指令即可。
写成函数的话就是这个样子:
rotl_ez proc x:dword,n:byte
       
        mov cl,n
        mov eax,x
        rol eax,cl
        ret
       
rotl_ez endp
上述代码均为MASM。

关于C里判断特定位是置位还是复位,一般会用and指令结合if实现,实质上是对数做与运算后判断是否等于判断用的常量。在C里这么写无可厚非,但在汇编里,如果你只需要判断一个位,则完全不需要这么搞。
有个汇编指令叫做bt,即bit test的意思,这个指令将操作数的特定位赋值到RFlags的CF位。于是要判断一个特定位就相当简单,第一个操作数放要判断的数,第二个操作数放位偏移即可。
指令执行完成后,由于指定位会直接复制到CF位里,直接通过jc或jnc来跳转就行了。若置位需要执行跳转,结合jc;若复位需要执行跳转,结合jnc。
和bt相关的指令还有btc,btr,bts三条指令。其中c,r,s分别代表complement,reset和set。意思是在bt完成后,分别对特定位进行补位,复位和置位。

追加:
在微软编译器中,有一些钦定的编译器内置宏,其中的一些宏可以用于位操作:

__bittest宏可以生成bt指令并对if语句作优化:https://msdn.microsoft.com/en-us/library/h65k4tze(v=vs.100).aspx
相似的,还有针对btc,btr,bts指令的编译器内置宏,分别是:
__bittestandcomplement:https://msdn.microsoft.com/en-us/library/zbdxdb11(v=vs.100).aspx
__bittestandreset:https://msdn.microsoft.com/en-us/library/hd0hzyf8(v=vs.100).aspx
__bittestandset:https://msdn.microsoft.com/en-us/library/z56sc6y4(v=vs.100).aspx
值得注意的是,如果操作数是64位长的,则应该用对应的64位版本。

__shiftleft128和__shiftright128指令可以实现对128位长的数据进行移位:
__shiftleft128:https://msdn.microsoft.com/en-us/library/szzkhewe(v=vs.100).aspx
__shiftright128:https://msdn.microsoft.com/en-us/library/5az7sk38(v=vs.100).aspx

比较奇葩的是,对于旋转位这玩意,微软是有相关的内置宏的,但是只有针对byte和word长变量的宏,它们分别是:
左旋用的__rotl8和__rotl16:https://msdn.microsoft.com/en-us/library/t5e2f3sc(v=vs.100).aspx
右旋用的__rotr8和__rotr16:https://msdn.microsoft.com/en-us/library/yy0728bz(v=vs.100).aspx
于是对dword左旋,你就愉快的#define __rotl32(x,n)        (x<<(32-n))|(x>>n)吧

需要注意的是,假如你要使用这些内置宏,必须指定编译器将代码视为C预言代码,通过设置/TC编译参数或者令后缀名位.c,绝对不可以有/TP参数!在视代码为C++代码的情况下,编译器则不会钦定这些宏!

参考资料:
《超微半导体AMD64架构程序员手册第三卷》第三章: https://support.amd.com/TechDocs/24594.pdf

0xAA55 发表于 2018-4-2 23:00:23

我正想说“旋转位的时候编译器有优化”。但说出这句话之前我突然想到:有的人就是因为太迷信编译器优化而产生错误的理解。
不过就在几分钟前,我在给单片机写启动代码的时候,遇到了一个十分蛋疼的问题:我为了不依赖标准库的memset,自己实现了一个,用来清空bss段。
结果在使用gcc编译的时候,因为开了-O3优化,它检测到我使用循环来把一块内存区域清零,于是自动把我的代码换成了一条memset的调用。
但是在链接的时候,因为嵌入式环境缺乏libc库,memset没有实现。于是链接器报错了。
最后我只好用-fno-tree-loop-distribute-patterns的方式来防止编译器做“聪明的事”。

其实我可以实现一个内存到内存DMA来造一个自己的memset的(检测DMA是否正在传输数据,如果空闲,就拿来用)。但考虑到这会让关联的外设运行异常并且难以调试,我就不这么做了。

唐凌 发表于 2018-4-2 23:49:30

0xAA55 发表于 2018-4-2 11:00
我正想说“旋转位的时候编译器有优化”。但说出这句话之前我突然想到:有的人就是因为太迷信编译器优化而产 ...

见过别人举过编译器优化出了奇怪问题的例子(比如CYY举过VC2010的尾递归优化失误),于是我基本上不敢开编译器优化了,直接就指定/Od了。
页: [1]
查看完整版本: 随口扯几句操作位的事情