C语言位运算11道精选题从基本运算符到GPIO寄存器模拟附完整题库和答案前言最近在系统性地补 C 语言方向是嵌入式。位运算是嵌入式开发的基本功——STM32 配置 GPIO、操作寄存器、写驱动每一步都在跟位打交道。学到现在回头看最深的体会是位运算不是背公式是理解每个运算符在做什么然后自己推导出掩码。这篇是我在位运算专题的完整学习记录从最基础的置位清零到模拟 GPIO 寄存器操作一共 11 道题。专题结构四幕 11 题整个专题分四幕每幕解决一类问题幕一三把刀题1-3学会三个基本运算符——|置位、清零、^翻转幕二检验与组合题4-6在基本运算上构建更复杂的操作还藏了一道找 Bug 题幕三寄存器操作题7-9从单 bit 升级到多 bit 操作模拟真实寄存器读写幕四工具与综合题10-11写一个实用的 BitCount 函数然后在一个 GPIO 模拟场景里把所有函数串起来用幕一三把刀定位器的概念做位运算第一步永远是构造定位器——一个只有目标位是 1、其余位全是 0 的数字。1Ubit1U是无符号整数 1 bit让它左移 bit 位就得到了第 bit 位是 1其余全是 0的定位器。1U而不是1是因为在嵌入式里必须精确控制位数。int在不同平台可能是 16 位或 32 位而uint32_t永远是 32 位。置 1BitSet|运算符value|(1Ubit)|的性质跟 0 或不变跟 1 或变 1。定位器只有目标位是 1所以|之后只有那一位置 1其余位不受影响。清 0BitClear和~组合value~(1Ubit)的性质跟 1 与不变跟 0 与变 0。所以我们需要一个只有目标位是 0、其余位全是 1的掩码。先用1U bit构造目标位是 1再用~取反得到目标掩码最后用清零。翻转BitToggle^运算符value^(1Ubit)^的性质跟 1 异或翻转跟 0 异或不变。定位器直接拿来用不用取反。三个核心公式操作公式一句话置 1value | (1U bit)用|把目标位点亮清 0value ~(1U bit)用和~把目标位熄灭翻转value ^ (1U bit)用^把目标位翻面幕二检验与组合BitCheck 找 Bug题目给了一段有 bug 的实现return(value(1Ubit))value;// Bug!这个条件要求 value 整体等于(value mask)即 value 的其它位必须全是 0。比如 value0x09bit310x09 0x08 0x080x08 ! 0x09明明 bit3 是 1 却返回 false。修法很简单不需要跟整个 value 比检查提取出来的那一位是不是非零就行return(value(1Ubit))!0;// 正确BitWrite 依赖链BitWrite 要把某一位设成 0 或 1。不需要重新写直接复用 BitSet 和 BitClearif(val1)returnBitSet(value,bit);elsereturnBitClear(value,bit);这就是依赖链的设计思路——前面的函数是后面函数的积木。执行追踪x (x - 1)这道题不写代码是读代码回答问题。uint32_tf(uint32_tx){returnx(x-1);}x - 1会把 x 最低位的 1 变 0、它右边的 0 全变 1。再跟原值做就把最低位 1 精确消掉了。这个技巧在后面的 BitCount 中还用了一次算是整个专题最有用的一个 trick。幕三寄存器操作前面三题是单 bit 操作每次只改一位幕三升级到多 bit 操作——一次改多位mask 可以是不连续的散位。BitMaskSet 和 BitMaskClear其实就是题1、题2 的多 bit 版// 单bit: value | (1U bit)// 多bit: value | mask// 单bit: value ~(1U bit)// 多bit: value ~mask思路完全一样只是从一个位变成了一组位。BitMaskWrite清 0 写入这是整幕最难的一道题。要理解 mask 在这里起了两个作用清 0value ~mask把 mask 指定的位全部清 0截断mask new_val只取 new_val 中 mask 范围内的位其余忽略两步合并(value ~mask) | (mask new_val)幕四工具与综合BitCount统计 1 的个数我的第一版用了朴素遍历——循环 32 次每次检查一位uint8_tBitCount(uint32_tvalue){uint8_tcount0;uint8_tbit0;while(bit32){if((1Ubitvalue)(1Ubit))count;bit;}returncount;}通过了所有测试但有一个问题不管 value 里有多少个 1都固定跑 32 次。后来学到了Brian Kernighan 算法uint8_tBitCount(uint32_tvalue){uint8_tcount0;while(value){valuevalue-1;// 每次消掉最低位 1count;}returncount;}这就是题6 的x (x - 1)技巧的另一种用法。value 里有多少个 1 就循环多少次0x00010000 只跑 1 次比 32 次快得多。GPIO 寄存器模拟这道题不用写新代码。把 32 位值看成 32 个开关面板●1, ○0用前面写的全部函数轮流拨动它——置位、清零、翻转、批量改、整体覆盖。每步操作后打印 32 个格子的变化直观验证每个函数在位级别上是否真的产生了正确效果。所有函数 PASS 后跑一遍哪里不懂回对应题目再琢磨。我踩的坑1. assert 条件写反最开始写assert(bit 32 || bit 0)以为这样能保证 bit 在有效范围内。实际上 assert 是条件不成立时才炸。正确写法应该是assert(bit 32)。当 bit32 时bit 32为 true整个 assert 条件为 true不炸——所以没拦住越界。这就是把 assert 语义彻底搞反了。2. 运算符优先级vs在 BitCount 里写了if(1Ubitvalue1Ubit)// 错!因为优先级高于所以实际解析成了if(1Ubit(value1Ubit))// 不是我要的!value 1U bit返回 0 或 1再跟1U bit做——完全不是我想的比较逻辑。正确写法要加括号if((1Ubitvalue)1Ubit)// 正确这个坑非常隐蔽编译器不会报错只是逻辑悄悄错了。3. BitClear 和 BitToggle 搞混清零用 ~翻转用^。只有清零需要取反掩码。一开始总是写到一半不确定该用哪个后来自己总结了一张表操作定位器运算符置 11Ubit|清 0~(1Ubit)翻转1Ubit^4. BitMaskWrite 要先清 0 再写入一开始想着直接value | (mask new_val)这样 mask 指定的位如果是 0 才能正确写入但如果原值是 1 就不对了——需要先清 0 再写入两步缺一不可。最终成绩55 PASS 0 FAIL 55 总计11 道题全部通过包括 5 个 assert 边界检查和位操作实验台。代码仓库完整的题目、测试、参考答案都在 GitHub 上c-practice-problemsgitclone https://github.com/ZhangXiaolin-persist/c-practice-problems.gitcdc-practice-problems/01_位运算# 打开 BitOps.c 写代码make# 自动判题下一步位运算专题收工接下来进入函数指针专题。目标是能手写一个基于函数指针的状态机比如 AT 指令解析器这也是嵌入式面试的常见考点。
C语言位运算11道精选题:从基本运算符到GPIO寄存器模拟,附完整题库和答案
发布时间:2026/5/31 1:02:26
C语言位运算11道精选题从基本运算符到GPIO寄存器模拟附完整题库和答案前言最近在系统性地补 C 语言方向是嵌入式。位运算是嵌入式开发的基本功——STM32 配置 GPIO、操作寄存器、写驱动每一步都在跟位打交道。学到现在回头看最深的体会是位运算不是背公式是理解每个运算符在做什么然后自己推导出掩码。这篇是我在位运算专题的完整学习记录从最基础的置位清零到模拟 GPIO 寄存器操作一共 11 道题。专题结构四幕 11 题整个专题分四幕每幕解决一类问题幕一三把刀题1-3学会三个基本运算符——|置位、清零、^翻转幕二检验与组合题4-6在基本运算上构建更复杂的操作还藏了一道找 Bug 题幕三寄存器操作题7-9从单 bit 升级到多 bit 操作模拟真实寄存器读写幕四工具与综合题10-11写一个实用的 BitCount 函数然后在一个 GPIO 模拟场景里把所有函数串起来用幕一三把刀定位器的概念做位运算第一步永远是构造定位器——一个只有目标位是 1、其余位全是 0 的数字。1Ubit1U是无符号整数 1 bit让它左移 bit 位就得到了第 bit 位是 1其余全是 0的定位器。1U而不是1是因为在嵌入式里必须精确控制位数。int在不同平台可能是 16 位或 32 位而uint32_t永远是 32 位。置 1BitSet|运算符value|(1Ubit)|的性质跟 0 或不变跟 1 或变 1。定位器只有目标位是 1所以|之后只有那一位置 1其余位不受影响。清 0BitClear和~组合value~(1Ubit)的性质跟 1 与不变跟 0 与变 0。所以我们需要一个只有目标位是 0、其余位全是 1的掩码。先用1U bit构造目标位是 1再用~取反得到目标掩码最后用清零。翻转BitToggle^运算符value^(1Ubit)^的性质跟 1 异或翻转跟 0 异或不变。定位器直接拿来用不用取反。三个核心公式操作公式一句话置 1value | (1U bit)用|把目标位点亮清 0value ~(1U bit)用和~把目标位熄灭翻转value ^ (1U bit)用^把目标位翻面幕二检验与组合BitCheck 找 Bug题目给了一段有 bug 的实现return(value(1Ubit))value;// Bug!这个条件要求 value 整体等于(value mask)即 value 的其它位必须全是 0。比如 value0x09bit310x09 0x08 0x080x08 ! 0x09明明 bit3 是 1 却返回 false。修法很简单不需要跟整个 value 比检查提取出来的那一位是不是非零就行return(value(1Ubit))!0;// 正确BitWrite 依赖链BitWrite 要把某一位设成 0 或 1。不需要重新写直接复用 BitSet 和 BitClearif(val1)returnBitSet(value,bit);elsereturnBitClear(value,bit);这就是依赖链的设计思路——前面的函数是后面函数的积木。执行追踪x (x - 1)这道题不写代码是读代码回答问题。uint32_tf(uint32_tx){returnx(x-1);}x - 1会把 x 最低位的 1 变 0、它右边的 0 全变 1。再跟原值做就把最低位 1 精确消掉了。这个技巧在后面的 BitCount 中还用了一次算是整个专题最有用的一个 trick。幕三寄存器操作前面三题是单 bit 操作每次只改一位幕三升级到多 bit 操作——一次改多位mask 可以是不连续的散位。BitMaskSet 和 BitMaskClear其实就是题1、题2 的多 bit 版// 单bit: value | (1U bit)// 多bit: value | mask// 单bit: value ~(1U bit)// 多bit: value ~mask思路完全一样只是从一个位变成了一组位。BitMaskWrite清 0 写入这是整幕最难的一道题。要理解 mask 在这里起了两个作用清 0value ~mask把 mask 指定的位全部清 0截断mask new_val只取 new_val 中 mask 范围内的位其余忽略两步合并(value ~mask) | (mask new_val)幕四工具与综合BitCount统计 1 的个数我的第一版用了朴素遍历——循环 32 次每次检查一位uint8_tBitCount(uint32_tvalue){uint8_tcount0;uint8_tbit0;while(bit32){if((1Ubitvalue)(1Ubit))count;bit;}returncount;}通过了所有测试但有一个问题不管 value 里有多少个 1都固定跑 32 次。后来学到了Brian Kernighan 算法uint8_tBitCount(uint32_tvalue){uint8_tcount0;while(value){valuevalue-1;// 每次消掉最低位 1count;}returncount;}这就是题6 的x (x - 1)技巧的另一种用法。value 里有多少个 1 就循环多少次0x00010000 只跑 1 次比 32 次快得多。GPIO 寄存器模拟这道题不用写新代码。把 32 位值看成 32 个开关面板●1, ○0用前面写的全部函数轮流拨动它——置位、清零、翻转、批量改、整体覆盖。每步操作后打印 32 个格子的变化直观验证每个函数在位级别上是否真的产生了正确效果。所有函数 PASS 后跑一遍哪里不懂回对应题目再琢磨。我踩的坑1. assert 条件写反最开始写assert(bit 32 || bit 0)以为这样能保证 bit 在有效范围内。实际上 assert 是条件不成立时才炸。正确写法应该是assert(bit 32)。当 bit32 时bit 32为 true整个 assert 条件为 true不炸——所以没拦住越界。这就是把 assert 语义彻底搞反了。2. 运算符优先级vs在 BitCount 里写了if(1Ubitvalue1Ubit)// 错!因为优先级高于所以实际解析成了if(1Ubit(value1Ubit))// 不是我要的!value 1U bit返回 0 或 1再跟1U bit做——完全不是我想的比较逻辑。正确写法要加括号if((1Ubitvalue)1Ubit)// 正确这个坑非常隐蔽编译器不会报错只是逻辑悄悄错了。3. BitClear 和 BitToggle 搞混清零用 ~翻转用^。只有清零需要取反掩码。一开始总是写到一半不确定该用哪个后来自己总结了一张表操作定位器运算符置 11Ubit|清 0~(1Ubit)翻转1Ubit^4. BitMaskWrite 要先清 0 再写入一开始想着直接value | (mask new_val)这样 mask 指定的位如果是 0 才能正确写入但如果原值是 1 就不对了——需要先清 0 再写入两步缺一不可。最终成绩55 PASS 0 FAIL 55 总计11 道题全部通过包括 5 个 assert 边界检查和位操作实验台。代码仓库完整的题目、测试、参考答案都在 GitHub 上c-practice-problemsgitclone https://github.com/ZhangXiaolin-persist/c-practice-problems.gitcdc-practice-problems/01_位运算# 打开 BitOps.c 写代码make# 自动判题下一步位运算专题收工接下来进入函数指针专题。目标是能手写一个基于函数指针的状态机比如 AT 指令解析器这也是嵌入式面试的常见考点。