
C言語の文字列は単なる文字の集まりではなく、メモリ上で連続した領域に配置された文字の配列だ。他の言語と異なり、C言語では文字列型という専用のデータ型が存在しない。代わりに文字配列またはポインタを使用して文字列を表現する。
C言語における文字列の基礎知識
C言語の文字列には最大の特徴がある。それは必ずヌル文字(\0)で終わることだ。このヌル文字は文字列の終端を示す目印として機能し、文字列操作関数はこのヌル文字を検出するまで処理を続ける。C言語の内部ではこのヌル文字を使って文字列の長さや終端位置を判断するのだ。
メモリ上での文字列の構造を理解するには配列のイメージを持つとよい。例えば「Hello」という文字列を格納するとき、実際には6バイトのメモリ領域が必要になる。5文字の「Hello」と1バイトのヌル文字だ。この構造のおかげで、C言語は文字列の長さを保持する変数を別途用意せずに済む。
char greeting[6] = "Hello"; /* メモリ上では H,e,l,l,o,\0 と配置される */
文字列操作の基本は配列の要素にアクセスすることから始まる。各文字には添字(インデックス)を使ってアクセスでき、最初の文字は添字0から始まる。この仕組みによって個々の文字を読み書きする。
#include <stdio.h>
int main() {
char name[10] = "Taro"; // "Taro" を格納
name[0] = 'J'; // 0番目の文字'T'を'J' に変更
printf("Updated name: %s\n", name); // 結果を表示
return 0;
}
出力
Updated name: Jaro
文字列の長さにヌル文字自体は含まれない。しかし、文字列を格納するバッファサイズを考えるときはヌル文字分のスペースも確保しなければならない。
文字列の宣言と初期化の方法
C言語で文字列を扱うには、文字配列として宣言する。
文字配列による宣言では、必要なサイズを指定して領域を確保する。この方法の最大の利点は、文字列の内容を後から変更できる点にある。
char name[20] = "Yamada"; /* 20文字まで格納可能な配列を確保 */
char city[] = "Tokyo"; /* 自動的に必要なサイズ(6バイト)が確保される */
配列サイズを明示的に指定すると、その範囲内で後から文字列を変更できる。サイズを省略すると、初期化する文字列のサイズ(ヌル文字含む)に合わせて自動的に決定される。
#include <stdio.h>
int main() {
char name[20] = "Yamada";
printf("%s\n", name);
return 0;
}
出力
Yamada
文字列を1文字ずつ初期化することも可能だが、冗長になりがちだ。
#include <stdio.h>
#include <string.h>
int main() {
char name[8];
name[0] = 'Y';
name[1] = 'a';
name[2] = 'm';
name[3] = 'a';
name[4] = 'd';
name[5] = 'a';
name[6] = '\0'; /* 忘れずにヌル文字を追加 */
printf("%s\n", name);
return 0;
}
出力
Yamada
宣言後に文字列全体を代入するには、strcpy()関数を使用する。この関数は文字列をコピーするための標準ライブラリ関数だ。
#include <stdio.h>
#include <string.h>
int main() {
char name[20] = "Yamada";
strcpy(name, "Tanaka");
printf("%s\n", name);
return 0;
}
出力
Tanaka
strcpy()関数を使うには、C言語の標準ライブラリの#include が必要になる。
文字列の初期化や代入においては、必ず配列サイズが十分であることを確認する習慣をつけよう。不足しているとバッファオーバーフローが発生し、深刻なセキュリティホールになりかねない。
文字列終端子(ヌル文字)の重要性
C言語の文字列操作において、ヌル文字(\0)は基盤となる重要な要素だ。この1バイトの文字は値が0であり、文字列の終わりを明確に示す役割を担う。
ヌル文字が果たす最も重要な機能は、文字列の終端位置を関数に伝えることだ。例えばprintf()関数はヌル文字に到達するまで文字を出力し続ける。ヌル文字がないと、メモリ上の不定なデータまで読み続け、予期せぬ動作を引き起こす。
char greeting[5] = {'H', 'e', 'l', 'l', 'o'}; /* ヌル文字がない - 危険 */
char correct[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; /* 正しい宣言 */
文字列リテラルを使った初期化では、コンパイラが自動的にヌル文字を追加する。
char name[] = "Tanaka"; /* 内部では T,a,n,a,k,a,\0 となる */
文字列関数はヌル文字を前提に設計されている。strlen()関数は文字数をカウントするとき、ヌル文字までの文字数を返す。このとき、ヌル文字自体はカウントされない。
/* "Hello"の長さは5と表示される(ヌル文字は含まない) */
printf("文字列の長さ: %d\n", strlen("Hello"));
配列サイズを計算するときは、常にヌル文字のスペースを確保する習慣をつけよう。最大9文字の名前を格納したいなら、少なくとも10バイトの配列が必要になる。
ヌル文字の欠如はメモリ破壊やセグメンテーション違反など深刻なエラーの原因となる。文字列処理のバグの多くはこのヌル文字の扱いに起因するため、意識して正しく管理しよう。
文字列操作の基本関数(strcpy, strcat, strlen)
C言語の標準ライブラリは文字列操作に必要な多様な関数を提供している。これらの関数を使いこなすにはstring.hヘッダファイルをインクルードする必要がある。
頻繁に使われる基本関数を見ていこう。
strlen()関数
strlen()関数は”string length”(文字列の長さ)の略で、文字列の長さを計算する。この関数はヌル文字が現れるまでの文字数を返す。ヌル文字自体はカウントしない点に注意が必要だ。
#include <stdio.h> // printf() を使うために必要
#include <string.h> // strlen() を使うために必要
int main() {
char message[] = "Hello"; // 文字列を定義
size_t length = strlen(message); // 文字列の長さを取得
printf("文字列の長さ: %zu\n", length); // size_t の出力には %zu を使う
return 0;
}
出力
文字列の長さ: 5
strcpy()関数
strcpy()関数は文字列をコピーする。第一引数にコピー先、第二引数にコピー元を指定する。この関数はヌル文字もコピーするため、コピー先の配列は十分な大きさを確保しておかなければならない。
#include <stdio.h> // printf() を使うために必要
#include <string.h> // strcpy() を使うために必要
int main() {
char destination[20]; // 十分なサイズの配列を用意
strcpy(destination, "Copy this text"); // 文字列をコピー
printf("コピーした文字列: %s\n", destination); // コピーされた文字列を表示
return 0;
}
出力
コピーした文字列: Copy this text
strcat()関数
strcat()関数は二つの文字列を連結する。第一引数の文字列の末尾に第二引数の文字列を追加する。この関数も十分なバッファサイズが必要だ。
#include <stdio.h> // printf() を使うために必要
#include <string.h> // strcat() を使うために必要
int main() {
char name[50] = "Hello, "; // 十分なサイズの配列を用意
strcat(name, "World!"); // 文字列を結合
printf("結合された文字列: %s\n", name); // 結果を表示
return 0;
}
結合された文字列: Hello, World!
strncpy()関数
安全性を高めたstrncpy()関数もよく使われる。これらの関数は最大コピー文字数を指定できるため、バッファオーバーフローを防止できる。
#include <stdio.h>
#include <string.h>
int main() {
char name[10]; // 10バイトの配列を確保
// 最大9文字までコピーし、最後にヌル終端を追加
strncpy(name, "Too long string", 9);
name[9] = '\0'; // 確実にヌル終端させる
printf("コピーされた文字列: %s\n", name);
return 0;
}
出力
コピーされた文字列: Too long
strncpy() は 「string copy (n)」の略で、「指定した最大文字数(n 文字)までコピーする関数」 という意味。strcpy() と違い、コピーする長さを指定できる ため、バッファオーバーフローを防ぐ目的で使われる。
これらの基本関数を適切に組み合わせることで、複雑な文字列操作も実現できる。標準関数はバッファサイズのチェックを自動で行わないため、責任を持って安全なコードを書く必要がある。最近の規格では、より安全な文字列処理のためにstrncpy_s()などの関数も追加されている。
まとめ
文字列操作はほぼすべてのプログラムで必要となる基本スキルであり、これらの関数を使いこなせると、C言語プログラミングの幅が大きく広がる。