C言語で文字列を扱うとき、長さを知るために使う代表的な関数がstrlenです。
ですが、strlenが数えているのは「文字数」なのか「バイト数」なのか、ヌル文字との関係はどうなっているのか、初心者の方には少しわかりにくいポイントが多くあります。
本記事では、strlenの基本から注意点、安全な使い方まで、サンプルコードとともに丁寧に解説します。
strlenとは何かを理解しよう
strlenとは
C言語におけるstrlen関数は、文字列の長さを求める標準ライブラリ関数です。
宣言はstring.hで行われており、プロトタイプは次のようになっています。
size_t strlen(const char *s);
この宣言からわかるように、strlenはconst char *型、つまり文字列(ヌル終端されたchar配列)を指すポインタを受け取り、その長さをsize_t型で返します。
ここで重要なのは、strlenは「終端のヌル文字(‘\0’)に出会うまでの長さを数える関数である、という点です。
この「ヌル終端」という仕組みが、C言語の文字列を理解するカギになります。
strlenで数えられるのは「文字数」ではなく「バイト数」
C言語では、文字列はchar型の配列として表現されます。
char型は通常1バイトであり、strlenはこの配列の中のバイト数をカウントします。
ASCII文字(英数字や記号)だけを扱う場合、1文字が1バイトで表現されるため、strlenで得られる値は「文字数」と一致します。
しかし日本語などのマルチバイト文字を含む場合、1文字が2バイト以上で表現されることがあり、strlenの結果は「見た目の文字数」とは一致しません。
つまり、strlenが返すのは常に「ヌル終端までのバイト数」であり、「見た目の文字数」ではないことを、まずしっかり押さえておく必要があります。
文字列とヌル文字(‘\0’)の関係
C言語では、文字列の終わりを示すためにヌル文字と呼ばれる特別な文字'\0'を使います。
ヌル文字は数値で表すと0であり、「ここで文字列が終わり」というマークの役割を持ちます。
例えば、文字列"ABC"は、メモリ上では次のように並びます。
'A''B''C''\0'
このように、実際の文字たちの後ろに必ず1バイト分のヌル文字が付くのがC言語の文字列の基本です。
したがって、文字列を格納する配列を定義するときは、必要な文字数+1バイトを確保する必要があります。
ヌル文字は配列の一部ですが、strlenが返す長さには含まれません。
先ほどの"ABC"の場合、配列のサイズが4バイト(3文字+ヌル文字)でも、strlenが返す値は3になります。
strlenの基本的な使い方
strlenの書式とヘッダファイル
strlenを使うには、必ず#include <string.h>を記述します。
ヘッダファイルをインクルードしないと、コンパイラがstrlenの宣言を知らない状態になり、警告や未定義動作につながる可能性があります。
基本的な書式は次の通りです。
#include <string.h>
size_t strlen(const char *s);
返り値の型であるsize_tは、配列のサイズや文字列の長さを表すために使われる符号なし整数型です。
環境によって実際のビット数は異なりますが、「負の値を取らない整数型」として理解しておけば十分です。
文字配列に対するstrlenの使い方
最も基本的な使い方は、文字配列(ヌル終端済み)に対してstrlenを呼び出す方法です。
#include <stdio.h>
#include <string.h>
int main(void) {
// 文字列リテラルで初期化すると、末尾に自動的に'#include <stdio.h>
#include <string.h>
int main(void) {
// 文字列リテラルで初期化すると、末尾に自動的に'\0'が付く
char str1[] = "Hello";
// 手動で初期化する場合も、最後に'\0'を入れる必要がある
char str2[6] = { 'W', 'o', 'r', 'l', 'd', '\0' };
// strlenの返り値はsize_t型なので、%zuで表示する
printf("str1の長さ: %zu\n", strlen(str1)); // "Hello" → 5
printf("str2の長さ: %zu\n", strlen(str2)); // "World" → 5
return 0;
}
'が付く
char str1[] = "Hello";
// 手動で初期化する場合も、最後に'#include <stdio.h>
#include <string.h>
int main(void) {
// 文字列リテラルで初期化すると、末尾に自動的に'\0'が付く
char str1[] = "Hello";
// 手動で初期化する場合も、最後に'\0'を入れる必要がある
char str2[6] = { 'W', 'o', 'r', 'l', 'd', '\0' };
// strlenの返り値はsize_t型なので、%zuで表示する
printf("str1の長さ: %zu\n", strlen(str1)); // "Hello" → 5
printf("str2の長さ: %zu\n", strlen(str2)); // "World" → 5
return 0;
}
'を入れる必要がある
char str2[6] = { 'W', 'o', 'r', 'l', 'd', '#include <stdio.h>
#include <string.h>
int main(void) {
// 文字列リテラルで初期化すると、末尾に自動的に'\0'が付く
char str1[] = "Hello";
// 手動で初期化する場合も、最後に'\0'を入れる必要がある
char str2[6] = { 'W', 'o', 'r', 'l', 'd', '\0' };
// strlenの返り値はsize_t型なので、%zuで表示する
printf("str1の長さ: %zu\n", strlen(str1)); // "Hello" → 5
printf("str2の長さ: %zu\n", strlen(str2)); // "World" → 5
return 0;
}
' };
// strlenの返り値はsize_t型なので、%zuで表示する
printf("str1の長さ: %zu\n", strlen(str1)); // "Hello" → 5
printf("str2の長さ: %zu\n", strlen(str2)); // "World" → 5
return 0;
}
str1の長さ: 5
str2の長さ: 5
ここでは配列の末尾にヌル文字が必ず存在しているため、strlenは安全に使えます。
逆に言うと、ヌル終端されていない配列に対してstrlenを呼ぶと危険であることも覚えておきましょう。
文字列リテラルに対するstrlenの使い方
strlenは直接文字列リテラルに対して呼び出すこともできます。
文字列リテラルはコンパイル時に配置される読み取り専用の領域に格納され、その先頭アドレスがポインタとして扱われます。
#include <stdio.h>
#include <string.h>
int main(void) {
// 文字列リテラルに直接strlenを適用
printf("\"ABC\" の長さ: %zu\n", strlen("ABC"));
printf("\"\" (空文字列) の長さ: %zu\n", strlen(""));
return 0;
}
"ABC" の長さ: 3
"" (空文字列) の長さ: 0
空文字列""は、実際には{ '\0' }という1バイトだけの配列です。
目に見える文字は1つもないので、strlenの結果は0になります。
printfと組み合わせた文字数表示の例
strlenは入力された文字列の長さを表示したり、バッファの利用状況を確認したりするときに便利です。
次の例では、配列に格納した文字列の内容と長さをprintfで表示します。
#include <stdio.h>
#include <string.h>
int main(void) {
char name[] = "Taro";
char message[] = "C programming";
printf("name: %s (長さ: %zu)\n", name, strlen(name));
printf("message: %s (長さ: %zu)\n", message, strlen(message));
return 0;
}
name: Taro (長さ: 4)
message: C programming (長さ: 13)
このように、%sで文字列を表示し%zuでstrlenの結果を表示することで、「見た目の文字列」と「内部的な長さ」の両方を確認できます。
strlenの仕組みと注意点
ヌル終端まで1文字ずつ走査する仕組み
strlenの内部動作は、とてもシンプルです。
先頭アドレスから1バイトずつ順に読み進め、ヌル文字'\0'に到達するまでカウントします。
イメージとしては、次のような処理が行われています。
#include <stddef.h> // size_tを使うためのヘッダ
// 実際のstrlenの実装例(あくまでイメージです)
size_t my_strlen(const char *s) {
size_t len = 0;
// s[len] が '#include <stddef.h> // size_tを使うためのヘッダ
// 実際のstrlenの実装例(あくまでイメージです)
size_t my_strlen(const char *s) {
size_t len = 0;
// s[len] が '\0' でない間はカウントを続ける
while (s[len] != '\0') {
len++;
}
return len;
}
' でない間はカウントを続ける
while (s[len] != '#include <stddef.h> // size_tを使うためのヘッダ
// 実際のstrlenの実装例(あくまでイメージです)
size_t my_strlen(const char *s) {
size_t len = 0;
// s[len] が '\0' でない間はカウントを続ける
while (s[len] != '\0') {
len++;
}
return len;
}
') {
len++;
}
return len;
}
(このコード単体では出力はありません。あくまで仕組みの例です)
このように、strlenは必ずヌル終端に依存して動作します。
そのため、ヌル文字が存在しない配列に対してstrlenを呼び出すと、メモリ上をどこまでも読み進めてしまう危険があります。
ヌル終端されていない配列にstrlenを使う危険性
次のコードは、典型的な危険な例です。
#include <stdio.h>
#include <string.h>
int main(void) {
// ヌル終端していないchar配列
char buf[5] = { 'H', 'e', 'l', 'l', 'o' }; // '#include <stdio.h>
#include <string.h>
int main(void) {
// ヌル終端していないchar配列
char buf[5] = { 'H', 'e', 'l', 'l', 'o' }; // '\0' がない
// 非常に危険な呼び出し例
printf("bufの長さ: %zu\n", strlen(buf));
return 0;
}
' がない
// 非常に危険な呼び出し例
printf("bufの長さ: %zu\n", strlen(buf));
return 0;
}
(このプログラムの動作は未定義です。環境によっては5が出ることもあれば、異常終了することもあります)
この配列には'\0'が含まれていないため、strlenは配列の外側のメモリ領域まで読み進めてしまう可能性があります。
その結果として、次のような問題が起こり得ます。
- 偶然どこかで
0のバイトに出会って止まり、たまたまそれらしい値が得られてしまう - 不正なメモリ領域を読もうとして、プログラムがクラッシュする
- 他のデータを文字列の一部だと誤認識してしまい、バグや脆弱性につながる
このような理由から、ヌル終端されていない配列に対してstrlenを呼び出してはいけない、という点は非常に重要です。
配列サイズ(sizeof)とstrlenの違い
初心者が混同しやすいのが、sizeofとstrlenの違いです。
両者は似ているようで、実際には全く別のものを測っています。
次の例で違いを確認してみましょう。
#include <stdio.h>
#include <string.h>
int main(void) {
char str1[] = "ABC"; // 実体は {'A', 'B', 'C', '#include <stdio.h>
#include <string.h>
int main(void) {
char str1[] = "ABC"; // 実体は {'A', 'B', 'C', '\0'}
char str2[10] = "ABC"; // 残りは '\0' で埋まるとは限らない点に注意
printf("str1: \"%s\"\n", str1);
printf("sizeof(str1): %zu\n", sizeof(str1)); // 配列全体のバイト数
printf("strlen(str1): %zu\n", strlen(str1)); // 'A'~'\0'までのバイト数
printf("\n");
printf("str2: \"%s\"\n", str2);
printf("sizeof(str2): %zu\n", sizeof(str2));
printf("strlen(str2): %zu\n", strlen(str2));
return 0;
}
'}
char str2[10] = "ABC"; // 残りは '#include <stdio.h>
#include <string.h>
int main(void) {
char str1[] = "ABC"; // 実体は {'A', 'B', 'C', '\0'}
char str2[10] = "ABC"; // 残りは '\0' で埋まるとは限らない点に注意
printf("str1: \"%s\"\n", str1);
printf("sizeof(str1): %zu\n", sizeof(str1)); // 配列全体のバイト数
printf("strlen(str1): %zu\n", strlen(str1)); // 'A'~'\0'までのバイト数
printf("\n");
printf("str2: \"%s\"\n", str2);
printf("sizeof(str2): %zu\n", sizeof(str2));
printf("strlen(str2): %zu\n", strlen(str2));
return 0;
}
' で埋まるとは限らない点に注意
printf("str1: \"%s\"\n", str1);
printf("sizeof(str1): %zu\n", sizeof(str1)); // 配列全体のバイト数
printf("strlen(str1): %zu\n", strlen(str1)); // 'A'~'#include <stdio.h>
#include <string.h>
int main(void) {
char str1[] = "ABC"; // 実体は {'A', 'B', 'C', '\0'}
char str2[10] = "ABC"; // 残りは '\0' で埋まるとは限らない点に注意
printf("str1: \"%s\"\n", str1);
printf("sizeof(str1): %zu\n", sizeof(str1)); // 配列全体のバイト数
printf("strlen(str1): %zu\n", strlen(str1)); // 'A'~'\0'までのバイト数
printf("\n");
printf("str2: \"%s\"\n", str2);
printf("sizeof(str2): %zu\n", sizeof(str2));
printf("strlen(str2): %zu\n", strlen(str2));
return 0;
}
'までのバイト数
printf("\n");
printf("str2: \"%s\"\n", str2);
printf("sizeof(str2): %zu\n", sizeof(str2));
printf("strlen(str2): %zu\n", strlen(str2));
return 0;
}
str1: "ABC"
sizeof(str1): 4
strlen(str1): 3
str2: "ABC"
sizeof(str2): 10
strlen(str2): 3
ここでのポイントを整理します。
- sizeofは配列全体のバイト数を返します。コンパイル時に決まっている静的なサイズです。
- strlenはヌル終端までのバイト数を返します。実行時に計算される長さです。
したがって、sizeofはintやdoubleなどの型の大きさを調べる目的にも使われますが、strlenは文字列専用である点に注意してください。
マルチバイト文字(日本語)とstrlenの落とし穴
日本語などを扱うときは、strlenが返す値が「見た目の文字数」と一致しないことに注意が必要です。
多くの環境では、UTF-8やShift_JISなどのマルチバイト文字コードが使われており、1文字が2バイト以上で表現されることがあります。
次のサンプルは、あくまで典型的な挙動の一例です(実際の結果は環境や文字コードに依存します)。
#include <stdio.h>
#include <string.h>
int main(void) {
// このソースコードの文字コードと実行環境のロケールに依存します
char str[] = "あい"; // 2文字の日本語
printf("文字列: %s\n", str);
printf("strlen(str): %zu\n", strlen(str));
return 0;
}
文字列: あい
strlen(str): 6 (例: UTF-8の場合、1文字3バイト×2文字)
このように、表示上は2文字でも、strlenは6という値を返すことがあります。
これは、strlenが「バイト数」を数えているからです。
「画面上の文字数」や「ユーザーが入力した文字数」を正しく知りたい場合には、マルチバイト文字を考慮した別の関数(例えばmbstowcsやワイド文字列関連の関数)を使う必要があります。
strlenだけに頼ると、文字数制限や切り詰め処理で予期しない不具合を生む原因になります。
strlenを安全に使うためのコツ
文字列を必ずヌル終端する初期化と代入の方法
strlenを安全に使うには、「文字列は必ずヌル終端されている」という前提を守ることが欠かせません。
そのための基本的な方法をいくつか紹介します。
まず、文字列リテラルで配列を初期化する方法は、ヌル終端が自動的に挿入されるので安全です。
#include <stdio.h>
#include <string.h>
int main(void) {
// 末尾に自動的に '#include <stdio.h>
#include <string.h>
int main(void) {
// 末尾に自動的に '\0' が付く
char str1[] = "Hello";
// サイズを指定するときは、文字数+1以上にする
char str2[10] = "Hi"; // "Hi" + '\0' + 残りの領域
printf("str1: %s (len=%zu)\n", str1, strlen(str1));
printf("str2: %s (len=%zu)\n", str2, strlen(str2));
return 0;
}
' が付く
char str1[] = "Hello";
// サイズを指定するときは、文字数+1以上にする
char str2[10] = "Hi"; // "Hi" + '#include <stdio.h>
#include <string.h>
int main(void) {
// 末尾に自動的に '\0' が付く
char str1[] = "Hello";
// サイズを指定するときは、文字数+1以上にする
char str2[10] = "Hi"; // "Hi" + '\0' + 残りの領域
printf("str1: %s (len=%zu)\n", str1, strlen(str1));
printf("str2: %s (len=%zu)\n", str2, strlen(str2));
return 0;
}
' + 残りの領域
printf("str1: %s (len=%zu)\n", str1, strlen(str1));
printf("str2: %s (len=%zu)\n", str2, strlen(str2));
return 0;
}
str1: Hello (len=5)
str2: Hi (len=2)
一方で、1文字ずつ代入する場合は、最後に必ず'\0'を書き込む必要があります。
#include <stdio.h>
#include <string.h>
int main(void) {
char str[6]; // "Hello" + '#include <stdio.h>
#include <string.h>
int main(void) {
char str[6]; // "Hello" + '\0' でちょうど6バイト
str[0] = 'H';
str[1] = 'e';
str[2] = 'l';
str[3] = 'l';
str[4] = 'o';
str[5] = '\0'; // これを忘れると危険
printf("str: %s (len=%zu)\n", str, strlen(str));
return 0;
}
' でちょうど6バイト
str[0] = 'H';
str[1] = 'e';
str[2] = 'l';
str[3] = 'l';
str[4] = 'o';
str[5] = '#include <stdio.h>
#include <string.h>
int main(void) {
char str[6]; // "Hello" + '\0' でちょうど6バイト
str[0] = 'H';
str[1] = 'e';
str[2] = 'l';
str[3] = 'l';
str[4] = 'o';
str[5] = '\0'; // これを忘れると危険
printf("str: %s (len=%zu)\n", str, strlen(str));
return 0;
}
'; // これを忘れると危険
printf("str: %s (len=%zu)\n", str, strlen(str));
return 0;
}
str: Hello (len=5)
ヌル終端を忘れるとstrlenは安全に使えないので、手動で代入するときこそ注意が必要です。
入力関数とstrlenを組み合わせるときの注意点
ユーザーから文字列を入力して、その長さをstrlenで求めたい場面も多くあります。
このとき、どの入力関数を使うかによって、安全性が大きく変わります。
代表的な入力方法としてscanfとfgetsがあります。
scanf(“%s”, …) を使う場合の注意
scanf("%s", buf);のように書くと、空白文字(スペースや改行)までを1つの単語として読み取り、末尾に'\0'を自動的に追加してくれます。
しかし、読み取る最大文字数を指定しないとバッファオーバーフローの危険があります。
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[10];
printf("単語を入力してください: ");
// 最大9文字まで読み込み('#include <stdio.h>
#include <string.h>
int main(void) {
char buf[10];
printf("単語を入力してください: ");
// 最大9文字まで読み込み('\0'の分を1つ残す)
scanf("%9s", buf);
printf("入力された文字列: %s (len=%zu)\n", buf, strlen(buf));
return 0;
}
'の分を1つ残す)
scanf("%9s", buf);
printf("入力された文字列: %s (len=%zu)\n", buf, strlen(buf));
return 0;
}
単語を入力してください: Hello
入力された文字列: Hello (len=5)
このように形式指定子に最大文字数を指定しておけば、ヌル終端も自動で行われ、strlenを安全に使えます。
fgetsを使う場合の注意
fgetsは、行単位で文字列を読み取る関数で、空白を含む文字列も読み取れるため、より安全な選択肢です。
fgetsも必ずヌル終端された文字列を返すので、strlenと相性が良いです。
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[20];
printf("文字列を入力してください: ");
// 最大19文字+ '#include <stdio.h>
#include <string.h>
int main(void) {
char buf[20];
printf("文字列を入力してください: ");
// 最大19文字+ '\0' を読み込む。改行も含まれることがある
if (fgets(buf, sizeof(buf), stdin) != NULL) {
printf("入力された文字列(そのまま): %s", buf);
printf("strlen(buf): %zu\n", strlen(buf));
}
return 0;
}
' を読み込む。改行も含まれることがある
if (fgets(buf, sizeof(buf), stdin) != NULL) {
printf("入力された文字列(そのまま): %s", buf);
printf("strlen(buf): %zu\n", strlen(buf));
}
return 0;
}
文字列を入力してください: test
入力された文字列(そのまま): test
strlen(buf): 5
ただし、fgetsは改行文字'\n'も読み込む点に注意が必要です。
場合によっては、末尾の改行を取り除いてからstrlenを使いたくなることもあります。
strlenの結果を使ったバッファ長チェックの基本
strlenの結果を利用して、文字列の長さがバッファサイズを超えないかどうかをチェックするのも、よくある使い方です。
例えば、1つの文字列を別の配列にコピーしたいとき、コピー先に十分なサイズがあるかどうかを事前に確認できます。
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Hello";
char dst[10];
// dstに収まるかどうかを事前にチェック
// 必要なのは strlen(src) + 1 バイト('#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Hello";
char dst[10];
// dstに収まるかどうかを事前にチェック
// 必要なのは strlen(src) + 1 バイト('\0'分)であることに注意
size_t required = strlen(src) + 1; // 必要なバイト数
size_t capacity = sizeof(dst); // dstのバイト数
if (required <= capacity) {
// strcpyはバッファオーバーフローの危険がある関数ですが、
// ここでは事前チェック済みのため安全に使用できる
strcpy(dst, src);
printf("コピー成功: %s\n", dst);
} else {
printf("エラー: バッファが小さすぎます(required=%zu, capacity=%zu)\n",
required, capacity);
}
return 0;
}
'分)であることに注意
size_t required = strlen(src) + 1; // 必要なバイト数
size_t capacity = sizeof(dst); // dstのバイト数
if (required <= capacity) {
// strcpyはバッファオーバーフローの危険がある関数ですが、
// ここでは事前チェック済みのため安全に使用できる
strcpy(dst, src);
printf("コピー成功: %s\n", dst);
} else {
printf("エラー: バッファが小さすぎます(required=%zu, capacity=%zu)\n",
required, capacity);
}
return 0;
}
コピー成功: Hello
ここでの重要ポイントは、必要なバイト数はstrlen(src) + 1であるということです。
+1を忘れると、ヌル終端分の領域が足りなくなり、結局危険な状態になってしまいます。
このように、strlenの結果を「ヌル終端分を含めた必要サイズ」に変換してからバッファチェックに使うことが、安全な文字列操作の基本になります。
まとめ
strlenは、C言語で文字列の長さ(正確にはヌル終端までのバイト数)を求めるための基本的な関数です。
理解のポイントは、「ヌル終端」「バイト数と文字数の違い」「sizeofとの違い」の3つです。
特に、ヌル終端されていない配列に対してstrlenを使うことや、日本語などのマルチバイト文字で見た目の文字数と混同することは大きな落とし穴になります。
文字列を必ずヌル終端し、入力関数の特性を理解したうえで、strlenの結果を用いたバッファチェックを行うことで、安全に文字列処理を行えるようになります。
