C言語で文字列を扱う際、避けて通れないのがstrcpy関数による文字列コピーです。
便利な一方で、使い方を誤るとバッファオーバーフローやクラッシュの原因になります。
本記事では、C言語の文字列の基本から、strcpyの安全な使い方、実践的な活用例、ありがちな誤用パターンまで、図解とサンプルコードを交えながら丁寧に解説します。
strcpyとは何か
C言語の文字列とnull文字の基本

C言語における文字列は、null文字(cst-code\0)で終端されたchar配列として表現されます。
ほとんどの標準ライブラリ関数は、このnull終端を頼りに文字列の長さを判定します。
例えば次の宣言を見てください。
char s1[6] = "Hello"; // 'H','e','l','l','o','\0'
char s2[] = "Hello"; // コンパイラが必要な要素数(6)を自動計算
配列の中身は次のようになっています。
- s1[0]〜s1[4]: それぞれ文字’H’,’e’,’l’,’l’,’o’
- s1[5]: 文字列終端を表す
'\0'(null文字)
この終端の'\0'が存在しないと、C標準ライブラリ関数はどこまでが文字列か判断できません。
その結果、メモリ領域外まで読み続けてしまい、想定外の動作やクラッシュが発生します。
文字列リテラルとの違い

文字列リテラルも実体はnull終端された文字列ですが、通常は読み取り専用領域に配置されます。
char *p = "Hello"; // pは"Hello"というリテラルを指すポインタ
多くの処理系では、このリテラル領域を書き換えようとすると未定義動作(クラッシュなど)になります。
そのため書き換えたい場合は、char配列を用意してstrcpyでコピーしてから操作するのが一般的です。
strcpyの概要とできること

strcpyは、ある文字列を、別の文字配列(バッファ)にコピーする関数です。
コピー先の配列に'\0'を含めた文字列全体を丸ごと転送します。
できることを整理すると次の通りです。
- src(コピー元)の文字列を、dest(コピー先)にそっくりそのまま複製する
- 終端の
'\0'も必ずコピーする - コピー先には、それまでの中身に関係なく新しい文字列が上書きされる
注意点として、strcpy自身はコピー先のサイズを一切チェックしません。
安全かどうかを確認するのはプログラマ側の責任です。
strcpyとstrncpyの違い

strncpyはstrcpyの「安全版」と言われることが多いですが、実際には用途が異なる関数です。
主な違いは次のようになります。
| 項目 | strcpy | strncpy |
|---|---|---|
| プロトタイプ | char *strcpy(char *dest, const char *src); | char *strncpy(char *dest, const char *src, size_t n); |
| 第3引数 | なし | コピーする最大文字数nを指定 |
| ‘\0’終端 | srcに’\0’があれば必ずコピーされる | srcがn文字以上だと、destは終端されない |
| 用途 | 完全な文字列コピー | 固定長領域へのコピー・パディング用途 |
strncpyは「バッファオーバーフローを完全に防ぐ魔法の関数」ではありません。
nの値によってはコピー先がnull終端されないため、その後printfやstrlenに渡すと危険です。
そのため、実務ではstrncpy単体ではなく、n - 1バイトだけコピーして、最後に自分で'\0'を付ける、といったパターンを使うことが多いです。
strcpyの基本的な使い方
strcpyの関数プロトタイプとヘッダファイル
strcpyは文字列操作関数群<string.h>に含まれています。
#include <string.h>
char *strcpy(char *dest, const char *src);
dest: コピー先のバッファ(書き込み可能であることが必須)src: コピー元の文字列(終端'\0'を含む必要がある)- 戻り値: destへのポインタがそのまま返される
ヘッダをインクルードし忘れると、古いCコンパイラでは暗黙の宣言となり、型がずれて未定義動作を引き起こす可能性があります。
必ず#include <string.h>を付けてください。
char配列への文字列コピーの基本例

#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Hello"; // コピー元の文字列
char dest[16]; // コピー先のバッファ(十分に大きく確保)
// srcの内容をdestにコピー
strcpy(dest, src);
// 結果を表示
printf("src : %s\n", src);
printf("dest: %s\n", dest);
return 0;
}
src : Hello
dest: Hello
上の例では、コピー先destのサイズ(16バイト)は、コピー元”Hello”の長さ(5文字)+終端1バイトより十分大きいので、安全にコピーできます。
リテラル文字列をchar配列にstrcpyする方法

文字列リテラルをそのまま配列にコピーすることもよくあります。
#include <stdio.h>
#include <string.h>
int main(void) {
char message[16];
// 文字列リテラルを配列にコピー
strcpy(message, "World");
printf("message: %s\n", message);
// 配列側は書き換え可能
message[0] = 'w';
printf("modified message: %s\n", message);
return 0;
}
message: World
modified message: world
直接"World"[0] = 'w';のようにリテラルを書き換えることは未定義動作ですが、配列にコピーしてしまえば自由に書き換えられます。
strcpyの戻り値と活用パターン
strcpyはコピー先ポインタdestをそのまま返します。
この仕様を利用して、関数呼び出しをつなげることもできます。
#include <stdio.h>
#include <string.h>
int main(void) {
char buffer[32];
// 戻り値をそのままprintfに渡す例
printf("コピー結果: %s\n", strcpy(buffer, "Hello, strcpy!"));
// 戻り値を別のポインタで受けることも可能
char *p = strcpy(buffer, "Second");
printf("buffer: %s\n", buffer);
printf("p : %s\n", p); // pはbufferを指す
return 0;
}
コピー結果: Hello, strcpy!
buffer: Second
p : Second
実務ではstrcpyの戻り値を使わないことも多いですが、関数チェーンの一部として使えるという点は知っておくと便利です。
strcpyの安全な使い方と注意点
strcpyで起こるバッファオーバーフローの危険性

strcpyはコピー先の配列サイズを全く気にせず、srcの末尾の'\0'まで書き込みます。
コピー先が小さいと、次のような重大な問題が起きます。
#include <stdio.h>
#include <string.h>
int main(void) {
char small[8];
char *long_str = "This is a very long string";
// 非常に危険なコード例(絶対に真似しないこと)
strcpy(small, long_str); // バッファオーバーフローの可能性大
printf("small: %s\n", small); // ここまで到達しないこともある
return 0;
}
このようなコードは、クラッシュ・データ破壊・セキュリティホールの原因になります。
特に外部入力(ユーザー入力やネットワークからの文字列)をstrcpyする際は、コピー先のサイズが十分かどうか必ずチェックしてください。
strcpyを使う前に確認すべき配列サイズ

安全にstrcpyを使うためには、コピー先配列が「コピー元の文字数+1以上」のサイズを持っていることを確認する必要があります。
#include <stdio.h>
#include <string.h>
int main(void) {
char dest[16];
const char *src = "Hello";
size_t src_len_with_null = strlen(src) + 1; // '\0'分を足す
if (sizeof(dest) >= src_len_with_null) {
// サイズが十分な場合のみstrcpyを使う
strcpy(dest, src);
printf("コピー成功: %s\n", dest);
} else {
printf("エラー: バッファが小さすぎます\n");
}
return 0;
}
コピー成功: Hello
配列変数のサイズを調べるにはsizeof(dest)が使えますが、ポインタには使えない点も重要です(後述の誤用例で触れます)。
strcpyと配列長を組み合わせたガード方法

実務では、strcpyのラッパ関数(安全版)を自前で用意して使うこともあります。
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
// 安全なコピーを行うラッパ関数
bool safe_strcpy(char *dest, size_t dest_size, const char *src) {
size_t len = strlen(src) + 1; // '\0'込みの長さ
if (dest_size < len) {
// 入りきらない場合はコピーせずfalseを返す
return false;
}
strcpy(dest, src);
return true;
}
int main(void) {
char buf[8];
if (!safe_strcpy(buf, sizeof(buf), "Hello")) {
printf("コピー失敗(バッファ不足)\n");
return 1;
}
printf("コピー成功: %s\n", buf);
// わざと長い文字列を試す
if (!safe_strcpy(buf, sizeof(buf), "Too long!")) {
printf("2回目はコピー失敗(安全に検出)\n");
}
return 0;
}
コピー成功: Hello
2回目はコピー失敗(安全に検出)
このように「サイズを一緒に渡す」という設計にしておくと、安全性が大きく向上します。
strncpyやstrlcpyなど安全な代替関数

標準Cライブラリには完全に安全なstrcpyの代替は存在しませんが、状況に応じて次のような関数が使われます。
| 関数名 | 標準C | 主な特徴 |
|---|---|---|
| strcpy | ○ | サイズ指定なし、もっとも単純だが危険 |
| strncpy | ○ | 最大n文字までコピー、終端されないことがある |
| strlcpy | ×(BSD系など) | destサイズを渡し、必ず終端を付ける設計 |
strncpyを「より安全」に使うパターンの例です。
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[8];
const char *src = "ABCDEFGH"; // 8文字
// bufのサイズ-1だけコピーし、最後に必ず終端を付ける
strncpy(buf, src, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
printf("buf: %s\n", buf); // "ABCDEFG" (最大7文字+終端)
return 0;
}
buf: ABCDEFG
strlcpyはPOSIX標準ではありませんが、BSD系OSや一部のライブラリで使われている設計の良い関数です。
// 典型的なstrlcpyのプロトタイプ(実装依存)
size_t strlcpy(char *dest, const char *src, size_t dest_size);
dest_sizeを渡し、そのサイズを超えないようにコピー- 可能な限りコピーしつつ必ず
'\0'終端を行う - 戻り値で
srcの本来の長さを返す(切り詰めが発生したか分かる)
環境が許すならstrcpyよりstrlcpyを優先的に検討する価値があります。
strcpyとmallocを使った動的メモリへのコピー

固定長配列では足りない場合や、長さが実行時にしか分からない場合は動的メモリ確保と組み合わせて使います。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *duplicate_string(const char *src) {
// '\0'込みのサイズを計算
size_t len = strlen(src) + 1;
// 必要なサイズだけmalloc
char *p = malloc(len);
if (p == NULL) {
return NULL; // メモリ確保失敗
}
// 動的に確保した領域にコピー
strcpy(p, src);
return p; // 呼び出し元でfreeする
}
int main(void) {
const char *original = "dynamic strcpy";
char *copy = duplicate_string(original);
if (copy == NULL) {
fprintf(stderr, "メモリ確保に失敗しました\n");
return 1;
}
printf("original: %s\n", original);
printf("copy : %s\n", copy);
free(copy); // 確保したメモリは必ず解放する
return 0;
}
original: dynamic strcpy
copy : dynamic strcpy
このようにmallocとstrcpyを組み合わせると、文字列の「完全な複製」を作ることができます。
POSIX環境ならstrdupという関数が同じ処理をまとめて提供しています。
マルチバイト文字列とstrcpyの注意点

日本語などのマルチバイト文字列(UTF-8, Shift-JISなど)を扱う場合も、strcpyは「バイト列」としてコピーするだけです。
文字単位ではなくバイト数でサイズを考える必要があります。
#include <stdio.h>
#include <string.h>
int main(void) {
// このソースコードがUTF-8として保存されていると仮定
const char *jp = "あい"; // UTF-8では通常1文字3バイトで計6バイト + 終端
printf("strlen(jp) = %zu\n", strlen(jp)); // 通常は6と表示される
char buf[8];
// bufは8バイトなので、6バイト+終端1バイト(計7バイト)は収まる
strcpy(buf, jp);
printf("buf = %s\n", buf);
return 0;
}
strlen(jp) = 6
buf = あい
マルチバイトだからといって特別なコピー関数が必要というわけではありませんが、「何文字入るか」ではなく「何バイト入るか」でバッファ設計をすることが重要です。
実践的なstrcpy活用例
構造体内のchar配列フィールドへの文字列コピー

構造体の中にchar name[32];のようなフィールドを持たせることはよくあります。
#include <stdio.h>
#include <string.h>
typedef struct {
char name[32];
int age;
} Person;
int main(void) {
Person p;
// nameフィールドに文字列をコピー
strcpy(p.name, "Taro");
p.age = 20;
printf("名前: %s, 年齢: %d\n", p.name, p.age);
// 別の値で上書き
strcpy(p.name, "Hanako");
p.age = 18;
printf("名前: %s, 年齢: %d\n", p.name, p.age);
return 0;
}
名前: Taro, 年齢: 20
名前: Hanako, 年齢: 18
このような場合も、構造体内の配列サイズ(ここでは32)を超えない文字列だけをコピーするように設計することが重要です。
外部入力などを入れる場合は、先ほど紹介したsafe_strcpyのようなガードを併用すると安全です。
入力文字列を検証してからstrcpyするパターン

ユーザーからの入力をそのままstrcpyするのは非常に危険です。
一時バッファに読み取り、長さや内容を検証してからコピーするパターンを取りましょう。
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#define NAME_MAX_LEN 31 // 終端を除いた最大文字数
bool is_valid_name(const char *s) {
// ここでは簡易的に、長さチェックと改行禁止だけにする
size_t len = strlen(s);
if (len == 0 || len > NAME_MAX_LEN) {
return false;
}
if (strchr(s, '\n') != NULL) {
return false;
}
return true;
}
int main(void) {
char input[128]; // 入力用の一時バッファ
char name[NAME_MAX_LEN + 1]; // 本番用のバッファ
printf("名前を入力してください(最大%d文字): ", NAME_MAX_LEN);
if (fgets(input, sizeof(input), stdin) == NULL) {
printf("入力エラー\n");
return 1;
}
// 末尾の改行を削除
char *nl = strchr(input, '\n');
if (nl != NULL) {
*nl = '\0';
}
if (!is_valid_name(input)) {
printf("不正な名前です\n");
return 1;
}
// 検証済みの文字列だけをstrcpyでコピー
strcpy(name, input);
printf("こんにちは、%sさん!\n", name);
return 0;
}
名前を入力してください(最大31文字): Taro
こんにちは、Taroさん!
このように「入力 → 一時バッファ → 検証 → 本番バッファにstrcpy」という流れを徹底すると、安全性が高まります。
文字列連結でstrcpyとstrcatを組み合わせる方法

複数の文字列を1つに連結する場合、strcpyとstrcatを組み合わせるのが定番です。
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[64];
// まず最初の文字列をstrcpyで入れる
strcpy(buf, "Hello");
// その後はstrcatで後ろに連結
strcat(buf, ", ");
strcat(buf, "World");
strcat(buf, "!");
printf("%s\n", buf); // "Hello, World!"と表示
return 0;
}
Hello, World!
当然ながら、全体として何文字になるかを事前に計算し、bufのサイズが十分であることを確認しなければなりません。
連結が多段になる場合はsnprintfなどサイズ指定ができる関数の利用も検討してください。
初心者が陥りやすいstrcpyの誤用例と対策

誤用1: ポインタに対してsizeofでサイズを求めてしまう
#include <stdio.h>
#include <string.h>
int main(void) {
char *p = malloc(16);
// 誤り: pは「ポインタ」であってバッファそのものではない
size_t size = sizeof(p); // 多くの環境で8や4になる
printf("sizeof(p) = %zu\n", size);
// これは「サイズチェックしたつもり」になってしまう危険なコード
if (size >= strlen("Hello") + 1) {
strcpy(p, "Hello"); // 実際には16バイトあるのに、sizeは8か4
}
free(p);
return 0;
}
sizeof(p) = 8 (環境による)
mallocで確保した領域のサイズは自分で覚えておくしかありません。
ポインタに対してsizeofを使っても正しいバッファサイズは得られないので注意してください。
誤用2: バッファサイズ不足の宣言
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[5];
// "Hello"は5文字だが、終端'\0'が必要なので6バイト必要
// buf[5]では1バイト足りない
strcpy(buf, "Hello"); // 未定義動作(バッファオーバーフロー)
printf("%s\n", buf);
return 0;
}
「文字数+1」の終端分のバイトを忘れないことが何よりも大切です。
誤用3: 文字列リテラルへの書き込み
#include <stdio.h>
#include <string.h>
int main(void) {
char *p = "Hello";
// pが指しているのは読み取り専用領域の可能性が高い
// このstrcpyは未定義動作になる
strcpy(p, "World"); // 絶対にやってはいけない
printf("%s\n", p);
return 0;
}
文字列を書き換えたい場合は、書き込み可能な配列を用意してからstrcpyしてください。
誤用4: null終端されていないsrcのコピー
#include <stdio.h>
#include <string.h>
int main(void) {
char src[5] = {'H', 'e', 'l', 'l', 'o'}; // '\0'がない
char dest[16];
// srcは文字列ではない(終端がない)
strcpy(dest, src); // strcpyは'\0'を探して読み続けてしまう
printf("%s\n", dest);
return 0;
}
strcpyのsrcには必ずnull終端された文字列を渡す必要があります。
バイナリデータや終端のない配列には使用しないでください。
まとめ
strcpyは、C言語で文字列をコピーする最も基本的な関数です。
一方で、コピー先バッファのサイズを一切チェックしないため、使い方を誤るとバッファオーバーフローやセキュリティ事故につながります。
配列サイズとstrlenを必ず比較し、「文字数+1」バイトの終端を含めた設計を守ることが重要です。
必要に応じて安全なラッパ関数やstrncpy、動的メモリ確保を組み合わせ、入力検証を行ったうえでstrcpyを使えば、Cらしい軽量さを保ちつつ安全に文字列を扱うことができます。
