web-dev-qa-db-ja.com

最適化されたstrcmpの実装

この関数が見つかりました ここ 。これはstrcmpの実装です。

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

私は最後の行を除いてすべてを理解しています、要するに最後の行で何が起こっているのですか?

11
Cody Smith
_return *(const unsigned char*)s1-*(const unsigned char*)s2;
_

OP:要するに、最後の行で何が起こっているのですか?

A:最初の潜在的な文字列の違いが比較されます。両方のcharsは、仕様で要求されているように_unsigned char_として参照されます。 2はintにプロモートされ、差が返されます。


ノート:

1戻り値の符号(<0、0、> 0)が最も意味のある部分です。 C仕様で指定されているのはこれだけです。

2一部のシステムでは、charsigned(より一般的)です。その他の場合、charunsignedです。最後の比較の「符号」を定義すると、移植性が向上します。 fgetc()は文字を_unsigned char_として取得することに注意してください。

3文字列が_\0_で終わることを除いて、使用される文字エンコード(ASCII-最も一般的)など)は、バイナリレベルで違いはありません。最初のcharsの値は65と97であり、文字エンコードが非ASCIIであっても、最初の文字列は2番目の文字列よりも小さくなります。OTOH、strcmp("A", "a")は、文字の場合に負の数値を返します。エンコーディングはASCIIですが、基になる値と順序がCで定義されていないため、異なる文字エンコーディングで正の数値を返す場合があります。

この実装は、組み込みのstrcmpの最適化ではなく、単なる別の実装であり、おそらく組み込みバージョンよりもパフォーマンスが低下すると思います。

比較関数は、比較される値が等しい場合は0を返し、最初の値が小さい場合は負の数を返し、最初の値が大きい場合は正の数を返すことになっています。そして、それが最後の行で起こることです。

最後の行のアイデアは、文字をunsigned charにキャストすることであり、作成者は、これが非標準文字(ASCIIコード0-127)の後に非標準文字をソートすることを意図したと思います。

編集:コードにバグはなく、s1が指す値が、s2が指す値よりも小さい場合、コード128以上の文字の前に標準文字を並べ替えると、負の値を返す可能性があります。 。

2

この実装はさらに最適化でき、いくつかの比較を省きます。

_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_です。戻り値の符号は、最初の異なる文字間の差の符号であり、C標準に従って_unsigned char_に変換されます。

  • charintよりも小さい場合(これは一部のまれな組み込みシステムを除くすべてに当てはまります)、この差は_c1_と_c2_の両方の単純な減算で計算できます。 intにプロモートされ、この差はタイプintの範囲に収まることが保証されています。

  • sizeof(int) == 1があるシステムでは、戻り値は次のように計算する必要があります。

    _return (c1 < c2) ? -1 : 1;
    _

私はこのコードを好みます:

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

strcmpは、それらが等しいかどうかだけでなく、どの文字列がより大きい他の文字列であるかを返します。

最後の行は、最初の一致しない文字を差し引いて、どちらが大きいかを確認します。文字列全体が一致する場合は、0-0=0になり、「等しい」結果が得られます。

この実装は、アセンブリコードとキャッシュライン、ロードサイズなどの知識が必要になるため、あまり最適化されていません。

0
ams