c-理解gnu-libc的strcmp函数



这是我在glibc:中找到的strcmp函数

int
STRCMP (const char *p1, const char *p2)
{
const unsigned char *s1 = (const unsigned char *) p1;
const unsigned char *s2 = (const unsigned char *) p2;
unsigned char c1, c2;
do
{
c1 = (unsigned char) *s1++;
c2 = (unsigned char) *s2++;
if (c1 == '')
return c1 - c2;
}
while (c1 == c2);
return c1 - c2;
}

这是一个非常简单的函数,其中while的主体以*s1*s2的值启动c1c2,并持续到c1nulc1c2的值相等,然后返回c1c2之间的差。

我不明白的是s1s2变量的使用。我的意思是,除了它们是unsigned char这一事实之外,它们也是const,就像两个参数p1p2一样,那么为什么不在while的主体中使用p1p2并对其进行强制转换呢?在这种情况下,使用这两个额外的变量是否会使函数在某种程度上更加优化?因为这里有我在github上找到的FreeBSD的相同功能:

int
strcmp(const char *s1, const char *s2)
{
while (*s1 == *s2++)
if (*s1++ == '')
return (0);
return (*(const unsigned char *)s1 - *(const unsigned char *)(s2 - 1));
}

在他们的版本中,他们甚至不需要使用任何额外的变量。

提前感谢您的回答。

附言:在问这个问题之前,我确实在网上搜索过这个具体的事实,但我什么都没得到。

我还想知道glibc使用这些额外变量而不是将参数p1p2直接投射到while内部是否有任何特殊原因。

我不明白的是s1和s2变量的使用。我的意思是,除了它们是无符号字符之外,它们也是const,就像2个参数p1和p2一样,所以为什么不在while的主体中使用p1和p2并强制转换它们呢?

为了可读性;让我们人类更容易维护代码。

如果您查看glibc源代码,代码倾向于可读性,而不是简洁的表达式。这似乎是一项好政策,因为30多年来,它一直保持着相关性和活力(积极维护(。

在这种情况下,使用这两个额外的变量是否会使函数在某种程度上更加优化?

不,一点也不。

我还想知道glibc使用这些额外变量而不是直接在while内部强制转换参数p1和p2是否有任何特殊原因。

仅供阅读。

作者知道所使用的C编译器应该能够很好地优化这些代码。(只需查看编译器生成的代码就很容易证明这一点。对于GCC,您可以使用-S选项,也可以使用binutils的objdump -d来检查对象文件或二进制可执行文件。(

请注意,由于与isspace()isalpha()等完全相同的原因,需要对unsigned char进行强制转换:为了获得正确的结果,必须将比较的字符代码视为unsigned char

你当然是对的。其中一个模型应该足够了。特别是如果指针是强制转换的,则强制转换检索到的值是不可行的。


这是用gcc -O3编译的x86-64,用于不必要的强制转换:

STRCMP:
.L4:
addq    $1, %rdi
movzbl  -1(%rdi), %eax
addq    $1, %rsi
movzbl  -1(%rsi), %edx
testb   %al, %al
je      .L7
cmpb    %dl, %al
je      .L4
subl    %edx, %eax
ret
.L7:
movzbl  %dl, %eax
negl    %eax
ret

这里有一个没有不必要的演员阵容:

STRCMP:
.L4:
addq    $1, %rdi
movzbl  -1(%rdi), %eax
addq    $1, %rsi
movzbl  -1(%rsi), %edx
testb   %al, %al
je      .L7
cmpb    %dl, %al
je      .L4
subl    %edx, %eax
ret
.L7:
movzbl  %dl, %eax
negl    %eax
ret

它们是相同的


然而,有一个问题,现在主要是历史问题。如果char有符号,则有符号表示为而不是二的补码,

*(const unsigned char *)p1

(unsigned char)*p1

而不是等价物。前者重新解释比特模式,而后者使用模运算来转换值。这只是历史上的兴趣,因为即使GCC也不支持任何没有2的补符号表示的架构。它是具有大多数端口的编译器。

最新更新