
C言語基礎|strcat と strncat の違い
連結は便利、でも油断は禁物!―strcat と strncat を使い分けて、バッファ事故をゼロにしよう。
文字列を作りながら表示したり、ユーザー入力をくっつけたりするとき、C言語では「連結」がよく登場します。
その代表が strcat と strncat です。
どちらも「末尾に追加する」点は同じですが、strcat は無制限に追加しようとするのに対して、strncat は追加する文字数に上限を設けられるのが大きな違いです。
つまり、使い分けのテーマはズバリ 安全性(バッファに収まるか) です。

strcat と strncat の基本(書式と役割)
strcat
- ヘッダ:#include <string.h>
- 形式:char *strcat(char * restrict s1, const char * restrict s2);
何をする命令なのか
- s1 が指す文字列の末尾(\0 の位置)に、s2 の内容を \0 まで全部追加する
- 追加後、s1 の末尾にも \0 が付く
- 返り値は s1(先頭ポインタ)
strncat
- ヘッダ:#include <string.h>
- 形式:char *strncat(char * restrict s1, const char * restrict s2, size_t n);
何をする命令なのか
- s1 の末尾に、s2 の先頭から最大 n 文字ぶん追加する
- 追加のあと、必ず \0 を 1 つ付ける
- 返り値は s1
一番大事な違い(表でスパッと理解)
| 観点 | strcat | strncat |
|---|---|---|
| 追加する量 | s2 を \0 まで全部 | s2 の先頭から最大 n 文字 |
| \0 の扱い | s2 の \0 も含めて連結結果を終端 | 追加後に必ず \0 を1個付ける |
| 安全性 | バッファ不足で事故りやすい | 量を制御できるので比較的安全 |
| 典型用途 | サイズが確実に足りると分かっている場合 | 入力や可変長データをつなぐ場合 |
重要ルール:strncat の n は「空き容量」ではない
ここが初心者がハマりやすいポイントです。
- strncat の n は 追加する最大文字数
- s1 の残り容量を渡すわけではありません
だから、安全にするには「残り容量(終端\0の分も考慮)」から n を計算します。
図でイメージ(末尾にくっつける)
例:s1 = "CAT"、s2 = "FISH"
連結前
s1: C A T \0 . . . .
s2: F I S H \0
strcat(s1, s2) の結果
s1: C A T F I S H \0
strncat で n=2 なら:
strncat(s1, s2, 2) の結果
s1: C A T F I \0
連結後の最大サイズ(安全計算の目安)
strncat(s1, s2, n) のあと、s1 の文字数(\0 を含む)は最大でこうなります。
- 連結後の最大サイズ(\0込み)
strlen(連結前の s1) + n + 1
つまり、s1 の配列サイズを cap とすると、安全にするには
- strlen(s1) + n + 1 <= cap
を満たす必要があります。
サンプルプログラム
「挨拶メッセージを組み立てる」プログラム例です。
ポイントは、strcat の危険な使い方と、strncat の安全な使い方(n計算)を両方が確認できることです。
プロジェクト名:chap11-10-1 ソースファイル名:chap11-10-1.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
#include <stdio.h>
#include <string.h>
int main(void)
{
char name[32];
char msg[64] = "こんにちは、";
const char *tail = "さん!";
printf("名前を入力してください:");
scanf("%31s", name);
// 危険:msg の残り容量を考えず strcat すると、長い入力で溢れる可能性がある
// strcat(msg, name);
// 安全寄り:残り容量から「追加できる最大文字数」を計算して strncat
size_t cap = sizeof(msg);
size_t len = strlen(msg);
if (len + 1 < cap) {
size_t rest = cap - len - 1; // 終端\0の分を引いた「追加できる文字数」
strncat(msg, name, rest);
}
// tail も同様に安全に追加
len = strlen(msg);
if (len + 1 < cap) {
size_t rest = cap - len - 1;
strncat(msg, tail, rest);
}
printf("完成メッセージ:%s\n", msg);
return 0;
}この例のいいところ:
- scanf は %31s にして入力長を制限
- msg へ追加するときは 残り容量 rest を計算して strncat
- strcat をコメントで「危険例」として残し、実務の勘どころを押さえています
返り値が便利(連結しながら表示できる)
strcat も strncat も s1 を返すので、次のように書けます。
printf("%s\n", strcat(msg, "追加"));
ただし、これは msg の容量に絶対余裕があるときだけにしましょう。
読みやすいけど、事故ったときに原因が見えにくくなりがちです。
誤った使い方(文字列リテラルに連結しない)
次はダメです。
char *s = "Soft";
strcat(s, "Bank"); // ダメ
理由は2つあります。
- 文字列リテラルが書き換え可能な領域かどうかは処理系依存
- そもそも "Soft" の後ろに書き込める空きがある保証がない。
連結するなら、必ず「書き換え可能で十分なサイズの配列」を用意します。
演習問題
演習11-5
次の仕様の関数 my_strcat と my_strncat を作成してください。
- my_strcat(d, s) は d の末尾に s を \0 まで連結して d を返す
- my_strncat(d, s, n) は s の先頭から最大 n 文字を連結し、最後に \0 を付けて d を返す
プロトタイプ:
#include <stddef.h>
char *my_strcat(char *d, const char *s);
char *my_strncat(char *d, const char *s, size_t n);
解答例
#include <stddef.h>
char *my_strcat(char *d, const char *s)
{
char *t = d;
while (*d != '\0')
d++;
while ((*d++ = *s++) != '\0')
;
return t;
}
char *my_strncat(char *d, const char *s, size_t n)
{
char *t = d;
while (*d != '\0')
d++;
while (n > 0 && *s != '\0') {
*d++ = *s++;
n--;
}
*d = '\0'; // 追加後は必ず終端を付ける
return t;
}解説
- どちらも最初に d の末尾(\0位置)まで進めてから追加します。
- my_strncat は最大 n 文字だけ追加し、最後に必ず \0 を入れるのが特徴です。
