优化的strcmp实现

11

这个函数可以在这里找到。它是strcmp的一种实现方式:

int strcmp(const char* s1, const char* s2)
{
    while (*s1 && (*s1 == *s2))
        s1++, s2++;
    return *(const unsigned char*)s1 - *(const unsigned char*)s2;
}

我理解前面的所有内容,但最后一行是什么意思?


8
这种实现并没有什么“优化”。 - Stephen Canon
5个回答

5
return *(const unsigned char*)s1-*(const unsigned char*)s2;

OP: 简而言之,最后一行发生了什么?

A: 首先比较潜在的字符串差异。两个chars按规范要求都被引用为unsigned char。它们被提升为int并返回差异。


注:

1 返回值的符号(<0、0、>0)是最有意义的部分。这是C规范指定的唯一部分。

2 在某些系统上,charsigned(更常见)。在其他系统上,charunsigned。定义最后一个比较的“符号性”有助于提高可移植性。请注意,fgetc()unsigned char获取字符。

3 除了字符串以\0结尾外,所采用的字符编码(如ASCII-最常见)在二进制级别上没有任何区别。如果两个字符串中第一个不同的char的值为65和97,则第一个字符串将小于第二个字符串,即使字符编码是非ASCII的。然而,当字符编码为ASCII时,strcmp("A", "a")将返回负数,但在不同的字符编码中,它们的基础值和顺序未被C定义,可能会返回正数。


2
我更喜欢这段代码:
int strcmp(const char *str1, const char *str2)
{
    int s1;
    int s2;
    do {
        s1 = *str1++;
        s2 = *str2++;
        if (s1 == 0)
            break;
    } while (s1 == s2);
    return (s1 < s2) ? -1 : (s1 > s2);
}

对于ARMv4,它被编译为:

strcmp:
    ldrsb   r3, [r0], #1 ;r3 = *r0++
    ldrsb   r2, [r1], #1 ;r2 = *r1++
    cmp     r3, #0       ;compare r3 and 0
    beq     @1           ;if r3 == 0 goto @1
    cmp     r3, r2       ;compare r3 and r2
    beq     strcmp       ;if r3 == r2 goto strcmp
;loop is ended
@1:
    cmp     r3, r2     ;compare r3 and r2
    blt     @2         ;if r3 < r2 goto @2
    movgt   r0, #1     ;if r3 > r2 r0 = 1
    movle   r0, #0     ;if r3 <= r2 r0 = 0
    bx      lr         ;return r0
@2:
    mov     r0, #-1    ;r0 = -1
    bx      lr         ;return r0

正如您所看到的,在循环中只有6个指令,最多在结尾处有5个指令。因此,复杂度为6 *(strlen+1)+ 5。

将(s1 == 0)移动到while条件会导致ARM机器代码更差(我不知道为什么)。


1
你应该将字符强制转换为无符号字符:s1 = (unsigned char)*str1++;,以实现strcmp()的确切语义。 - chqrlie

2

这个实现绝对不是内置的strcmp函数的优化版本,它只是另一种实现方式,我相信它很可能比内置版本表现更差。

比较函数应该返回0,如果被比较的值相等,如果第一个值较小,则返回任何负数,如果第一个值较大,则返回任何正数。这就是最后一行发生的事情。

最后一行的想法是将字符转换为无符号字符,并且我相信作者的意图是将非标准字符(ASCII码0-127之外的字符)排在标准字符之后。

编辑:代码中没有错误,如果指向s1的值小于指向s2的值,则可以并且将返回负值,将标准字符排在具有128及以上代码的字符之前。


1
真正的实现在这里,但大多数架构实际上会使用更好、更具体的版本来覆盖此版本,例如这个 - ams
2
我无法订阅这个答案。http://ideone.com/crYyX7 我理解错了什么? - Charlie Burns
那么如果这两个字符的编码都大于128,这个错误就会被显示出来吗? - Charlie Burns
@CharlieBurns 没有“但是”啊,现在多亏了 Blagovest Buyukliev 在被采纳的回答下面发表的评论,我知道这是由于整数提升导致的。 - Ivaylo Strandjev
我正在查看标准转换,但那并不适用。排名也不适用。我猜这是整数提升 http://www.idryman.org/blog/2012/11/21/integer-promotion/ 。哎呀,刚刚看到了你的评论。你想让我删除所有评论还是保留它们? - Charlie Burns
显示剩余5条评论

1

这个实现可以进一步优化,减少一些比较:

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

如果字符串相同,直到包括结束的空字节,则返回值为0。返回值的符号是第一个不同字符之间的差异,转换为unsigned char,根据C标准确定。

  • If char is smaller than int, which is true on all but some rare embedded systems, this difference can be computed with a simple subtraction, both c1 and c2 being promoted to int and this difference is guaranteed to fit in the range of type int.

  • On systems where sizeof(int) == 1, the return value should be computed this way:

    return (c1 < c2) ? -1 : 1;
    

0

strcmp 返回的是哪个字符串比另一个字符串“大”,而不仅仅是它们是否相等。

最后一行减去第一个不匹配的字符,以查看哪个更大。如果整个字符串匹配,则为 0-0=0,这将给出“相等”的结果。

这种实现并没有真正优化,因为那需要汇编代码和对缓存行、加载大小等的了解。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接