C言語で文字列を連結する代表的な関数がstrcatです。
使い方は簡単に見えますが、バッファサイズやヌル終端を正しく扱わないと危険なバグにつながります。
この記事では初心者でも安全にstrcatを使いこなせるように、基本から安全な書き方までを段階的に解説します。
C言語のstrcatの基本
文字列はヌル終端
C言語の文字列はchar配列で、文字列の終わりを示すヌル文字'\0'で終端されます。
このヌル終端が無いと、関数はどこまでが文字列か分からず、メモリを読み続けてしまいます。
配列の大きさと見かけの長さは異なる点も意識してください。
メモリ上のイメージ
例えば"ABC"は4バイトで{'A','B','C','\0'}の並びになります。
ヌル終端は文字列長に含まれないものの、領域としては必ず1バイト必要です。
strcatの役割と引数
strcatは、連結先の末尾に連結元を続けてコピーします。
関数プロトタイプと要点を整理します。
連結先の末尾に連結元を追加し、最後にヌル終端を付けるのがstrcatの役割です。
| 項目 | 内容 |
|---|---|
| ヘッダ | #include <string.h> |
| プロトタイプ | char *strcat(char *dest, const char *src); |
| 引数dest | 連結先の書き込み可能なchar配列。事前にヌル終端されている必要あり |
| 引数src | 連結元のヌル終端された文字列 |
| 返り値 | destへのポインタ。チェーン呼び出しにも使える |
| 破壊性 | destの内容を書き換える。srcは変更しない |
兄弟関数としてstrncatがあります。
char *strncat(char *dest, const char *src, size_t n); は、srcから最大n文字を追加し、最後にヌル終端を付けます。安全に使うにはnを残り容量ぴったりに設定することが重要です。
連結先は書き込み可能なchar配列
連結先destは必ず書き込み可能な配列や動的メモリでなければなりません。
文字列リテラルは書き込み不可です。
#include <stdio.h>
#include <string.h>
int main(void) {
// 悪い例: 文字列リテラルは書き込み不可領域
// char *bad = "Hello";
// strcat(bad, " World"); // 未定義動作。実行してはいけません
// 良い例: 書き込み可能な配列
char good[32] = "Hello";
strcat(good, " World");
printf("%s\n", good); // "Hello World"
return 0;
}
Hello World
strcatの使い方
バッファサイズを決める
必要なサイズは「連結したい全文字数 + 1(ヌル終端)」です。
固定長配列を使うなら、最大ケースを見積もって十分な余裕を持たせましょう。
可変長が混じる場合は、動的確保や追従チェックを検討します。
目安の考え方
- 例: 「”Hello”」5文字 + 「”, “」2文字 + 「”world!”」6文字 + 終端1 = 14バイト以上が必要
連結先を空文字で初期化
連結開始前にdestを必ずヌル終端にしておきます。
最短はdest[0] = '\0';です。
#include <string.h>
void init_buffer(char *buf, size_t size) {
// どちらでもOK
buf[0] = '#include <string.h>
void init_buffer(char *buf, size_t size) {
// どちらでもOK
buf[0] = '\0'; // 空文字
// または
// memset(buf, 0, size); // 全ゼロ初期化
}
'; // 空文字
// または
// memset(buf, 0, size); // 全ゼロ初期化
}
基本例
配列を用意し、空文字に初期化してから順にstrcatします。
#include <stdio.h>
#include <string.h>
int main(void) {
char msg[32]; // 余裕のあるバッファ
msg[0] = '#include <stdio.h>
#include <string.h>
int main(void) {
char msg[32]; // 余裕のあるバッファ
msg[0] = '\0'; // 空文字で開始する
strcat(msg, "Hello");
strcat(msg, ", ");
strcat(msg, "world!"); // 合計 5 + 2 + 6 + 1(終端) = 14 <= 32 で安全
printf("%s\n", msg);
return 0;
}
'; // 空文字で開始する
strcat(msg, "Hello");
strcat(msg, ", ");
strcat(msg, "world!"); // 合計 5 + 2 + 6 + 1(終端) = 14 <= 32 で安全
printf("%s\n", msg);
return 0;
}
Hello, world!
複数回連結のコツ
複数回連結するほど、残り容量の追跡が重要になります。
逐次strlenで長さを測ると安全ですが、回数が多いなら残り容量を変数で管理すると効率的です。
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[32];
buf[0] = '#include <stdio.h>
#include <string.h>
int main(void) {
char buf[32];
buf[0] = '\0';
size_t cap = sizeof(buf);
size_t used = strlen(buf); // 現在の長さ
size_t avail = cap - used - 1; // 残りに入る最大文字数
// まず1回目
strncat(buf, "C", avail); // availは終端分を含まない
used = strlen(buf);
avail = cap - used - 1;
// 2回目
strncat(buf, " language", avail);
used = strlen(buf);
avail = cap - used - 1;
// 3回目
strncat(buf, " guide", avail);
printf("%s\n", buf);
return 0;
}
';
size_t cap = sizeof(buf);
size_t used = strlen(buf); // 現在の長さ
size_t avail = cap - used - 1; // 残りに入る最大文字数
// まず1回目
strncat(buf, "C", avail); // availは終端分を含まない
used = strlen(buf);
avail = cap - used - 1;
// 2回目
strncat(buf, " language", avail);
used = strlen(buf);
avail = cap - used - 1;
// 3回目
strncat(buf, " guide", avail);
printf("%s\n", buf);
return 0;
}
C language guide
strncatの第3引数には「残り容量そのもの」を渡すことで、終端分1バイトも含めて安全に収まります。
strcatの注意点
バッファオーバーフローに注意
連結先の容量が足りないとメモリ破壊やクラッシュなどの未定義動作になります。
特にユーザ入力や可変長の文字列を扱うときは注意してください。
// 危険例: 実行しないでください
#include <string.h>
int main(void) {
char small[8] = "Hello"; // 5文字 + 終端 = 6バイト消費
strcat(small, "!!!!!"); // さらに5文字 → 合計11 + 終端で明らかに溢れる
return 0;
}
このようなコードは未定義動作を引き起こします。
常に残り容量を確認しましょう。
ヌル終端が無いと危険
初期化されていないバッファをstrcatに渡すと、終端'\0'を探すために読み続け、予期しない領域にアクセスします。
// 悪い例
char buf[32]; // 初期化されていない
// strcat(buf, "abc"); // 実行すると危険。必ず buf[0] = '// 悪い例
char buf[32]; // 初期化されていない
// strcat(buf, "abc"); // 実行すると危険。必ず buf[0] = '\0' をしてから
' をしてから
自己連結は不可
destとsrcが同じ領域や重なる領域を指すと未定義動作です。
例えばstrcat(buf, buf)やstrcat(buf, buf + 1)は不可です。
別の作業バッファを使うか、設計を見直してください。
文字列リテラルを連結先にしない
文字列リテラルは通常、書き込み不可領域に置かれます。
必ず配列や動的確保したメモリを連結先に使いましょう。
// 悪い例: char *p = "Hi"; strcat(p, "!"); // 未定義動作
char ok[8] = "Hi";
strcat(ok, "!");
安全な書き方
残り容量をチェックする
「バッファ全体のサイズ」から「現在の長さ+終端1」を引いて、残りに入る最大文字数を求めます。
0なら追加できません。
#include <stdio.h>
#include <string.h>
int main(void) {
char path[32] = "/usr";
size_t cap = sizeof(path);
// 追加するパーツ
const char *sep = "/local";
const char *leaf = "/bin";
// 1回目: "/usr" + "/local"
size_t used = strlen(path);
size_t avail = cap - used - 1; // 残りに入れられる最大文字数
strncat(path, sep, avail);
// 2回目: … + "/bin"
used = strlen(path);
avail = cap - used - 1;
strncat(path, leaf, avail);
printf("%s\n", path);
return 0;
}
/usr/local/bin
第3引数には必ず「残りの書き込み可能文字数」を渡す。
誤ってavail + 1を渡すと終端分が重なって溢れます。
strncatで最大長を指定する
strncatは「最大n文字」しか追加しないため、あふれを防ぐ基本手段になります。
ただし仕様上、destが前もってヌル終端であることが前提です。
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[10];
buf[0] = '#include <stdio.h>
#include <string.h>
int main(void) {
char buf[10];
buf[0] = '\0'; // 事前のヌル終端が前提
// 追加したいのは "ABCDEFGHIJ"
const char *src = "ABCDEFGHIJ";
size_t cap = sizeof(buf);
size_t used = strlen(buf);
size_t avail = cap - used - 1; // 9文字まで入る
strncat(buf, src, avail); // 9文字だけ追加、末尾に '\0'
printf("buf=\"%s\" 長さ=%zu\n", buf, strlen(buf));
return 0;
}
'; // 事前のヌル終端が前提
// 追加したいのは "ABCDEFGHIJ"
const char *src = "ABCDEFGHIJ";
size_t cap = sizeof(buf);
size_t used = strlen(buf);
size_t avail = cap - used - 1; // 9文字まで入る
strncat(buf, src, avail); // 9文字だけ追加、末尾に '#include <stdio.h>
#include <string.h>
int main(void) {
char buf[10];
buf[0] = '\0'; // 事前のヌル終端が前提
// 追加したいのは "ABCDEFGHIJ"
const char *src = "ABCDEFGHIJ";
size_t cap = sizeof(buf);
size_t used = strlen(buf);
size_t avail = cap - used - 1; // 9文字まで入る
strncat(buf, src, avail); // 9文字だけ追加、末尾に '\0'
printf("buf=\"%s\" 長さ=%zu\n", buf, strlen(buf));
return 0;
}
'
printf("buf=\"%s\" 長さ=%zu\n", buf, strlen(buf));
return 0;
}
buf="ABCDEFGHI" 長さ=9
より堅牢な手段としてsnprintfでbuf + strlen(buf)に書き込む方法もあります。
ただし本記事ではstrcat/strncatに焦点を当てます。
末尾の’\0’を保証する
操作の前後でヌル終端が維持されているかを常に意識し、必要なら明示的に終端を置きます。
特に外部入力や低レベル処理を挟んだ後は注意が必要です。
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[8];
// たとえば外部APIが buf を埋めるが終端は保証しないケースを想定
// ここでは例として手動で埋める
buf[0] = 'O'; buf[1] = 'K'; buf[2] = 0; // 今回は終端あり。無い可能性もあると想定
// 念のため最終バイトを終端にしておく(保険)
buf[sizeof(buf) - 1] = '#include <stdio.h>
#include <string.h>
int main(void) {
char buf[8];
// たとえば外部APIが buf を埋めるが終端は保証しないケースを想定
// ここでは例として手動で埋める
buf[0] = 'O'; buf[1] = 'K'; buf[2] = 0; // 今回は終端あり。無い可能性もあると想定
// 念のため最終バイトを終端にしておく(保険)
buf[sizeof(buf) - 1] = '\0';
// ここから安全に連結
size_t cap = sizeof(buf);
size_t used = strlen(buf);
size_t avail = cap - used - 1;
strncat(buf, "!", avail);
printf("%s\n", buf);
return 0;
}
';
// ここから安全に連結
size_t cap = sizeof(buf);
size_t used = strlen(buf);
size_t avail = cap - used - 1;
strncat(buf, "!", avail);
printf("%s\n", buf);
return 0;
}
OK!
連結前にbufがヌル終端であることを前提にする関数は多いため、信頼できないデータなら自前で終端を担保してください。
チェックリスト
最後に、連結前に確認したい要点をまとめます。
- 連結先は書き込み可能な
char配列か動的メモリですか(リテラルは不可)。 - 連結先はヌル終端されていますか。開始時は
buf[0] = '\0'で空文字に。 - 残り容量は
sizeof(buf) - strlen(buf) - 1で計算しましたか。 strncatを使うとき、第3引数は「残り容量そのもの」になっていますか。- 自己連結や領域の重なりはありませんか。
- 失敗時に切り捨てられても問題ない設計になっていますか。
まとめ
strcatは「ヌル終端の維持」と「バッファ残量の管理」を徹底すれば、安全かつ簡単に文字列を連結できる関数です。
初期化を怠らず、連結先は必ず書き込み可能な領域を使い、長さは都度計算しましょう。
可変長が絡む場合はstrncatで最大長を指定し、sizeof(buf) - strlen(buf) - 1という定石を守れば、バッファオーバーフローの多くは防げます。
必要に応じて終端の明示保証やより堅牢なAPIの検討も合わせて行い、安全第一の文字列操作を身につけてください。
