位运算对齐公式推导

为什么 ((addr + align - 1) / align) * align 能等价地变成 (addr + align - 1) & !(align - 1)?

核心前提:align 必须是 2 的幂

只有当 align = 2^k(比如 2、4、8、16…)时,这个转换才成立。如果不是 2 的幂,就不能用位运算。

第一步:理解"除以 2^k"和"对 2^k 取余"在位运算里是什么

先看一个关键事实:

align - 1 的低 k 位全是 1。

例如 align = 8 = 2^3,那么 align - 1 = 7 = 0b111,低 3 位全是 1。

由此推出两个等价关系:

addr % align      ==  addr & (align - 1)      // 取余 = 保留低 k 位
addr / align * align == addr & !(align - 1)   // 抹掉余数 = 低 k 位清零

为什么?因为 align 是 2 的幂,它的倍数在二进制里都是"低 k 位为 0"的数。

第二步:验证"& !(align-1) = 向下取整"

以 addr = 100, align = 8 为例:

100 = 0b0110_0100
  7 = 0b0000_0111
 !7 = 0b1111_1000

100 & !7 = 0b0110_0100 & 0b1111_1000
         = 0b0110_0000
         = 96

96 就是"100 向下取整到 8 的倍数"(96 ≤ 100,且 96 是 8 的倍数)。

所以:

addr & !(align - 1)  ==  (addr / align) * align   // 向下取整

第三步:把"向上取整"转成"向下取整"

我们要的是向上取整,不是向下。有一个经典技巧:

向上取整到 align 的倍数 = 向下取整到 (addr + align - 1) 的 align 倍数

也就是:

align_up(addr) = align_down(addr + align - 1)

为什么加 align - 1 就变成向上了?因为:

第四步:拼起来

把第三步的结论代入第二步的位运算:

向上取整 = align_down(addr + align - 1)
        = (addr + align - 1) & !(align - 1)

这就得到了最终公式。

完整推导链(一句话版)

((addr + align - 1) / align) * align     // 算术:向上取整
   ↓ 因为 align = 2^k
(addr + align - 1) & !(align - 1)        // 位运算:等价实现

核心就两条:

  1. 加 align - 1 → 把"向上取整"变成"向下取整"。
  2. & !(align - 1) → 把"除以 align 再乘 align(抹余数)"变成"低 k 位清零"。

一个更直观的类比

把地址想成"厘米",align=8 想成"只能以 8 厘米为单位的刻度尺"。

小结

你只要记住这条链:加(align-1) 变向上 → &!(align-1) 抹余数,就能自己推导出来,不用死记公式。