← 返回笔记

C 位掩码:从一个寄存器位开始

用可验证的内存示例理解置位、清位与状态判断,再明确真实硬件的边界。

本文为站点内容示例。代码只操作普通内存变量,用来演示思路,不代表真实开发板或项目经历。

先写出希望保留什么

假设一个三十二位数值的第三级位控制某项功能,位编号从零开始。初始值是十六进制的 0x20,也就是第五位已经置位。目标不是给整个数值重新赋值,而是在保留第五位的同时,单独改变第三位。先把这句话写清楚,比直接记住运算符更有帮助。

这里的掩码只在目标位置为一。按位或用于置位,按位与配合取反用于清位;检查状态时,把按位与的结果与零比较。代码中的断言就是预期结果,可以先写断言,再补上操作。

#include <assert.h>
#include <stdint.h>

int main(void) {
    uint32_t value = UINT32_C(0x20);
    const uint32_t mask = UINT32_C(1) << 3;

    value |= mask;
    assert(value == UINT32_C(0x28));
    assert((value & mask) != 0);

    // 原有的第五位需要保留,因此不能直接把整个值清零。
    value &= ~mask;
    assert(value == UINT32_C(0x20));
    return 0;
}

用一行图检查推导

只画最低八位,就能看出每一步保留了什么。将纸面结果与断言对照,有助于区分“掩码写错”和“运算符选错”。

初始值  0010 0000
掩码    0000 1000
置位后  0010 1000
清位后  0010 0000

再走向真实寄存器

这个练习先验证普通整数的位运算。迁移到外设之前,还需要阅读具体芯片手册,确认位宽、保留位以及读写语义。例如“写一清零”的状态位,不能照搬普通变量的清位方法。若主程序和中断都修改同一个寄存器,也要单独检查读、修改、写回之间是否可能互相覆盖。

在笔记里保留三样内容:一个明确的初始值、一个目标变化和一个验证结果。下一次遇到多位字段,可以先画出字段掩码,再补上边界条件,而不是复制上一段寄存器代码。对于移位操作,还应检查位号是否处于类型允许的范围内。

还可以给同一段练习换两个输入:目标位原本已经置位,以及所有位都是零。前者检查重复置位是否仍保留其他位,后者帮助观察掩码究竟影响了哪里。先在纸面上预测,再运行断言;两个过程对应起来,才算完成这一次最小练习。

关于运算符的语言定义,可查阅 C11 委员会草案 N1570 的 6.5.7 至 6.5.12 节。