C言語で文字列を比較する方法: strcmp, strncmp, streq

353, 2021-11-27

目次

C言語で文字列を比較する方法

C言語では文字列を扱うことができます。
この文字列は関数を使うと別の文字列と比較することができます
この記事ではC言語の文字列の比較方法について解説します。

文字列の比較は様々なシーンで使われる利用頻度の高い処理です。
C言語の文字列の比較は他の言語と比べると少し変わってますので、その特徴を押さえておくのは有用と言えます。

この記事では文字列の比較について具体的に↓を見ていきます。

  • 文字列の比較の原理

  • 文字列の比較関数

  • strcmp()を使って比較する

  • strncmp()でN文字分を比較する

  • 自作関数streq()を作る

  • strcmp()を自作する

関連記事
C言語の文字列の配列の使い方
C言語で文字列の長さを取得する: strlen, wcslen
C言語の文字列の切り出し関数を作る: strncpy, trim
C言語で文字列を比較する方法: strcmp, strncmp, streq
C言語の文字列を初期化する方法: 文字配列、文字列ポインタの初期化

文字列の比較の原理

laptop

文字列の比較の原理について解説します。
C言語の文字列の比較で自分でstrcmp()などを実装する場合は、C言語の文字列について知っておく必要があります。

C言語の文字列は文字の集まりがナル終端されたものです。
文字列の比較では文字列がナル終端されているかどうかが非常に大事になります。
なぜかというとナル終端されていな文字列を比較した場合、バッファーオーバフローなどになる可能性があるからです。

strcmp()などは実装してみるとわかりますが、ベーシックな実装は文字列の先頭から文字を取り出し、順に別の文字列の文字と比較していくものです。
どちらかの文字列の文字がナル文字になったらその場で比較をやめて、結果を返します。

このようにC言語の文字列の比較では、文字列の終端されたナル文字の扱いが重要になってきます。
しかしこれは実装レベルの話で、関数を使うだけの場合はその実装については知る必要はありません。

文字列の比較関数

C言語には演算レベルで文字列の比較を行う仕組みがありません。
そのため文字列同士の比較には関数を使う必要があります

文字列系の関数はstring.hでプロトタイプ宣言されています。
その中の文字列を比較する関数は↓になります。

  • strcmp

  • strncmp

この記事ではおもにこの2つの関数について取り上げたいと思います。

strcmp()を使って比較する

strcmp()は第1引数の文字列と第2引数の文字列を比較する関数です。
2つの文字列が一致していたら0を返し、一致していなかったら0以外を返します。

#include <string.h>

int strcmp(const char *s1, const char *s2);

たとえば「Hello」という文字列と「Hello」という文字列を比較します。

    // 2つの文字列を比較
    int ret = strcmp("Hello", "Hello");
    printf("ret[%d]\n", ret);  // ret[0]

↑の場合、ret値は0になります。
これはstrcmp()第1引数と第2引数の文字列が一致しているからです。
一致していない場合も見てみます。

    // 一致していない文字列の比較
    ret = strcmp("Hello", "World");
    printf("ret[%d]\n", ret);  // ret[-1]

    ret = strcmp("World", "Hello");
    printf("ret[%d]\n", ret);  // ret[1]

↑のように一致していない場合はretは0以外の値になります。

strcmp()の返す結果は整数なので、他の言語に親しんだ方などは面食らうかもしれません。
つまりif文で文字列が一致している場合の処理を書きたい場合は↓のようにします。

    // if文とstrcmp()
    if (strcmp("Hello", "Hello") == 0) {
        puts("一致");  // 一致
    } else {
        puts("不一致")
    }

あるいは否定演算子を使って↓のように書くことも出来ます。

    // if文とstrcmp()と否定演算子
    if (!strcmp("Hello", "Hello")) {
        puts("一致");  // 一致
    } else {
        puts("不一致")
    }

うーん、なんか違和感

そう思われた方は後述する自作関数streq()の使用を検討してみてください。

strcmp()第1引数や第2引数の文字列がNULLだった場合はセグフォ(Segmentation fault)になる場合があります

    int ret = strcmp("abc", NULL);
    printf("ret[%d]\n", ret);
    // Segmentation fault

ただしセグフォにならない場合もあるので注意が必要です。
セグフォになるリスクを考えると、strcmp()にはNULLポインタは渡さないようにした方がいいでしょう。
例によってstring.h系の関数は渡された引数をチェックしません。

strncmp()でN文字分を比較する

strncmp()は第1引数の文字列と第2引数の文字列を先頭から第3引数の数だけ比較する関数です。

#include <string.h>

int strncmp(const char *s1, const char *s2, size_t n);

strncmps1s2を先頭からnの数だけ比較します。
s1s2が一致していたら0を返し、一致していなければ0以外を返します。

    int ret = strncmp("Hello", "Hell", 4);  // 先頭4文字だけ比較
    printf("ret[%d]\n", ret);  // ret[0]

    ret = strncmp("Hige", "Hoge", 2);  // 先頭2文字だけ比較
    printf("ret[%d]\n", ret);  // ret[-6]

第3引数のnは文字列長より大きくなっても問題ありません。

    ret = strncmp("Hige", "Hoge", 100);  // 先頭100文字だけ比較
    printf("ret[%d]\n", ret);  // ret[-6]

またnが負数になってもセグフォなどにはなりません。

    ret = strncmp("Hige", "Hoge", -1);
    printf("ret[%d]\n", ret);  // ret[-6]

strncmp()第1引数または第2引数がNULLだった場合はセグフォになる場合があります

    int ret = strncmp(NULL, "abc", 4);  // 先頭4文字だけ比較
    printf("ret[%d]\n", ret);
    // Segmentation fault

これもセグフォにはならない場合があるので注意が必要です。
strncmp()strcmp()と同様に引数をチェックしないので、NULLポインタは渡さないようにした方がいいでしょう

自作関数streq()を作る

ここではstreq()という自作関数を作ります。
これは単にstrcmp()をラップしただけの関数です。

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

/**
 * s1とs2を比較し一致していたらtrueを返す
 * 一致していなかったらfalseを返す
 * LICENSE: MIT
 *
 * @param s1 文字列1
 * @param s2 文字列2
 * 
 * @return true | false
 */
bool streq(const char *s1, const char *s2) {
    return strcmp(s1, s2) == 0;
}

int main(void) {
    if (streq("Hello", "Hello")) {
        puts("一致");  // 一致
    } else {
        puts("不一致");
    }

    return 0;
}

streq()は内部でstrcmp()を呼び出し、その結果が0かどうかチェックしています。
文字列が一致していた、つまり0だった場合はtrueを返し、0でなければfalseを返します。

streq()を使った場合は↑のようにif文を書くことができます。
こちらのほうが他言語に親しんだ方には直感的かと思います。

strcmp()を自作する

strcmp()を自作してみましょう。

#include <stdio.h>
#include <string.h>
#include <assert.h>

/**
 * 自作strcmp
 * LICENSE: MIT
 */
int my_strcmp(const char *s1, const char *s2) {
    size_t s1len = strlen(s1);
    size_t s2len = strlen(s2);

    if (s1len > s2len) {
        return 1;
    } else if (s1len < s2len) {
        return -1;
    }

    for (; *s1 && *s2; s1 += 1, s2 += 1) {
        int dif = *s1 - *s2;
        if (dif < 0) {
            return -1;
        } else if (dif > 0) {
            return 1;
        }
    }
    return 0;
}

int main(void) {
    assert(my_strcmp("Hello", "Hello") == strcmp("Hello", "Hello"));
    assert(my_strcmp("Hello", "World") == strcmp("Hello", "World"));
    assert(my_strcmp("World", "Hello") == strcmp("World", "Hello"));
    assert(my_strcmp("Hige", "Hoge") == strcmp("Hige", "Hoge"));
    assert(my_strcmp("Hige", "Hi") == strcmp("Hige", "Hi"));
    assert(my_strcmp("Hi", "Hige") == strcmp("Hi", "Hige"));
    return 0;
}

my_strcmp()は第1引数と第2引数の文字列を比較します。
for文で2つの文字列を回し、1文字ずつ先頭か比較します。
比較には引き算を使います。
引き算の結果が0だったら2つの文字は同じです。
0でなかったらどちらかの文字が大きく、どちらかの文字が小さくなります。
0でなかった場合は比較を中断して結果を返します。
比較が最後まで進んだ場合は最終的に0を返します。

使ってみるとわかりますが、↑の自作したmy_strcmp()の挙動は実際のstrcmp()の挙動とは違っています。
↑のmain関数内のテストではうまくいってるように見えますが。
↑のmy_strcmp()strcmp()完全なクローンなわけではないので使ってみる方は注意してください。

完全なクローンはブログには書けないよね

ライセンスがあるからね

おわりに

今回はC言語の文字列の比較について解説しました。
C言語の文字列の比較はstrcmp()などを使って行います。
また、streq()などの自作関数を自分で作ることで、直感的な比較も行えます。
my_strcmp()を自作すれば文字列処理の片鱗を味わうことができます。
この記事があなたの何かの参考になれば幸いです。

比較されるのに疲れました

比較しない生き方を生きよう