当前位置: 首页 > news >正文

位运算的奇技淫巧:builtin内建函数

什么是内建函数:

GCC 编译器自带的内建函数。这些_builtin*形式的内建函数一般是基于不同硬件平台采用专门的硬件指令实现的,因此性能较高。


介绍一些常用的内建函数:

__builtin_ctz()

返回从最低位开始(右起)的连续 0 的个数

根据数据类型不同有:

int __builtin_ctz (unsigned int x)
int __builtin_ctzl (unsigned long x)
int __builtin_ctzll (unsigned long long x)

参数 x 虽然都是无符号整型,但是值必须大于0(0 怎么也找不到 1 吧),否则会发生无法定义的后果。

example:

unsigned int x = 0x0820; // 0000 1000 0010 0000
unsigned int zero = __builtin_ctz(x); // zero = 5

__builtin_clz()

返回从最高位开始(左起)的连续 0 的个数

根据数据类型不同有:

int __builtin_clz (unsigned int x)
int __builtin_clzl (unsigned long x)
int __builtin_clzll (unsigned long long x)

参数 x 虽然都是无符号整型,但是值必须大于0(0 怎么也找不到 1 吧),否则会发生无法定义的后果。

example:

unsigned int x = 0x0820; // 0000 1000 0010 0000
unsigned int zero = __builtin_clz(x); // zero = 4

__builtin_ffs()

返回输入数二进制表示的最低非 0 位的下标,下标从 1 开始计数;如果传入 0 则返回 0。

根据数据类型不同有:

int __builtin_ffs (unsigned int x)
int __builtin_ffsl (unsigned long x)
int __builtin_ffsll (unsigned long long x)

example:

unsigned int x = 0x0820; // 0000 1000 0010 0000
unsigned int index = __builtin_ffs(x); // index = 5

除 0 外,发现传入其他的数,值就是 __builtin_ctz()+1,实际函数实现也是如此。

__builtin_popcount()

返回输入的二进制表示中 1 的个数;如果传入 0 则返回 0 。

根据数据类型不同有:

int __builtin_popcount (unsigned int x)
int __builtin_popcountl (unsigned long x)
int __builtin_popcountll (unsigned long x)

example:

unsigned int x = 0x0820; // 0000 1000 0010 0000
unsigned int count = __builtin_popcount(x); // count = 2

解决实际项目的例子

使用两字节(16 位)即可控制 16 个任意 IO 口的输出状态

例如设立一个 uint16_t ioCtrl = 0xC8A6; // 1100 1000 1010 0110,那么就是对第 1、2、5、9、11、14、15 位 IO 进行控制,再设立一个 uint16_t ioValue = 0x0C9D; // 0000 1100 1001 1101,来表示 16 位 IO 的状态。

常规的写法是使用循环来对每一个控制的 IO 写入值:

void IOCtrl_WriteIO(uint16_t ioCtrl, uint16_t ioValue)
{for (uint16_t i = 0; i < 16; i++){if (ioCtrl & (1 << i)){WriteIO(IO[i].port, IO[i].pin, ioValue & (1 << i));}}
}

这样很简单的就实现使用两字节就管理了 16 个 IO 的状态,但是我们发现即使只控制 1 个 IO,例如 0x0800,也需要去遍历整个 16 位效率很低,当结合内建函数来写:

void IOCtrl_WriteIO(uint16_t ioCtrl, uint16_t ioValue)
{while (ioCtrl){// 找到 ioCtrl 最低的置位 bituint16_t i = __builtin_ctz(ioCtrl);WriteIO(IO[i].port, IO[i].pin, (ioValue >> i) & 1);ioCtrl &= ioCtrl - 1; // 将最后一位1置0}
}

只会循环 1 的个数次数,而不用每次都遍历整个 16 位。

扩展

ioCtrl &= ioCtrl - 1是怎么将最后一位 1 置 0 的?

假设 ioCtrl = 0xA8; // 1010 1000

0xA8 - 1 = 0xA7,转成 2 进制后是 1010 0111

0xA8:1010 1000

0xA7:1010 0111

位与操作后就变成了 1010 0000,如此就将最后一位 1 置 0 了。

如何判断一个数是否是 2 的幂?

在位运算里,一个数如果是 2 的幂,那么它的二进制形式一定是:

1, 10, 100, 1000, ...

如果这个数是 2 的幂,那么这个数一定只存在一个 1,对最后一位 1 置 0 后,这个数就变成了 0

bool isPowerOfTwo(uint32_t x)
{return x & (x - 1) == 0;
}

前面提过内联函数 __builtin_popcount()可以返回 1 的个数,使用此内联函数也可快速判断是否是 2 的幂

bool isPowerOfTwo(uint32_t x)
{return __builtin_popcount(x) == 1;
}
http://www.hskmm.com/?act=detail&tid=20363

相关文章:

  • 数据类型-列表
  • 智表 ZCELL:纯前端 Excel 导入导出的高效解决方案,让数据处理更轻松
  • 【MySQL 高阶】MySQL 架构与存储引擎全面详解 - 实践
  • ISO 雨晨 26200.6588 Windows 11 企业版 LTSC 25H2 自用 edge 140.0.3485.81 - 教程
  • lc1039-多边形三角剖分的最低得分
  • Powershell 进阶语(三)
  • 随机函数
  • 集合进阶-collection集合
  • 115. 不同的子序列
  • 素数定理的初等证明
  • Spring Boot项目中集成MyBatis-Plus
  • 深入解析:ShellExtensionU.dll COMToolKit.dll CardRes.dll grubinst.exe vbar332.dll Vb5db.dll dao360.dll
  • 我不懂 愈完美愈是空洞 挂着张可憎面容 维系虚假脆弱的梦
  • 51c自动驾驶~合集33 - 详解
  • VSCod安装esp-idf插件 ERROR_INVALID_PIP错误解决
  • 一款在线免费 PDF AI 工具平台,PDF 拆分,合并,加水印,PDF与Word、Excel、PPT、图片、TXT、HTML、Markdown互转的在线AI工具
  • 计算机核心课
  • 【SimpleFOC】vofa+监控电机数据
  • ubuntu虚拟机磁盘扩展
  • 数学知识
  • The 3rd Universal Cup. Stage 23: Hong Kong
  • 从0到1搭建高隐蔽性C2基础设施
  • RESTful风格
  • 软工9.27
  • 一些积分的题解
  • 2025 年超声波清洗机最新权威推荐排行榜:龙门式 / 悬挂式 / 全自动等多类型设备 TOP3 品牌深度解析与选购指南
  • 问题总结,软工9.28
  • 数据类型-字符串
  • 在AI技术唾手可得的时代,挖掘新需求成为制胜关键——某知名益智游戏框架需求探索
  • 详细介绍:零基础学AI大模型之LangChain六大核心模块与大模型IO交互链路