C言語の文字列はchar配列で、末尾のヌル文字(‘\0’)で終端します。
長さを測るときは標準関数strlen
を使いますが、ヌル終端の扱い、sizeof
との違い、日本語のバイト数などで混乱しがちです。
本記事ではヌル文字に注意してstrlen
で正しく長さを測るための基本と安全な使い方を丁寧に解説します。
C言語のstrlenとは?
文字列長を返す仕組み
内部的な動きのイメージ
strlen
は、文字配列の先頭からヌル文字'\0'
に出会うまで1バイトずつ数え上げます。
したがって、配列の途中に'\0'
があればそこでカウントは止まり、その後ろのデータは「文字列の一部」と見なされません。
コード例: 自作strlenで仕組みを確認
以下はstrlen
の挙動をまねた簡易実装です。
#include <stdio.h>
#include <string.h> // 本物の strlen を使うため
// 教材用の簡易実装: 先頭から '#include <stdio.h>
#include <string.h> // 本物の strlen を使うため
// 教材用の簡易実装: 先頭から '\0' まで数える
size_t my_strlen(const char *s) {
// 注意: 本物の strlen と同じく NULL チェックはしません
size_t n = 0;
while (s[n] != '\0') {
n++;
}
return n;
}
int main(void) {
const char *s = "Hello";
printf("my_strlen(\"%s\") = %zu\n", s, my_strlen(s));
printf("strlen(\"%s\") = %zu\n", s, strlen(s));
return 0;
}
' まで数える
size_t my_strlen(const char *s) {
// 注意: 本物の strlen と同じく NULL チェックはしません
size_t n = 0;
while (s[n] != '#include <stdio.h>
#include <string.h> // 本物の strlen を使うため
// 教材用の簡易実装: 先頭から '\0' まで数える
size_t my_strlen(const char *s) {
// 注意: 本物の strlen と同じく NULL チェックはしません
size_t n = 0;
while (s[n] != '\0') {
n++;
}
return n;
}
int main(void) {
const char *s = "Hello";
printf("my_strlen(\"%s\") = %zu\n", s, my_strlen(s));
printf("strlen(\"%s\") = %zu\n", s, strlen(s));
return 0;
}
') {
n++;
}
return n;
}
int main(void) {
const char *s = "Hello";
printf("my_strlen(\"%s\") = %zu\n", s, my_strlen(s));
printf("strlen(\"%s\") = %zu\n", s, strlen(s));
return 0;
}
my_strlen("Hello") = 5
strlen("Hello") = 5
戻り値(size_t)とヘッダ<string.h>
size_tとは
strlen
の戻り値は符号なし整数型のsize_t
です。
size_t
は配列サイズを表すのに適した型で、環境によりビット幅が異なります。
表示には%zu
を使います。
#include <stdio.h>
#include <string.h> // strlen を使うために必須
int main(void) {
size_t len = strlen("ABC");
printf("len = %zu\n", len); // %zu は size_t 専用の書式
return 0;
}
len = 3
ヘッダ<string.h>を必ずインクルード
strlen
は#include <string.h>
で宣言されます。
これを忘れると未宣言関数の使用になり、警告や未定義動作の原因になります。
使いどころ
strlen
は以下のような場面で使われます。
文字列入力の長さチェック、繰り返し処理での終端判定、バッファに収まるかの検証、動的メモリ確保(malloc)
時の必要サイズ計算などです。
どれもヌル終端を前提に正しく使うことが重要です。
ヌル文字とstrlenの基本
ヌル終端(‘\0’)までを数える
途中の’\0’でカウントが止まる例
#include <stdio.h>
#include <string.h>
int main(void) {
// 'A','B','#include <stdio.h>
#include <string.h>
int main(void) {
// 'A','B','\0','C','D' という配列
char s[] = { 'A', 'B', '\0', 'C', 'D', '\0' };
printf("strlen = %zu\n", strlen(s)); // 'A','B' の 2 で止まる
return 0;
}
','C','D' という配列
char s[] = { 'A', 'B', '#include <stdio.h>
#include <string.h>
int main(void) {
// 'A','B','\0','C','D' という配列
char s[] = { 'A', 'B', '\0', 'C', 'D', '\0' };
printf("strlen = %zu\n", strlen(s)); // 'A','B' の 2 で止まる
return 0;
}
', 'C', 'D', '#include <stdio.h>
#include <string.h>
int main(void) {
// 'A','B','\0','C','D' という配列
char s[] = { 'A', 'B', '\0', 'C', 'D', '\0' };
printf("strlen = %zu\n", strlen(s)); // 'A','B' の 2 で止まる
return 0;
}
' };
printf("strlen = %zu\n", strlen(s)); // 'A','B' の 2 で止まる
return 0;
}
strlen = 2
空文字(“”)は長さ0
空文字の定義と結果
""
は最初から'\0'
だけを含む文字列です。
長さは0になります。
#include <stdio.h>
#include <string.h>
int main(void) {
char empty[] = "";
printf("empty の長さ = %zu\n", strlen(empty));
return 0;
}
empty の長さ = 0
ヌル文字が無いchar配列は危険
ヌル終端の欠落は未定義動作
ヌル終端のないchar
配列にstrlen
を呼ぶのは未定義動作です。
どこまでも読み続けてメモリ破壊やクラッシュにつながる可能性があります。
#include <stdio.h>
#include <string.h>
int main(void) {
char bad[3] = { 'A', 'B', 'C' }; // ← '#include <stdio.h>
#include <string.h>
int main(void) {
char bad[3] = { 'A', 'B', 'C' }; // ← '\0' が無い
// printf("%zu\n", strlen(bad)); // 危険: 未定義動作になる可能性
// 安全策: 明示的に終端を付ける
char ok[4] = { 'A', 'B', 'C', '\0' };
printf("安全な ok の長さ = %zu\n", strlen(ok));
return 0;
}
' が無い
// printf("%zu\n", strlen(bad)); // 危険: 未定義動作になる可能性
// 安全策: 明示的に終端を付ける
char ok[4] = { 'A', 'B', 'C', '#include <stdio.h>
#include <string.h>
int main(void) {
char bad[3] = { 'A', 'B', 'C' }; // ← '\0' が無い
// printf("%zu\n", strlen(bad)); // 危険: 未定義動作になる可能性
// 安全策: 明示的に終端を付ける
char ok[4] = { 'A', 'B', 'C', '\0' };
printf("安全な ok の長さ = %zu\n", strlen(ok));
return 0;
}
' };
printf("安全な ok の長さ = %zu\n", strlen(ok));
return 0;
}
安全な ok の長さ = 3
初心者がつまずくポイント
strlenとsizeofの違い
strlen
は'\0'
までの文字数、sizeof
は型や配列のバイト数です。
ポインタに対するsizeof
は文字列の長さではなくポインタ自体のサイズになります。
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "ABC"; // 配列要素: 'A','B','C','#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "ABC"; // 配列要素: 'A','B','C','\0' → sizeof(a) は 4
char b[10] = "ABC"; // 10バイト配列 → sizeof(b) は 10
const char *p = "ABC"; // 文字列リテラルへのポインタ → sizeof(p) はポインタのサイズ
printf("strlen(a)=%zu, sizeof(a)=%zu\n", strlen(a), sizeof(a));
printf("strlen(b)=%zu, sizeof(b)=%zu\n", strlen(b), sizeof(b));
printf("strlen(p)=%zu, sizeof(p)=%zu\n", strlen(p), sizeof(p));
return 0;
}
' → sizeof(a) は 4
char b[10] = "ABC"; // 10バイト配列 → sizeof(b) は 10
const char *p = "ABC"; // 文字列リテラルへのポインタ → sizeof(p) はポインタのサイズ
printf("strlen(a)=%zu, sizeof(a)=%zu\n", strlen(a), sizeof(a));
printf("strlen(b)=%zu, sizeof(b)=%zu\n", strlen(b), sizeof(b));
printf("strlen(p)=%zu, sizeof(p)=%zu\n", strlen(p), sizeof(p));
return 0;
}
strlen(a)=3, sizeof(a)=4
strlen(b)=3, sizeof(b)=10
strlen(p)=3, sizeof(p)=8
概要を表にまとめます。
対象 | strlenの結果 | sizeofの結果 |
---|---|---|
char a[] = “ABC” | 3 | 4 |
char b[10] = “ABC” | 3 | 10 |
const char *p = “ABC” | 3 | ポインタサイズ(例: 8) |
NULLポインタにstrlenしない
NULLポインタにstrlen
を呼ぶのは未定義動作です。
必ずチェックしましょう。
#include <stdio.h>
#include <string.h>
size_t safe_strlen(const char *s) {
if (s == NULL) {
// 呼び出し側のバグの可能性。ここでは 0 を返す例。
return 0;
}
return strlen(s);
}
int main(void) {
const char *s1 = "ABC";
const char *s2 = NULL;
printf("safe_strlen(s1) = %zu\n", safe_strlen(s1));
printf("safe_strlen(s2) = %zu\n", safe_strlen(s2)); // ここでは 0
return 0;
}
safe_strlen(s1) = 3
safe_strlen(s2) = 0
改行(‘\n’)やスペースも文字数に含まれる
strlen
は空白や改行も普通の文字としてカウントします。
fgets
で読み込むと末尾に'\n'
が付くことがあるので必要なら取り除きます。
#include <stdio.h>
#include <string.h>
int main(void) {
const char with_nl[] = "ABC\n";
const char with_sp[] = "A B";
printf("with_nl の長さ = %zu\n", strlen(with_nl)); // 4 ('\n' を含む)
printf("with_sp の長さ = %zu\n", strlen(with_sp)); // 3 (スペース含む)
// fgets で読み取ったと仮定した文字列の末尾改行を取り除く例
char buf[100] = "hello world\n";
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') {
buf[len - 1] = '#include <stdio.h>
#include <string.h>
int main(void) {
const char with_nl[] = "ABC\n";
const char with_sp[] = "A B";
printf("with_nl の長さ = %zu\n", strlen(with_nl)); // 4 ('\n' を含む)
printf("with_sp の長さ = %zu\n", strlen(with_sp)); // 3 (スペース含む)
// fgets で読み取ったと仮定した文字列の末尾改行を取り除く例
char buf[100] = "hello world\n";
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') {
buf[len - 1] = '\0';
len--;
}
printf("改行除去後: \"%s\" (長さ %zu)\n", buf, len);
return 0;
}
';
len--;
}
printf("改行除去後: \"%s\" (長さ %zu)\n", buf, len);
return 0;
}
with_nl の長さ = 4
with_sp の長さ = 3
改行除去後: "hello world" (長さ 11)
日本語(UTF-8など)は文字数でなくバイト数
strlen
は「文字数」ではなく「バイト数」を返すことに注意してください。
UTF-8では日本語1文字が3バイトになることが多く、見かけの文字数とstrlen
の結果が一致しません。
#include <stdio.h>
#include <string.h>
int main(void) {
// ソースコードが UTF-8 保存である前提
const char *s1 = "あ"; // UTF-8 では通常 3 バイト
const char *s2 = "日本"; // 通常 6 バイト
printf("\"あ\" の strlen = %zu\n", strlen(s1));
printf("\"日本\" の strlen = %zu\n", strlen(s2));
return 0;
}
実行結果例(UTF-8環境):
"あ" の strlen = 3
"日本" の strlen = 6
人間の文字数を数えたい場合はマルチバイト/ワイド文字API(mbstowcs, mbrtowc など)
や外部ライブラリの利用を検討してください。
未初期化メモリの文字列に注意
未初期化のchar
配列にstrlen
を呼ぶのは危険です。
必ずヌル終端を用意しましょう。
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[16]; // 未初期化
// printf("%zu\n", strlen(buf)); // 危険: どこまで読みに行くか不明
// 安全: 空文字として初期化
buf[0] = '#include <stdio.h>
#include <string.h>
int main(void) {
char buf[16]; // 未初期化
// printf("%zu\n", strlen(buf)); // 危険: どこまで読みに行くか不明
// 安全: 空文字として初期化
buf[0] = '\0';
printf("初期化後の長さ = %zu\n", strlen(buf)); // 0
return 0;
}
';
printf("初期化後の長さ = %zu\n", strlen(buf)); // 0
return 0;
}
初期化後の長さ = 0
strlenの使い方パターン
入力の文字列長チェックに使う
最小・最大長を検証する
ユーザー入力が要件を満たすかstrlen
で検証します。
ここでは疑似的な入力で例示します。
#include <stdio.h>
#include <string.h>
int main(void) {
const size_t MIN_LEN = 3;
const size_t MAX_LEN = 8;
// 例: 入力された文字列がここに入ったと仮定
char username[64] = "Alice\n";
// 末尾の改行を取り除く
size_t len = strlen(username);
if (len > 0 && username[len - 1] == '\n') {
username[len - 1] = '#include <stdio.h>
#include <string.h>
int main(void) {
const size_t MIN_LEN = 3;
const size_t MAX_LEN = 8;
// 例: 入力された文字列がここに入ったと仮定
char username[64] = "Alice\n";
// 末尾の改行を取り除く
size_t len = strlen(username);
if (len > 0 && username[len - 1] == '\n') {
username[len - 1] = '\0';
len--;
}
if (len < MIN_LEN) {
printf("短すぎます(最小 %zu 文字)\n", MIN_LEN);
} else if (len > MAX_LEN) {
printf("長すぎます(最大 %zu 文字)\n", MAX_LEN);
} else {
printf("OK: \"%s\" は %zu 文字\n", username, len);
}
return 0;
}
';
len--;
}
if (len < MIN_LEN) {
printf("短すぎます(最小 %zu 文字)\n", MIN_LEN);
} else if (len > MAX_LEN) {
printf("長すぎます(最大 %zu 文字)\n", MAX_LEN);
} else {
printf("OK: \"%s\" は %zu 文字\n", username, len);
}
return 0;
}
OK: "Alice" は 5 文字
ループではstrlenを一度だけ計算
繰り返しのたびに呼ばない
ループの条件にstrlen(s)
を直接書くと毎回末尾まで走査します。
一度だけ長さを求めて変数に保持しましょう。
#include <stdio.h>
#include <string.h>
int main(void) {
const char *s = "The quick brown fox jumps over the lazy dog";
// 悪い例: strlen が毎回呼ばれる
// for (size_t i = 0; i < strlen(s); ++i) { ... }
// 良い例: 1回だけ計算
size_t n = strlen(s);
for (size_t i = 0; i < n; ++i) {
// ここで s[i] を使った処理を行う
}
printf("長さ %zu を1回だけ計算してループしました\n", n);
return 0;
}
長さ 43 を1回だけ計算してループしました
バッファ長と比較して安全に扱う
オーバーフローを事前に防ぐ
コピーや連結前にstrlen
で長さを調べ、バッファに収まるか確認します。
#include <stdio.h>
#include <string.h>
int main(void) {
char dest[8];
const char *src = "hello"; // 5 文字
if (strlen(src) >= sizeof(dest)) {
printf("エラー: src が dest に収まりません\n");
return 1;
}
// 収まるなら安全にコピー
strcpy(dest, src); // 今回は事前検査済みなので OK
printf("コピー成功: \"%s\"\n", dest);
return 0;
}
コピー成功: "hello"
動的確保時の必要サイズ
ヌル終端の1バイト分を忘れずに確保します。
malloc(strlen(src) + 1)
が基本形です。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
const char *src = "sample";
size_t need = strlen(src) + 1; // +1 は '#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
const char *src = "sample";
size_t need = strlen(src) + 1; // +1 は '\0' の分
char *p = (char *)malloc(need);
if (!p) {
perror("malloc");
return 1;
}
strcpy(p, src);
printf("確保サイズ %zu でコピー: \"%s\"\n", need, p);
free(p);
return 0;
}
' の分
char *p = (char *)malloc(need);
if (!p) {
perror("malloc");
return 1;
}
strcpy(p, src);
printf("確保サイズ %zu でコピー: \"%s\"\n", need, p);
free(p);
return 0;
}
確保サイズ 7 でコピー: "sample"
まとめ
strlenはヌル終端'\0'
までのバイト数を返す関数です。
戻り値はsize_t
で%zu
で表示し、<string.h>
を必ずインクルードします。
ヌル終端の欠落、NULLポインタ、未初期化配列への適用は未定義動作であり、空文字は長さ0、改行やスペースも長さに含まれる点を押さえましょう。
また、UTF-8の日本語は「文字数」ではなく「バイト数」になるため要件に合わせたAPI選択が必要です。
実用ではループでの重複計算を避け、バッファサイズと比較して安全に扱う、動的確保では+1
を忘れない、という基本を守れば、strlenを正しく安全に使いこなせます。