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

在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位系统下,需要做相应修改。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
GCC内联汇编asm格式是一种将汇编代码嵌入到C或C++源代码中的方法。它允许开发人员直接使用汇编语言来访问底层硬件或执行高性能算法。以下是GCC内联汇编asm格式的详细说明。 基本格式 GCC内联汇编asm格式基本格式如下: ```c asm (assembly code : output operands : input operands : clobbered registers); ``` - assembly code:汇编代码,可以是单行或多行代码。 - output operands:用于存储计算结果的变量,可以有多个,用逗号分隔。输出操作数是可选的,可以省略。 - input operands:用于传递参数的变量,可以有多个,用逗号分隔。输入操作数是必需的。 - clobbered registers:代码执行期间会被修改的寄存器,用于通知编译器。可以有多个,用逗号分隔。clobbered registers是可选的,可以省略。 示例 以下是一个简单的GCC内联汇编asm格式示例,将eax寄存器中的值加1,并将结果存储在eax中。 ```c int value = 10; asm ("addl $1, %%eax" : "=a" (value) : "a" (value)); ``` - "addl $1, %%eax":汇编代码,将eax加1。 - "=a" (value):输出操作数,将eax中的值存储在value变量中。 - "a" (value):输入操作数,将value的值传递给eax。 - 没有clobbered registers。 输出操作数 输出操作数用于将汇编代码的结果存储在变量中。输出操作数有两种类型:普通输出(通道约束)和跨约束输出。 普通输出 普通输出使用“=约束”语法表示,其中约束指定了输出操作数应存储在哪个寄存器或内存置中。约束可以是以下之一: - "=r"(任意寄存器) - "=m"(任意内存置) - "=a"(eax寄存器) - "=d"(edx寄存器) - "=q"(eax或edx寄存器) 示例 以下是一个使用普通输出的示例,将eax寄存器中的值加1,并将结果存储在value变量中。 ```c int value; asm ("addl $1, %%eax" : "=a" (value) : "a" (value)); ``` 跨约束输出 跨约束输出是一种将结果存储在多个输出变量中的方法。它使用“+约束”语法表示,其中约束指定了输出操作数应存储在哪个寄存器或内存置中。多个约束可以用逗号分隔。 示例 以下是一个使用跨约束输出的示例,将eax寄存器中的值加1,并将结果存储在value1和value2变量中。 ```c int value1, value2; asm ("addl $1, %%eax" : "+a" (value1), "=r" (value2)); ``` 输入操作数 输入操作数用于将变量的值传递给汇编代码。输入操作数使用“约束”语法表示,其中约束指定了变量应该存储在哪个寄存器或内存置中。约束可以是以下之一: - "r"(任意寄存器) - "m"(任意内存置) - "a"(eax寄存器) - "d"(edx寄存器) - "q"(eax或edx寄存器) 示例 以下是一个使用输入操作数的示例,将value变量的值传递给eax寄存器中。 ```c int value = 10; asm ("movl %0, %%eax" : : "r" (value)); ``` clobbered registers clobbered registers是在汇编代码执行期间会被修改的寄存器列表。它用于通知编译器哪些寄存器应该被保存和恢复。clobbered registers使用“%约束”语法表示,其中约束指定了被修改的寄存器名称。多个寄存器可以用逗号分隔。 示例 以下是一个使用clobbered registers的示例,将eax寄存器中的值加1,并告诉编译器edx寄存器也被修改了。 ```c asm ("addl $1, %%eax" : : "a" (value) : "%edx"); ``` 总结 GCC内联汇编asm格式是一种将汇编代码嵌入到C或C++源代码中的方法。它允许开发人员直接使用汇编语言来访问底层硬件或执行高性能算法。通过输出操作数、输入操作数和clobbered registers,开发人员可以管理汇编代码与C或C++代码之间的数据流和寄存器使用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值