【问题标题】:How the glibc strlen() implementation works [duplicate]glibc strlen() 实现如何工作[重复]
【发布时间】:2013-11-30 00:37:24
【问题描述】:

来自 K&R 的 strlen() 只需要几行代码。

int strlen(char *s)
{
    char *p = s;
    while (*p != '\0')
        p++;
    return p - s;
}

但是glibc version 要长得多。为简单起见,我删除了所有的 cmets 和 64 位实现,提取的版本 strlen() 如下所示:

size_t strlen(const char *str)
{
    const char *char_ptr;
    const unsigned long int *longword_ptr;
    unsigned long int longword, magic_bits, himagic, lomagic;

    for (char_ptr = str; ((unsigned long int) char_ptr 
             & (sizeof (longword) - 1)) != 0; ++char_ptr)
       if (*char_ptr == '\0')
           return char_ptr - str;

    longword_ptr = (unsigned long int *) char_ptr;

    himagic = 0x80808080L;
    lomagic = 0x01010101L;

    for (;;)
    { 
        longword = *longword_ptr++;

        if (((longword - lomagic) & himagic) != 0)
        {
            const char *cp = (const char *) (longword_ptr - 1);

            if (cp[0] == 0)
                return cp - str;
            if (cp[1] == 0)
                return cp - str + 1;
            if (cp[2] == 0)
                return cp - str + 2;
            if (cp[3] == 0)
                return cp - str + 3;
        }
    }
}

在非常有帮助的评论(点击here)的帮助下,我了解了它的大部分工作原理。 glibc strlen() 不是逐字节检查'\0',而是检查每个字(32 位机器中为 4 个字节,64 位机器中为 8 个字节)。这样,当字符串比较长的时候,可以提高性能。

它通过逐字节读取来检查前几个字符,直到char_ptrlongword 边界对齐。然后它使用一个循环来检查longword 是否有任何全零字节。如果有,检查哪个字节,并返回结果。

我不明白的部分是,这如何检查longword 中的一个字节是否全为零?

if (((longword - lomagic) & himagic) != 0)

我可以构建一个0x81818181longword 值,它可以使0x81818181 - 0x01010101) & 0x80808080 不等于0,但是没有全零字节。

这是否与 ASCII 值范围从 0127 的事实有关,所以 0x81 不是有效的 ASCII?但我不认为 C 标准强制字符串使用 ASCII。

【问题讨论】:

标签: c string performance glibc


【解决方案1】:

我想通了。没想到这么简单,我花了最后半个小时搞定。

检查没问题

if (((longword - lomagic) & himagic) != 0)

让像0x81818181 这样的值通过,因为如果它通过了,下面对每个字节的测试将不会返回,因为没有全零字节。所以循环可以继续测试下一个longword


检查背后的算法是基于Determine if a word has a zero byte

unsigned int v; 
bool hasZeroByte = ~((((v & 0x7F7F7F7F) + 0x7F7F7F7F) | v) | 0x7F7F7F7F);

在 2 的补码中,- 0x01010101+ 0xFEFEFEFF 具有相同的效果。不同之处在于glibc 没有v & 0x7F7F7F7F,这确保了字中的字节具有0 的最高有效位。这可以防止像 0x81818181 这样的例子,但是 glibc 省略了它,因为它不必像前面所说的那样让它通过,只要它不会错过任何具有全零字节的单词,检查就是正确的。

【讨论】:

  • 他们本可以使用(((v) - 0x01010101UL) & ~(v) & 0x80808080UL),它不会给出误报,但需要两个额外的操作。我想已经做出了妥协,因为没有与~(v) 进行 AND 运算,它适用于 ASCII 字符串,并且处理的大量信息是 ASCII。
  • @HristoIliev 是的,我猜他们选择不这样做是因为现实生活中的大多数字符串实际上只包含 ASCII 值。
  • 减去 0x01010101 相当于加上 0xFEFEFEFF。 0xFE 允许对任何不是 0x00 的字节进行进位。
  • @godel9 你是对的,它们是不同的,我已经解决了。谢谢。
  • 我很高兴提出原始问题的用户弄明白了,但对于那些不熟悉“bit twiddling”并且会仍然想了解发生了什么。
猜你喜欢
  • 2023-03-17
  • 2018-11-12
  • 1970-01-01
  • 2020-05-07
  • 1970-01-01
  • 2012-08-01
  • 1970-01-01
  • 2011-06-10
  • 1970-01-01
相关资源
最近更新 更多