C言語基礎|strcmp と strncmp の違い

文字列比較は「全部」か「先頭だけ」か―strcmp と strncmp を使い分けて、判定ミスを防ごう。

C言語で「文字列が同じか」「どっちが辞書順で前か」を調べたいとき、定番が strcmp と strncmp です。
見た目はそっくりですが、決定的な違いは 比較する範囲

  • strcmp:最後のナル文字まで、文字列全体を比較する。
  • strncmp:先頭から最大 n 文字だけ比較する(途中でナル文字が出たらそこで止まる)

「先頭3文字だけ合っていればOK」みたいな判定は strncmp が得意です。逆に「完全一致」が目的なら strcmp が安心ですね。

strcmp / strncmp の書式と「何をする命令か」

strcmp

  • ヘッダ:#include <string.h>
  • 形式:int strcmp(const char *s1, const char *s2);

何をする命令なのか

  • s1 と s2 を先頭から1文字ずつ比較していき、最初に違いが出た場所で大小を決める
  • 全部一致(両方とも同じ場所で \0 に到達)なら等しい

返却値の意味

  • 0:等しい
  • 正の値:s1 のほうが大きい
  • 負の値:s1 のほうが小さい

※「正の値が 1 になる」などは保証されません。値の大きさ自体に意味はなく、符号だけ見ればOKです。

strncmp

  • ヘッダ:#include <string.h>
  • 形式:int strncmp(const char *s1, const char *s2, size_t n);

何をする命令なのか

  • s1 と s2 の先頭から最大 n 文字までを比較する
  • 途中で \0 が出たら、それ以降は比較しない(文字列の終端で止まる)

返却値の意味

  • strcmp と同じ(0 / 正 / 負)

一番大事な違い(表で整理)

観点strcmpstrncmp
比較範囲文字列全体(\0 まで)先頭から最大 n 文字
目的完全一致・辞書順比較プレフィックス比較、先頭だけ比較
文字列でない領域基本は文字列が前提(\0 必須)先頭 n バイトに \0 がなくても比較は進む(ただし安全な範囲で)
典型例パスワード一致、コマンド一致先頭3文字だけ判定、拡張子判定、ヘッダ判定

返却値は「差の値」っぽいけど、符号だけ見よう

strcmp/strncmp は「違った文字同士」を unsigned char として比較し、その差に近い値を返すことが多いです。
でも、どんな値になるかは処理系や実装により得るので、実務ではこう考えるのが安全です。

  • 等しいか:== 0
  • それ以外:< 0 か > 0 だけ見る

比較の流れ(図でイメージ)

例:s1="ABCDE"、s2="ABC"

この違いが、「先頭だけ一致」を判定したいときに strncmp が便利な理由です。

文字列の大小関係は「文字コード」によって決まる

辞書順っぽく見えますが、基準は文字コードです。
たとえば "a" と "Z" の大小は、人の感覚ではなく文字コード順で決まります。

なので、英字の大小無視で比較したい、ロケール順で比較したい、といった用途では別の手段(例えば正規化や専用関数)が必要になることがあります。

サンプルプログラム

「入力されたコマンドが quit なら終了。先頭3文字が get なら取得扱い」という、プログラム例です。

プロジェクト名:chap11-11-1 ソースファイル名:chap11-11-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

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

int main(void)
{
    char cmd[128];

    puts("コマンドを入力してください。quit で終了します。");
    puts("get で始まると「取得コマンド」として扱います。");

    while (1) {
        printf("\n入力:");
        scanf("%127s", cmd);

        if (strcmp(cmd, "quit") == 0) {
            puts("終了します。");
            break;
        }

        if (strncmp(cmd, "get", 3) == 0) {
            puts("取得コマンドとして受け取りました。");
        } else {
            puts("その他のコマンドです。");
        }

        printf("strcmp(cmd, quit) の結果(符号が重要):%d\n", strcmp(cmd, "quit"));
        printf("strncmp(cmd, get, 3) の結果(0なら先頭一致):%d\n", strncmp(cmd, "get", 3));
    }

    return 0;
}

ポイント

  • 完全一致(quit)は strcmp
  • 先頭一致(get...)は strncmp
  • 表示メッセージも今回用に変更済みです。

よくある落とし穴

strncmp で n を大きくしすぎると危ないことがある

strncmp は最大 n 文字見るので、比較対象が「必ず n 文字以上読める領域」になっていないと危険です。
文字列なら通常は \0 で止まりますが、比較対象が「文字列ではない生のバイト列」だったり、そもそも不正なポインタだったりするとアウトです。

等しい判定は必ず == 0

次はダメです。

  • if (strcmp(a, b)) を「等しい」と思って書く
    これは 0 以外が真なので、逆になります。

正しくは:

  • if (strcmp(a, b) == 0)

演習問題

演習11-6

次の仕様の関数 my_strcmp と my_strncmp を作成してください。

  • my_strcmp(s1, s2) は strcmp と同じ仕様
  • my_strncmp(s1, s2, n) は strncmp と同じ仕様

プロトタイプ:

#include <stddef.h>
int my_strcmp(const char *s1, const char *s2);
int my_strncmp(const char *s1, const char *s2, size_t n);

解答例

#include <stddef.h>

int my_strcmp(const char *s1, const char *s2)
{
    while (*s1 != '\0' && *s2 != '\0') {
        unsigned char c1 = (unsigned char)*s1;
        unsigned char c2 = (unsigned char)*s2;
        if (c1 != c2)
            return (c1 > c2) ? 1 : -1;
        s1++;
        s2++;
    }

    if (*s1 == *s2)
        return 0;

    return ((unsigned char)*s1 > (unsigned char)*s2) ? 1 : -1;
}

int my_strncmp(const char *s1, const char *s2, size_t n)
{
    while (n > 0 && *s1 != '\0' && *s2 != '\0') {
        unsigned char c1 = (unsigned char)*s1;
        unsigned char c2 = (unsigned char)*s2;
        if (c1 != c2)
            return (c1 > c2) ? 1 : -1;
        s1++;
        s2++;
        n--;
    }

    if (n == 0)
        return 0;

    if (*s1 == *s2)
        return 0;

    return ((unsigned char)*s1 > (unsigned char)*s2) ? 1 : -1;
}

解説

  • unsigned char に直して比較するのがポイント(負の char を避ける)
  • my_strncmp は n が 0 になったら「そこまで一致」として 0 を返す