文字列の内容を比較して等しいかどうか、または辞書順の前後関係を知るのは、C言語の実用プログラミングで頻出の処理です。
本記事では、strcmp
の基本から戻り値の正しい解釈、活用例、そして初心者がつまずきやすい注意点までを丁寧に解説します。
戻り値は数値そのものではなく「符号」を見ることが最大のポイントです。
C言語の文字列比較(strcmp)の基本
strcmpとは
strcmp
は、2つのC文字列(ヌル終端されたchar
配列)を比較し、辞書順(レキシコグラフィック順)の前後関係を整数で返す標準関数です。
ヘッダ#include <string.h>
で提供されます。
戻り値が0なら等しい、負なら第1引数が第2引数より小さい、正なら第1引数が第2引数より大きいという契約に従って扱います。
数値の絶対値(例えば-2や+7)には意味を持たせてはいけません。
宣言
int strcmp(const char *s1, const char *s2);
使い方
典型的には、等値判定や分岐、並べ替えの比較関数として使います。
基本のサンプルを示します。
#include <stdio.h>
#include <string.h>
int main(void) {
const char *a = "apple";
const char *b = "apricot";
// strcmpは辞書順で比較する
int r1 = strcmp(a, b);
if (r1 == 0) {
printf("\"%s\" と \"%s\" は等しい\n", a, b);
} else if (r1 < 0) {
printf("\"%s\" は \"%s\" より小さい (r1=%d)\n", a, b, r1);
} else {
printf("\"%s\" は \"%s\" より大きい (r1=%d)\n", a, b, r1);
}
// 等価判定
int r2 = strcmp("Hello", "Hello");
printf("strcmp(\"Hello\", \"Hello\") = %d\n", r2);
return 0;
}
"apple" は "apricot" より小さい (r1=-2)
strcmp("Hello", "Hello") = 0
具体的な戻り値の数値(-2など)は処理系依存であり、符号だけに意味がある点に注意してください。
比較のルール
strcmp
は、以下のルールで比較します。
1. 先頭から1文字ずつ比較
先頭から順に、unsigned char
としての値で比較します。
最初に異なる文字を見つけた時点で勝敗が決まります。
途中まで一致していて、一方が先に終端'\0'
に達した場合、'\0'
はどの文字よりも小さいとみなされます。
2. バイト値による辞書順
比較はバイト値(実質的に文字コード値)に基づきます。
ASCIIであれば、'A'
(65)より'a'
(97)の方が大きいという結果になります。
人間の言語の辞書順(ロケール依存の照合)とは一致しません。
3. ヌル終端までが文字列
'\0'
が見つかるまでを文字列とみなします。
ヌル終端がないデータを渡すと未定義動作です。
比較の前提
両方の引数は、有効なアドレスを指し、かつヌル終端された文字列でなければなりません。
また、const char*
として扱われるため、読み取り専用の意図が明確です。
スタック上の配列、静的配列、文字列リテラルを指すポインタのいずれでも構いませんが、NULLや未初期化ポインタは渡してはいけません。
大文字小文字は区別する
strcmp
は大文字と小文字を区別します。
例えば"ABC"
と"abc"
は等しくありません。
必要に応じて、POSIX環境ならstrcasecmp
(大文字小文字を無視)や、Windowsなら_stricmp
の利用を検討してください(移植性に注意)。
#include <stdio.h>
#include <string.h>
int main(void) {
printf("strcmp(\"ABC\", \"abc\") = %d\n", strcmp("ABC", "abc"));
return 0;
}
strcmp("ABC", "abc") = -32
数値は例です。
大文字小文字が異なるため0以外になります。
strcmpの戻り値の意味
0(等しい)の判定
等しいかどうかを判定するときはstrcmp(s1, s2) == 0
だけを使います。
それ以外の値はすべて「等しくない」です。
負の値/正の値
strcmp(s1, s2)
が
- 負の場合:
s1
がs2
より辞書順で前。 - 正の場合:
s1
がs2
より辞書順で後。
どれくらい小さい(大きい)かという絶対値には意味がありません。
内部実装によっては、最初に異なる文字の値の差を返すことがありますが、これは規格上の約束ではありません。
下の表は意味のまとめです。
戻り値 | 意味 | 使いどころ |
---|---|---|
0 | 文字列が完全一致 | 等価判定、分岐 |
負 | 左(第1引数)が前 | 昇順ソートの比較関数でそのまま返す |
正 | 左(第1引数)が後 | 降順にしたいなら符号を反転して返す |
戻り値の扱い方
典型的なパターンは以下の通りです。
「-1なら小さい」「+1なら大きい」とは限らないため、< 0
や> 0
で判定します。
int cmp = strcmp(s1, s2);
if (cmp == 0) {
/* 等しい */
} else if (cmp < 0) {
/* s1 < s2 */
} else {
/* s1 > s2 */
}
戻り値の例
いくつかの比較で、実際の戻り値(の一例)を観察します。
数値は処理系によって異なる点に注意してください。
#include <stdio.h>
#include <string.h>
static void show(const char *a, const char *b) {
int r = strcmp(a, b);
printf("strcmp(\"%s\", \"%s\") = %d -> ", a, b, r);
if (r == 0) puts("等しい");
else if (r < 0) puts("aが前");
else puts("aが後");
}
int main(void) {
show("ABC", "ABD"); // C(67)とD(68)の差で負値になることが多い
show("cat", "catalog"); // 途中まで一致、次は '#include <stdio.h>
#include <string.h>
static void show(const char *a, const char *b) {
int r = strcmp(a, b);
printf("strcmp(\"%s\", \"%s\") = %d -> ", a, b, r);
if (r == 0) puts("等しい");
else if (r < 0) puts("aが前");
else puts("aが後");
}
int main(void) {
show("ABC", "ABD"); // C(67)とD(68)の差で負値になることが多い
show("cat", "catalog"); // 途中まで一致、次は '\0' と 'a' の比較で負値になりやすい
show("zoo", "ant"); // 先頭文字比較で正値
show("", ""); // 空文字同士は等しい
show("apple", "apple"); // 完全一致
return 0;
}
' と 'a' の比較で負値になりやすい
show("zoo", "ant"); // 先頭文字比較で正値
show("", ""); // 空文字同士は等しい
show("apple", "apple"); // 完全一致
return 0;
}
strcmp("ABC", "ABD") = -1 -> aが前
strcmp("cat", "catalog") = -97 -> aが前
strcmp("zoo", "ant") = 25 -> aが後
strcmp("", "") = 0 -> 等しい
strcmp("apple", "apple") = 0 -> 等しい
具体的な数値(-1, -97, 25など)は参考程度です。
符号のみを使用してください。
strcmpの使いどころと例
入力文字列の一致判定
ユーザ入力の合否やY/N判定など、完全一致の判定に最適です。
注意点としてfgets
は末尾に改行を含むため、取り除いてから比較します。
#include <stdio.h>
#include <string.h>
int main(void) {
char input[64];
printf("続行しますか? yes か no を入力してください: ");
if (!fgets(input, sizeof input, stdin)) {
puts("入力エラー");
return 1;
}
// 末尾の改行を取り除く
size_t len = strlen(input);
if (len > 0 && input[len - 1] == '\n') {
input[len - 1] = '#include <stdio.h>
#include <string.h>
int main(void) {
char input[64];
printf("続行しますか? yes か no を入力してください: ");
if (!fgets(input, sizeof input, stdin)) {
puts("入力エラー");
return 1;
}
// 末尾の改行を取り除く
size_t len = strlen(input);
if (len > 0 && input[len - 1] == '\n') {
input[len - 1] = '\0';
}
if (strcmp(input, "yes") == 0) {
puts("続行します。");
} else if (strcmp(input, "no") == 0) {
puts("中止します。");
} else {
puts("有効な入力ではありません。yes か no を入力してください。");
}
return 0;
}
';
}
if (strcmp(input, "yes") == 0) {
puts("続行します。");
} else if (strcmp(input, "no") == 0) {
puts("中止します。");
} else {
puts("有効な入力ではありません。yes か no を入力してください。");
}
return 0;
}
出力例(1):
続行しますか? yes か no を入力してください: yes
続行します。
出力例(2):
続行しますか? yes か no を入力してください: YES
有効な入力ではありません。yes か no を入力してください。
大文字小文字は区別されるためYES
は不一致です。
大文字小文字を無視したい場合は、入力をtolower
で正規化してからstrcmp
するか、strcasecmp
を検討します。
設定名やコマンド名の分岐
コマンド文字列に応じた処理分岐に便利です。
#include <stdio.h>
#include <string.h>
// 使い方: ./prog start|stop|status
int main(int argc, char *argv[]) {
if (argc < 2) {
puts("Usage: prog start|stop|status");
return 1;
}
const char *cmd = argv[1];
if (strcmp(cmd, "start") == 0) {
puts("サービスを開始します。");
} else if (strcmp(cmd, "stop") == 0) {
puts("サービスを停止します。");
} else if (strcmp(cmd, "status") == 0) {
puts("サービスの状態を表示します。");
} else {
puts("不明なコマンドです。start|stop|status を指定してください。");
return 1;
}
return 0;
}
$ ./prog start
サービスを開始します。
辞書順での並べ替えの基準
配列のソートでqsort
の比較関数にstrcmp
をそのまま利用できます。
昇順ならstrcmp
の結果をそのまま返すのが定石です。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// qsortに渡す比較関数はvoidポインタ経由で受け取り、文字列へキャストしてからstrcmpします
static int cmp_str_asc(const void *lhs, const void *rhs) {
const char *const *a = (const char *const *)lhs;
const char *const *b = (const char *const *)rhs;
return strcmp(*a, *b); // 昇順
}
int main(void) {
const char *words[] = {"banana", "apple", "Apricot", "cherry"};
const size_t n = sizeof(words) / sizeof(words[0]);
puts("ソート前:");
for (size_t i = 0; i < n; ++i) puts(words[i]);
qsort(words, n, sizeof(words[0]), cmp_str_asc);
puts("ソート後(ASCII順・大文字小文字は区別):");
for (size_t i = 0; i < n; ++i) puts(words[i]);
return 0;
}
ソート前:
banana
apple
Apricot
cherry
ソート後(ASCII順・大文字小文字は区別):
Apricot
apple
banana
cherry
ロケールに応じた人間らしい順序にしたい場合はstrcoll
の利用を検討してください。
フラグやモード名のチェック
シンプルなフラグチェックにも有効です。
複数フラグを受け取り、設定内容を表示します。
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
// 使い方: ./prog [-v] [fast|safe]
int main(int argc, char *argv[]) {
bool verbose = false;
const char *mode = "safe"; // デフォルト
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-v") == 0) {
verbose = true;
} else if (strcmp(argv[i], "fast") == 0) {
mode = "fast";
} else if (strcmp(argv[i], "safe") == 0) {
mode = "safe";
} else {
printf("不明な引数: %s\n", argv[i]);
return 1;
}
}
printf("verbose=%s, mode=%s\n", verbose ? "ON" : "OFF", mode);
return 0;
}
$ ./prog -v fast
verbose=ON, mode=fast
strcmpの注意点とよくある間違い
==では比較できない
文字列の内容は==
では比較できません。
==
はポインタのアドレス比較(同一オブジェクトか)であり、内容の等価判定はstrcmp
を使います。
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "abc";
char b[] = "abc";
// aとbは別々の配列。==はアドレス比較なので通常はfalse
if (a == b) {
puts("a == b (アドレスが同じ)"); // まず起きない
} else {
puts("a != b (アドレスが異なる)");
}
// 内容の等価判定はstrcmpで行う
if (strcmp(a, b) == 0) {
puts("内容は等しい (strcmp==0)");
}
return 0;
}
a != b (アドレスが異なる)
内容は等しい (strcmp==0)
NULLや未初期化ポインタを渡さない
NULLや未初期化のポインタをstrcmp
に渡すと未定義動作です。
常に有効な、ヌル終端された文字列を指していることを確認してください。
#include <string.h>
int main(void) {
char *p; // 未初期化 (ダングリング)
char name[] = "ABC";
// 次の呼び出しは未定義動作。実行しないこと。
// int r = strcmp(p, name);
(void)name; // 未使用警告回避
return 0;
}
ヌル終端がないと未定義動作
最後に'\0'
のない配列は文字列ではありません。
そこへstrcmp
を適用すると未定義動作です。
#include <stdio.h>
#include <string.h>
int main(void) {
char bad[3] = {'a', 'b', 'c'}; // ヌル終端なし (文字列ではない)
char ok[] = "ab"; // ヌル終端あり
// 未定義動作の例 (実行しないこと)
// printf("%d\n", strcmp(bad, ok));
// 正しい作り方の一例: 常に終端を付ける
char good[4] = {'a', 'b', 'c', '#include <stdio.h>
#include <string.h>
int main(void) {
char bad[3] = {'a', 'b', 'c'}; // ヌル終端なし (文字列ではない)
char ok[] = "ab"; // ヌル終端あり
// 未定義動作の例 (実行しないこと)
// printf("%d\n", strcmp(bad, ok));
// 正しい作り方の一例: 常に終端を付ける
char good[4] = {'a', 'b', 'c', '\0'};
printf("strcmp(\"%s\", \"%s\") = %d\n", good, ok, strcmp(good, ok));
return 0;
}
'};
printf("strcmp(\"%s\", \"%s\") = %d\n", good, ok, strcmp(good, ok));
return 0;
}
strcmp("abc", "ab") = 1
戻り値は-1/+1に限られない
戻り値は-1や+1に「固定」ではありません。
if (strcmp(a,b) == 1)
のようなコードは誤りです。
常に< 0
、== 0
、> 0
の三値で判断してください。
先頭だけの一致は等しいではない
"prefix"
と"pref"
は先頭が一致しても文字列としては不一致です。
strcmp("pref", "prefix")
は負になります。
先頭n文字の一致判定が目的ならstrncmp
を使用します。
#include <stdio.h>
#include <string.h>
int main(void) {
printf("strcmp(\"pref\", \"prefix\") = %d\n", strcmp("pref", "prefix"));
printf("strncmp(\"prefix\", \"prefixA\", 6) = %d\n", strncmp("prefix", "prefixA", 6)); // 先頭6文字は等しい
return 0;
}
strcmp("pref", "prefix") = -105
strncmp("prefix", "prefixA", 6) = 0
strncmp
も戻り値は負/0/正の三値です。
日本語などマルチバイトは注意
UTF-8などのマルチバイト文字列に対してstrcmp
はバイト列として比較します。
人間の辞書順(照合規則)を期待する場合はstrcoll
やロケール設定(例:setlocale(LC_COLLATE, "")
)を用いるべきです。
また、ワイド文字ならwcscmp
の選択肢もあります。
エンコーディングが混在した文字列同士を比較するのは避けてください。
目的 | 適した関数 | 備考 |
---|---|---|
バイトとしての辞書順 | strcmp | 速く単純。ASCII想定の機械的な順序 |
ロケールに基づく照合 | strcoll | setlocaleの設定に依存 |
ワイド文字列の比較 | wcscmp | ロケールと<wchar_t>の扱いに注意 |
マルチバイトの途中で分割された不正なバイト列を比較すると、期待外の順序や表示崩れの原因になります。
まとめ
strcmpは「ヌル終端された2つの文字列を辞書順で比較し、負/0/正の三値で結果を返す」関数です。
使いこなしの要点は、次の通りです。
まず、等価判定は== 0
で行い、大小判定は< 0
と> 0
で行います。
戻り値の具体的な数値や-1/+1に依存しないことが重要です。
次に、引数は必ず有効な、ヌル終端された文字列でなければならず、==
による内容比較はできません。
用途としては、入力の一致判定、コマンド分岐、並べ替えの比較関数、フラグチェックなどに広く活用できます。
最後に、大文字小文字は区別される点や、日本語などマルチバイトはバイト比較になる点にも注意してください。
これらを押さえれば、strcmp
は文字列処理の強力な基盤となります。