gcc 嵌入式汇编(asm)实现bsr(位扫描)指令

来源:互联网 发布:linux的dd命令SD卡 编辑:程序博客网 时间:2024/05/20 14:16

在c/c++语言中,如果你想获取一个二进制数为1的最高位的位置(比如40的最高位位置是5,1的最高位位置是0),该怎么办?

c语言实现

最笨的办法就是下面的代码

//对一个64位无符号整数进行位扫描(从高位到低位)inline __int8 _bsr_int64_(unsigned __int64 num) {    __int8 count=(sizeof(num)<<3)-1;    for(unsigned __int64 mask=1LLU<<count;!(num&mask)&&count>=0;count--,mask>>=1);    return count;}

基本的思路就是用for循环从最高位开始对每一位做与运算,找到第一个为1的位,就中止循环,count中就是结果,如果所有的位都为0,则count为-1;

注意这里1LLU<< count,
LLU限定前面的数字1为long long(64位),U限定为无符号类型(unsigned),就是指1为unsigned long long 型64位无符号整数。

如果是32位整数,代码也差不多

inline __int8 _bsr_int32_(unsigned __int32 num) {    __int8 count=(sizeof(num)<<3)-1;    for(unsigned __int32 mask=1U<<count;!(num&mask)&&count>=0;count--,mask>>=1);    return count;}

如果更懒一点,可以直接调用前面的_bsr_int64_

inline __int8 _bsr_int32_(unsigned __int32 num) {    return _bsr_int64_(num);}inline __int8 _bsr_int16_(unsigned __int16 num) {    return _bsr_int64_(num);}inline __int8 _bsr_int8_(unsigned __int8 num) {    return _bsr_int64_(num);}

gcc内建函数实现

gcc本身提供了丰富有用的内置函数(Built-in Functions)(点击打开gcc官网链接),在这些函数中我们发现一个对解决这个问题有用的函数

int __builtin_clz (unsigned int x)

下面这段是gcc官网的对这个函数的解释:

— Built-in Function: int __builtin_clz (unsigned int x)
Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.//返回前导的0的个数,如果x为0,则返回结果不确定。
— Built-in Function: int __builtin_clzll (unsigned long long)//ll后续的函数是参数为64位整数的版本
Similar to __builtin_clz, except the argument type is unsigned long long.

so,我有了新的想法,用这个函数来实现bsr

inline __int8 _bsr_int64_(unsigned __int64 num) {//num为0时直接返回-1,避免不确定的结果    return num==0?-1:(sizeof(num)<<3)-1-__builtin_clzll(num);}inline __int8 _bsr_int32_(unsigned __int32 num) {    return num==0?-1:(sizeof(num)<<3)-1-__builtin_clz(num);}inline __int8 _bsr_int16_(unsigned __int16 num) {    return _bsr_int64_(num);}inline __int8 _bsr_int8_(unsigned __int8 num) {    return _bsr_int64_(num);}

如果你不追求效率,那么下面的内容就可以跳过了(话说,如果不追求效率,干嘛用c/c++来写程序呢?)
唉,总觉得哪里不对,c语言对位的操作好麻烦。
其实,x86结构的cpu(386以上)的指令集中本身就有用于位扫描的指令bsf,bsr(点击链接百度百科)
bsf用于从低到高位扫描,bsr用于从高位到低位扫描
只用这一条汇编指令就能搞定前面那么多循环才能解决的问题。

于是我们可以用在c/c++内嵌汇编代码的方式实现上面的功能:

asm汇编实现

inline __int8 _bsr_int64_(unsigned __int64 num) {    __int64 count;    __asm__(            "bsrq %1, %0\n\t"//bsr和mov后面的q是指8字节数据宽度,每行汇编代码结尾都要加换行符\n\t            "jnz 1f\n\t"     //寄存器ZF标志为0,%0中结果有效直接跳转到标号1,f是指向前跳转            "movq $-1,%0\n\t"//寄存器ZF标志为1,代表所有的位都是0,所以返回-1            "1:"            :"=q"(count):"q"(num));    return count;}inline __int8 _bsr_int32_(unsigned __int32 num) {    __int32 count;    __asm__(            "bsrl %1, %0\n\t"//bsr和mov后面的l是指4字节数据宽度,            "jnz 1f\n\t"            "movl $-1,%0\n\t"            "1:"            :"=r"(count):"r"(num));    return count;}inline __int8 _bsr_int16_(unsigned __int16 num) {    __int16 count;    __asm__(            "bsrw %1, %0\n\t"//bsr和mov后面的w是指2字节数据宽度,            "jnz 1f\n\t"            "movw $-1,%0\n\t"            "1:"            :"=r"(count):"r"(num));    return count;}inline __int8 _bsr_int8_(unsigned __int8 num) {    return _bsr_int16_(num);}

gcc内嵌的汇编不是我们常见的intel汇编格式,而是at&t汇编格式,关于这方面的知识可以在网上找到很多参考资料如:

gcc内嵌汇编用法(点击打开链接)
AT&T汇编格式与Intel汇编格式的比较(点击打开链接)

因为bsr只是x86体系的指令,并不适用于其他平台,所以如果考虑代码跨平台开发,还是要把上面所有的代码结合起来用预编译宏重新封装。

跨平台封装

#if __x86_64__ //判断是否为x86_64结构,如果是则用内嵌汇编实现 inline __int8 _bsr_int64_(unsigned __int64 num) {    __int64 count;    __asm__(            "bsrq %1, %0\n\t"            "jnz 1f\n\t"            "movq $-1,%0\n\t"            "1:"            :"=q"(count):"q"(num));    return count;}inline __int8 _bsr_int32_(unsigned __int32 num) {    __int32 count;    __asm__(            "bsrl %1, %0\n\t"            "jnz 1f\n\t"            "movl $-1,%0\n\t"            "1:"            :"=r"(count):"r"(num));    return count;}inline __int8 _bsr_int16_(unsigned __int16 num) {    __int16 count;    __asm__(            "bsrw %1, %0\n\t"            "jnz 1f\n\t"            "movw $-1,%0\n\t"            "1:"            :"=r"(count):"r"(num));    return count;}inline __int8 _bsr_int8_(unsigned __int8 num) {    return _bsr_int16_(num);}#else #ifdef __GNUC__//gcc 编译时使用内建函数实现inline __int8 _bsr_int64_(unsigned __int64 num) {    return num==0?-1:(sizeof(num)<<3)-1-__builtin_clzll(num);}inline __int8 _bsr_int32_(unsigned __int32 num) {    return num==0?-1:(sizeof(num)<<3)-1-__builtin_clz(num);}inline __int8 _bsr_int16_(unsigned __int16 num) {    return _bsr_int64_(num);}inline __int8 _bsr_int8_(unsigned __int8 num) {    return _bsr_int64_(num);}#else//非x86_64体系,也非gcc编译时,则是用c实现,//如果你对cpu平台的汇编指令熟悉,也可以用如上面的方式用内嵌汇编实现inline __int8 _bsr_int64_(unsigned __int64 num) {    __int8 count=(sizeof(num)<<3)-1;    for(unsigned __int64 mask=1LLU<<count;!(num&mask)&&count>=0;count--,mask>>=1);    return count;}inline __int8 _bsr_int32_(unsigned __int32 num) {    return _bsr_int64_(num);}inline __int8 _bsr_int16_(unsigned __int16 num) {    return _bsr_int64_(num);}inline __int8 _bsr_int8_(unsigned __int8 num) {    return _bsr_int64_(num);}#endif#endif

__x86_64__和__GNUC__为gcc预定义的宏,关于预定义宏请参见:

查看gcc的默认宏定义命令(点击打开链接)

为更方便调用,还可以进一步利用c++的重载特性把这些代码封装成类,

c++封装

class _MyUtils {public:    static __int8 _bsr(unsigned __int64 num) {        return _bsr_int64_(num);    }    static __int8 _bsr(unsigned __int32 num) {        return _bsr_int32_(num);    }    static __int8 _bsr(unsigned __int16 num) {        return _bsr_int16_(num);    }    static __int8 _bsr(unsigned __int8 num) {        return _bsr_int8_(num);    }}MyUtils ;

然后就可以这样方便的调用

int res=MyUtils._bsr(88080LLU);//调用static __int8 _bsr(unsigned __int64 num)

看完这些内容,如果你想实现bsf(正向位扫描)指令,也可以在这个基础上如法炮制了。

请注意以上代码在mingw gcc 64位编译器下实现,在32位系统下,需要做相应修改。

0 0
原创粉丝点击