为什么strlen()的实现有效?

(免责声明:我已经看到了这个问题 ,我并没有重新询问它 – 我对代码的工作原理感兴趣,而不是它的工作方式。)

所以这是Apple的(好吧,FreeBSD的) strlen() 。 它使用一个众所周知的优化技巧,即它一次检查4或8个字节,而不是与0进行逐字节比较:

 size_t strlen(const char *str) { const char *p; const unsigned long *lp; /* Skip the first few bytes until we have an aligned p */ for (p = str; (uintptr_t)p & LONGPTR_MASK; p++) if (*p == '\0') return (p - str); /* Scan the rest of the string using word sized operation */ for (lp = (const unsigned long *)p; ; lp++) if ((*lp - mask01) & mask80) { p = (const char *)(lp); testbyte(0); testbyte(1); testbyte(2); testbyte(3); #if (LONG_BIT >= 64) testbyte(4); testbyte(5); testbyte(6); testbyte(7); #endif } /* NOTREACHED */ return (0); } 

现在我的问题是:也许我错过了明显的,但是这不能读过字符串的结尾吗? 如果我们有一个长度不能被字大小整除的字符串怎么办? 想象一下以下场景:

 || +-------------+-------------+-------------+-------------+-------------+ - - | 'A' | 'B' | 'C' | 'D' | 0 | +-------------+-------------+-------------+-------------+-------------+ - - ^ ^^ | || +------------------------------------------------------++-------------- - - long word #1 long word #2 

当读取第二个长字时,程序访问实际上不应该访问的字节…这不是错误的吗? 我非常有信心Apple和BSD的人都知道他们在做什么,所以有人可以解释为什么这是正确的吗?

我注意到的一件事是, 啤酒男孩认为这是未定义的行为 ,我也相信它确实是,但他被告知它不是,因为“我们使用初始for循环调整字大小”(未显示)这里)。 但是,我根本没有看到为什么如果数组不够长并且我们正在阅读它的末尾,那么对齐将是任何相关的。

虽然这在技术上是未定义的行为,但实际上没有本机架构以比字大小更精细的粒度检查越界内存访问。 因此,虽然通过终结器的垃圾可能最终被读取,但结果不会是崩溃。

我根本没有看到为什么如果数组不够长并且我们正在阅读它的结尾,那么对齐将是任何相关的。

例程开始于对齐字边界有两个原因:首先,在大多数体系结构中读取对齐地址的字更快(并且在几个CPU上也是强制性的 )。 速度提升足以在大量类似的操作中使用相同的技巧:memcpy,strcpy,memmove,memchr等。

第二:如果你继续读取从单词边界开始的单词 ,你可以确保字符串的其余部分驻留在同一个内存页面中。 字符串(包括其终止零)不能跨越存储器页面边界,也不能读取单词。 (1)

所以这总是最快和最安全的,即使内存页面粒度是sizeof(LONG_BIT)(它不是)。

拾取字符串末尾附近的整个单词可能会在最后的零之后拾取额外的字节,但是从有效存储器读取未定义的字节不是UB – 仅对其内容起作用是(2)。 如果该字在内部的任何地方都包含零终止符,则使用test_byte检查各个字节,并且如原始源中所示,这将永远不会对终结符后的字节起作用

(1)显然他们可以,但我的意思是“永远不会进入锁定的页面”或类似的东西。

(2)正在讨论中。 在Sneftel的回答下看到(对不起!)。