strlen 小品一则
计算字符串长度还在一个字节一个字节比对 0x00?
据说 glibc 是这样的 strlen 实现,一次性判断例如4个字节(32位),看其中是否存在 0x00,再仔细查找。
例如我们是32位的寄存器,那么可以一次查找4个字节。
uint32_t no_zero_byte(uint32_t v)
{
const uint32_t himagic = 0x80808080;
const uint32_t lomagic = 0x01010101;
return ((v - lomagic) & ~v & himagic);
}
这是啥玩意?因为0x00-0x01会产生借位,而借位后的值是0xFF,与0x00取反相同,围绕这个点展开计算。
首先字符串是连续的字节地址,那么可以直接当作32位值进行读取,记作 v
。
然后若某个字节是0x00,那么当个字节减去0x01应当会发生借位,也就是 (v - lomagic)
。使用 & ~v
可以判断是否发生了借位。
如果是超出最高位的借位,那么是0xFF,则使用 & himagic
再判断。
假设理解不了那么结合这个实例
如果现在判断到的字符串4字节是 {0x11, 0x22, 0x33, 0x00}
那么直接取址转换 v 是 0x00332211
v - 0x01010101 = 0xFF322110
~v = 0xFFCCDDEE
(v - 0x01010101) & ~v = 0xFF000100 // 意味着发生了借位
FF000100 & 0x80808080 = 0x80000000 // 意为着发生了超出最高位的借位,也就是找到了0x00的位置,是第4个字节
如果现在判断到的字符串4字节是 {0x11, 0x22, 0x33, 0x44}
那么直接取址转换 v 是 0x44332211
v - 0x01010101 = 0x43322110
~v = 0xBBCCDDEE
(v - 0x01010101) & ~v = 0x03000100
优势
某些芯片中,类似 if(v == 0x00) 这样的判断非常耗时,使用这样单纯的位计算得值则可以避免这个问题。