0

我正在查看来自 GNU C 库 glibc-2.18 的函数,这是我为 strncmp.c 找到的代码

看着它,我不明白为什么它是这样写的。这个循环展开了吗?为什么不使用 5 或 10 而不是 4?为什么他们这样写而不是使用更直接的方法?

/* Compare no more than N characters of S1 and S2,
   returning less than, equal to or greater than zero
   if S1 is lexicographically less than, equal to or
   greater than S2.  */
int
STRNCMP (const char *s1, const char *s2, size_t n)
{
  unsigned char c1 = '\0';
  unsigned char c2 = '\0';

  if (n >= 4)
    {
      size_t n4 = n >> 2;
      do
    {
      c1 = (unsigned char) *s1++;
      c2 = (unsigned char) *s2++;
      if (c1 == '\0' || c1 != c2)
        return c1 - c2;
      c1 = (unsigned char) *s1++;
      c2 = (unsigned char) *s2++;
      if (c1 == '\0' || c1 != c2)
        return c1 - c2;
      c1 = (unsigned char) *s1++;
      c2 = (unsigned char) *s2++;
      if (c1 == '\0' || c1 != c2)
        return c1 - c2;
      c1 = (unsigned char) *s1++;
      c2 = (unsigned char) *s2++;
      if (c1 == '\0' || c1 != c2)
        return c1 - c2;
    } while (--n4 > 0);
      n &= 3;
    }

  while (n > 0)
    {
      c1 = (unsigned char) *s1++;
      c2 = (unsigned char) *s2++;
      if (c1 == '\0' || c1 != c2)
    return c1 - c2;
      n--;
    }

  return c1 - c2;
}

有人可以解释代码背后的逻辑吗?

谢谢。

4

3 回答 3

2

是的,它是一个部分展开的循环。

4:

1)一般大小:在更多分支(小数字)和更大代码大小(大数字)之间总是存在权衡。

2)具体大小(4,而不是 5):正如 Joachim Isaksson 指出的那样,>>如果选择 5 而不是 4,则需要将 before 循环更改为除法。他说,在某些(例如嵌入式)CPU 上,这变得很重要。(通常我们只考虑循环的成本,而不是它的设置,但他可能是对的,因为大多数字符串往往都很小!)

请注意,如果您想将循环展开为任何数字(无论是否为 2 的幂),那么您可以 - 而不是进行除法并保持n4- 您可以计算端点s1并在最后检查s1它:

char* s1end = s1 + n;
do {...} while(s1 < s1end);
于 2013-12-15T08:43:53.257 回答
1

这种方式有更少的分支(if 语句),它允许编译器针对管道进行优化。

于 2013-12-15T08:43:59.520 回答
1

是的,这是循环展开。

为什么是 4 而不是 5 或 10。可能是因为这是为了缓存友好并确保调用与内存页/行对齐。

问题可能是为什么不是 8、16 或 32。也许是代码大小,也许它是 4 时更容易为某些旧处理器生成优化的程序集。或者实际代码太大而无法容纳典型的指令缓存。

于 2013-12-15T08:44:22.260 回答