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

2022-05-07 09:57:49 浏览数 (1)

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

c语言实现

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

代码语言:javascript复制
//对一个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位整数,代码也差不多

代码语言:javascript复制
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_

代码语言:javascript复制
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官网链接),在这些函数中我们发现一个对解决这个问题有用的函数

代码语言:javascript复制
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

代码语言:javascript复制
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汇编实现

代码语言:javascript复制
inline __int8 _bsr_int64_(unsigned __int64 num) {
    __int64 count;
    __asm__(
            "bsrq %1, %0nt"//bsr和mov后面的q是指8字节数据宽度,每行汇编代码结尾都要加换行符nt
            "jnz 1fnt"     //寄存器ZF标志为0,%0中结果有效直接跳转到标号1,f是指向前跳转
            "movq $-1,%0nt"//寄存器ZF标志为1,代表所有的位都是0,所以返回-1
            "1:"
            :"=q"(count):"q"(num));
    return count;
}
inline __int8 _bsr_int32_(unsigned __int32 num) {
    __int32 count;
    __asm__(
            "bsrl %1, %0nt"//bsr和mov后面的l是指4字节数据宽度,
            "jnz 1fnt"
            "movl $-1,%0nt"
            "1:"
            :"=r"(count):"r"(num));
    return count;
}
inline __int8 _bsr_int16_(unsigned __int16 num) {
    __int16 count;
    __asm__(
            "bsrw %1, %0nt"//bsr和mov后面的w是指2字节数据宽度,
            "jnz 1fnt"
            "movw $-1,%0nt"
            "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体系的指令,并不适用于其他平台,所以如果考虑代码跨平台开发,还是要把上面所有的代码结合起来用预编译宏重新封装。

跨平台封装

代码语言:javascript复制
#if __x86_64__ //判断是否为x86_64结构,如果是则用内嵌汇编实现 
inline __int8 _bsr_int64_(unsigned __int64 num) {
    __int64 count;
    __asm__(
            "bsrq %1, %0nt"
            "jnz 1fnt"
            "movq $-1,%0nt"
            "1:"
            :"=q"(count):"q"(num));
    return count;
}
inline __int8 _bsr_int32_(unsigned __int32 num) {
    __int32 count;
    __asm__(
            "bsrl %1, %0nt"
            "jnz 1fnt"
            "movl $-1,%0nt"
            "1:"
            :"=r"(count):"r"(num));
    return count;
}
inline __int8 _bsr_int16_(unsigned __int16 num) {
    __int16 count;
    __asm__(
            "bsrw %1, %0nt"
            "jnz 1fnt"
            "movw $-1,%0nt"
            "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 封装

代码语言:javascript复制
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 ;

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

代码语言:javascript复制
int res=MyUtils._bsr(88080LLU);//调用static __int8 _bsr(unsigned __int64 num)

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

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

0 人点赞