C言語で文字列を扱う際、文字配列に別の文字列をコピーする場面は頻繁に出てきます。
そこで使う代表的な関数がstrcpy
です。
便利な一方で、サイズチェックがないため誤用すると深刻な不具合につながります。
本記事では、正しい使い方と注意点、用途に応じた代替関数の選び方まで、C言語初心者の方にも分かりやすく解説します。
strcpyとは
文字列をコピーする関数
strcpy
は、ヌル終端文字列(終端に'\0'
があるchar
配列)を、src
(コピー元)からdst
(コピー先)へ、終端の'\0'
を含めて丸ごとコピーする関数です。
コピー先の領域サイズを一切チェックしないため、プログラマが十分な領域を確保しておく必要があります。
振る舞いの要点
- コピーは
'\0'
に出会うまで継続し、終端を含めてコピーします。 - 戻り値は
dst
へのポインタです。連結などの式に組み込めます。 - コピー元とコピー先の領域が重なると未定義動作になります。
- サイズ超過時はバッファオーバーフローになり得ます。
使うヘッダ
strcpy
は#include <string.h>
で宣言されます。
C89以降のISO C標準で利用できます。
インクルード例
#include <string.h> // strcpy, strncpy など
書式と引数
以下が標準的な宣言です。
C99以降では最適化に関係するrestrict
修飾子が付く実装もあります。
/* C標準ライブラリの宣言(実装によりrestrictが付くことがあります) */
char *strcpy(char *restrict dst, const char *restrict src);
/*
引数:
dst : コピー先の先頭アドレス(書き込み可能な領域)
src : コピー元の先頭アドレス(ヌル終端文字列)
戻り値:
dst と同じポインタ(式で連結して使える)
重要:
・dst のサイズは十分でなければならない
・dst と src が重なると未定義動作
*/
strcpyの正しい使い方
基本の例
最小限の安全条件(十分なバッファ、書き込み可能な配列)を満たしていれば、strcpy
はシンプルに使えます。
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Hello, C!"; // ヌル終端付きのコピー元
char dst[32]; // 十分大きいコピー先(書き込み可能な配列)
char *ret = strcpy(dst, src); // 文字列を完全コピー(終端 '#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Hello, C!"; // ヌル終端付きのコピー元
char dst[32]; // 十分大きいコピー先(書き込み可能な配列)
char *ret = strcpy(dst, src); // 文字列を完全コピー(終端 '\0' 含む)
// 結果の確認
printf("dst: \"%s\"\n", dst);
printf("戻り値はdstと同じか: %s\n", (ret == dst) ? "YES" : "NO");
return 0;
}
' 含む)
// 結果の確認
printf("dst: \"%s\"\n", dst);
printf("戻り値はdstと同じか: %s\n", (ret == dst) ? "YES" : "NO");
return 0;
}
dst: "Hello, C!"
戻り値はdstと同じか: YES
コピー先は配列などの書き込み可能領域で十分なサイズを確保することが最重要です。
コピー先のサイズは文字数+1
Cの文字列は最後に'\0'
を持つため、長さ+1バイトが必要です。
リテラルから配列を作る場合はsizeof
が便利です。
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "abc"; // 長さ3、実際の配列サイズは4(終端込み)
char dst[sizeof(src)]; // 終端込みのちょうどよいサイズ
strcpy(dst, src);
printf("srcのstrlen: %zu\n", strlen(src)); // 3
printf("dstの配列サイズ(sizeof): %zu\n", sizeof(dst)); // 4
printf("dst: \"%s\"\n", dst); // 正しくコピーされる
return 0;
}
srcのstrlen: 3
dstの配列サイズ(sizeof): 4
dst: "abc"
ヒント
- リテラルを別配列にコピーするときは
char dst[sizeof("文字列")];
のように書くと、終端を含めたサイズが確保できます。 - 実行時にサイズが決まる場合は
strlen(src) + 1
を基準に確保します。
ヌル終端(‘\0’)を含める
strcpy
は'\0'
を含めてコピーします。
終端を含むことを可視化して確認してみます。
#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Hi";
char dst[sizeof(src)]; // "H","i","#include <stdio.h>
#include <string.h>
int main(void) {
char src[] = "Hi";
char dst[sizeof(src)]; // "H","i","\0" の3バイト
strcpy(dst, src);
// 各バイトの数値を表示(終端が0になっていることを確認)
for (size_t i = 0; i < sizeof(dst); ++i) {
printf("%02X ", (unsigned char)dst[i]);
}
printf("\n表示: %s\n", dst);
return 0;
}
" の3バイト
strcpy(dst, src);
// 各バイトの数値を表示(終端が0になっていることを確認)
for (size_t i = 0; i < sizeof(dst); ++i) {
printf("%02X ", (unsigned char)dst[i]);
}
printf("\n表示: %s\n", dst);
return 0;
}
48 69 00
表示: Hi
コピー先は書き込み可能な配列
文字列リテラルは書き込み不可です。
次の「悪い例」は未定義動作で、環境によってはクラッシュします。
/* 悪い例: 絶対に実行しないこと
int main(void) {
char *dst = "immutable"; // リテラルは読み取り専用領域に置かれる
strcpy(dst, "OK"); // 書き込もうとして未定義動作
return 0;
}
*/
安全なコードは、書き込み可能な配列や動的に確保した領域を使います。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void) {
// 1) 配列を使う
char a[16] = "immutable";
strcpy(a, "OK"); // 配列へは書き込める
printf("配列版: %s\n", a);
// 2) 動的確保を使う
const char *src = "dynamic";
size_t need = strlen(src) + 1;
char *b = (char *)malloc(need); // 必要サイズを確保
if (!b) return 1;
strcpy(b, src);
printf("動的確保版: %s\n", b);
free(b);
return 0;
}
配列版: OK
動的確保版: dynamic
strcpyの注意点と落とし穴
バッファオーバーフローに注意
strcpyはサイズを確認しません。
コピー先より長い文字列を渡すとバッファオーバーフローになります。
下は安全に回避する一例です。
#include <stdio.h>
#include <string.h>
int main(void) {
char small[10];
const char *longsrc = "This is a long string";
if (strlen(longsrc) >= sizeof(small)) {
printf("エラー: コピー先が小さすぎます(必要:%zu, 用意:%zu)\n",
strlen(longsrc) + 1, sizeof(small));
// 対策: 1) バッファを拡張する 2) 代替関数を使う 3) 切り詰めを許容する等
} else {
strcpy(small, longsrc);
printf("コピー成功: %s\n", small);
}
return 0;
}
エラー: コピー先が小さすぎます(必要:22, 用意:10)
領域が重なる場合は使わない
strcpy
は重なり領域でのコピーが未定義です。
自己バッファ内で位置をずらすようなコピーはmemmove
を使います。
#include <stdio.h>
#include <string.h>
int main(void) {
char buf[32] = "ABCDEF";
printf("before: %s\n", buf);
// buf の内容を2文字先頭へ重ねて移動(自己コピー)
// strcpy(buf + 2, buf); // 未定義動作: 使わない
memmove(buf + 2, buf, strlen(buf) + 1); // 終端含めて安全に移動
printf("after : %s\n", buf); // 期待: "ABABCDEF"
return 0;
}
before: ABCDEF
after : ABABCDEF
NULLポインタを渡さない
NULLを渡すと未定義動作です。
防御的にチェックしましょう。
#include <stdio.h>
#include <string.h>
int main(void) {
char *dst = NULL;
const char *src = "hello";
if (!dst || !src) {
printf("エラー: NULLポインタが渡されました\n");
return 1;
}
// 実務ではこの前に十分なサイズがあることも確認する
strcpy(dst, src);
return 0;
}
エラー: NULLポインタが渡されました
部分コピーはできない
strcpy
は常に全体をコピーします。
先頭の数文字だけ欲しい場合は、手動でヌル終端を付けるか他の関数を使います。
#include <stdio.h>
int main(void) {
const char *src = "musashino";
char head3[4]; // 3文字 + 終端
// 先頭3文字を手動でコピーして終端を付ける
head3[0] = src[0];
head3[1] = src[1];
head3[2] = src[2];
head3[3] = '#include <stdio.h>
int main(void) {
const char *src = "musashino";
char head3[4]; // 3文字 + 終端
// 先頭3文字を手動でコピーして終端を付ける
head3[0] = src[0];
head3[1] = src[1];
head3[2] = src[2];
head3[3] = '\0';
printf("部分取得(3文字): %s\n", head3);
return 0;
}
';
printf("部分取得(3文字): %s\n", head3);
return 0;
}
部分取得(3文字): mus
strcpyの安全な代替と選び方
最優先はサイズを把握してチェックすることです。
そのうえで、用途に応じて以下の選択肢を検討します。
比較表(概要)
関数 | 引数(主要) | サイズ指定 | ヌル終端保証 | オーバーラップ時 | 戻り値 | 規格/入手性 |
---|---|---|---|---|---|---|
strcpy | dst, src | なし | あり | 未定義 | dst | ISO C |
strncpy | dst, src, n | nで上限 | 切り詰め時は保証なし | 未定義 | dst | ISO C |
strlcpy | dst, src, dstsz | dstszで上限 | あり(サイズ>0時) | 未定義 | src長 | BSD系/一部環境 |
strcpy_s | dst, dstsz, src | dstszで上限 | あり(失敗時は空文字に) | 未定義 | errno_t | C11 Annex K/主にWindows |
※ いずれも重なり領域非対応です。
重なる場合はmemmove
を使います。
strncpyの基本と注意点
strncpy
は最大n文字までをコピーしますが、切り詰めが起きると終端'\0'
を付けません。
実用では明示的に終端を保証する書き方を徹底します。
#include <stdio.h>
#include <string.h>
int main(void) {
const char *src = "Hello";
char dst[4]; // 3文字 + 終端の想定
// 最大3文字までコピー(切り詰めの可能性あり)
strncpy(dst, src, sizeof(dst) - 1);
dst[sizeof(dst) - 1] = '#include <stdio.h>
#include <string.h>
int main(void) {
const char *src = "Hello";
char dst[4]; // 3文字 + 終端の想定
// 最大3文字までコピー(切り詰めの可能性あり)
strncpy(dst, src, sizeof(dst) - 1);
dst[sizeof(dst) - 1] = '\0'; // 重要: 必ず終端を付ける
printf("結果: \"%s\"\n", dst); // "Hel"
printf("長さ: %zu\n", strlen(dst)); // 3
return 0;
}
'; // 重要: 必ず終端を付ける
printf("結果: \"%s\"\n", dst); // "Hel"
printf("長さ: %zu\n", strlen(dst)); // 3
return 0;
}
結果: "Hel"
長さ: 3
追加の注意
strncpy
は、src
が短い場合にn
まで'\0'
でパディングするため、無駄な埋め操作が発生します。長いバッファに小さな文字列を頻繁にコピーする用途に不向きです。
strlcpy(一部環境)という選択肢
strlcpy
は常に終端を保証する設計で、戻り値にsrc
の長さを返します。
標準Cではありませんが、BSD系やmacOS、いくつかの環境で利用できます。
/* 参考用: 対応環境でのみコンパイル可
#include <stdio.h>
#include <string.h>
int main(void) {
char dst[5];
size_t n = strlcpy(dst, "Orange", sizeof(dst)); // n=6(元の長さ)
printf("dst: \"%s\" (元の長さ: %zu, 実際に入った長さ: %zu)\n",
dst, n, strlen(dst)); // dst: "Oran"
return 0;
}
*/
特徴
- サイズ>0の場合は必ず終端。
- 切り詰め検出が容易(戻り値とバッファサイズ比較)。
- 移植性は限定的。Linux/glibcでは提供されない場合があります。
strcpy_s(Windows)という選択肢
strcpy_s
はMicrosoft拡張(Annex K系)で、バッファサイズを引数に取り、失敗時は空文字にするなど、より防御的な挙動をします。
主にWindows/MSVCで利用します。
/* MSVCなど対応環境でコンパイルしてください
例えば、GCCではstrcpy_sは利用できません
*/
#include <stdio.h>
#include <string.h> // strcpy_s
int main(void) {
#ifdef _MSC_VER
char dst[5];
errno_t e = strcpy_s(dst, sizeof(dst), "Tiger"); // 5文字+終端で溢れる
if (e != 0) {
printf("strcpy_s失敗(errno=%d)。dstは空文字に初期化されます: \"%s\"\n",
(int)e, dst);
}
e = strcpy_s(dst, sizeof(dst), "Cat");
printf("成功後 dst: \"%s\"\n", dst);
#else
printf("この環境ではstrcpy_sは利用できません。\n");
#endif
return 0;
}
strcpy_s失敗(errno=22)。dstは空文字に初期化されます: ""
成功後 dst: "Cat"
サイズチェックを最優先
代替関数を使っても、「コピー先のサイズを把握してチェックする」ことが最重要です。
小さなヘルパー関数でミスを減らせます。
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
/* 安全なコピー: dst_sizeはコピー先配列のサイズ */
bool safe_strcpy(char *dst, size_t dst_size, const char *src) {
if (!dst || !src || dst_size == 0) return false;
size_t i = 0;
for (; i + 1 < dst_size && src[i] != '#include <stdio.h>
#include <string.h>
#include <stdbool.h>
/* 安全なコピー: dst_sizeはコピー先配列のサイズ */
bool safe_strcpy(char *dst, size_t dst_size, const char *src) {
if (!dst || !src || dst_size == 0) return false;
size_t i = 0;
for (; i + 1 < dst_size && src[i] != '\0'; ++i) {
dst[i] = src[i];
}
dst[i] = '\0';
// 切り詰めが起きたかどうかを返すならここで判定可能
return src[i] == '\0'; // trueなら完全コピー、falseなら切り詰め
}
int main(void) {
char a[6];
char b[6];
bool ok1 = safe_strcpy(a, sizeof(a), "Hello"); // ちょうど入る
bool ok2 = safe_strcpy(b, sizeof(b), "Kitten"); // 1文字多いので切り詰め
printf("a: \"%s\" (完全コピー: %s)\n", a, ok1 ? "YES" : "NO");
printf("b: \"%s\" (完全コピー: %s)\n", b, ok2 ? "YES" : "NO");
return 0;
}
'; ++i) {
dst[i] = src[i];
}
dst[i] = '#include <stdio.h>
#include <string.h>
#include <stdbool.h>
/* 安全なコピー: dst_sizeはコピー先配列のサイズ */
bool safe_strcpy(char *dst, size_t dst_size, const char *src) {
if (!dst || !src || dst_size == 0) return false;
size_t i = 0;
for (; i + 1 < dst_size && src[i] != '\0'; ++i) {
dst[i] = src[i];
}
dst[i] = '\0';
// 切り詰めが起きたかどうかを返すならここで判定可能
return src[i] == '\0'; // trueなら完全コピー、falseなら切り詰め
}
int main(void) {
char a[6];
char b[6];
bool ok1 = safe_strcpy(a, sizeof(a), "Hello"); // ちょうど入る
bool ok2 = safe_strcpy(b, sizeof(b), "Kitten"); // 1文字多いので切り詰め
printf("a: \"%s\" (完全コピー: %s)\n", a, ok1 ? "YES" : "NO");
printf("b: \"%s\" (完全コピー: %s)\n", b, ok2 ? "YES" : "NO");
return 0;
}
';
// 切り詰めが起きたかどうかを返すならここで判定可能
return src[i] == '#include <stdio.h>
#include <string.h>
#include <stdbool.h>
/* 安全なコピー: dst_sizeはコピー先配列のサイズ */
bool safe_strcpy(char *dst, size_t dst_size, const char *src) {
if (!dst || !src || dst_size == 0) return false;
size_t i = 0;
for (; i + 1 < dst_size && src[i] != '\0'; ++i) {
dst[i] = src[i];
}
dst[i] = '\0';
// 切り詰めが起きたかどうかを返すならここで判定可能
return src[i] == '\0'; // trueなら完全コピー、falseなら切り詰め
}
int main(void) {
char a[6];
char b[6];
bool ok1 = safe_strcpy(a, sizeof(a), "Hello"); // ちょうど入る
bool ok2 = safe_strcpy(b, sizeof(b), "Kitten"); // 1文字多いので切り詰め
printf("a: \"%s\" (完全コピー: %s)\n", a, ok1 ? "YES" : "NO");
printf("b: \"%s\" (完全コピー: %s)\n", b, ok2 ? "YES" : "NO");
return 0;
}
'; // trueなら完全コピー、falseなら切り詰め
}
int main(void) {
char a[6];
char b[6];
bool ok1 = safe_strcpy(a, sizeof(a), "Hello"); // ちょうど入る
bool ok2 = safe_strcpy(b, sizeof(b), "Kitten"); // 1文字多いので切り詰め
printf("a: \"%s\" (完全コピー: %s)\n", a, ok1 ? "YES" : "NO");
printf("b: \"%s\" (完全コピー: %s)\n", b, ok2 ? "YES" : "NO");
return 0;
}
a: "Hello" (完全コピー: YES)
b: "Kitte" (完全コピー: NO)
実務での指針
ローカル配列にはsizeof(dst)
が使えます。
動的確保では確保サイズを一緒に管理しましょう(構造体でサイズとポインタを束ねるなど)。
ライブラリ境界では、「ポインタ+サイズ」を必ずセットで渡す設計が安心です。
まとめ
strcpyは簡潔で高速な一方、サイズチェックを一切しないため誤用が致命傷になりやすい関数です。
正しく使うには、(1) コピー先は書き込み可能な配列で十分なサイズがあること、(2) ヌル終端を含めて長さ+1が必要であること、(3) 領域の重なりやNULL引数は未定義動作になること、を確実に守る必要があります。
用途に応じてstrncpy、strlcpy(対応環境)、strcpy_s(Windows)といった代替も検討し、常にサイズチェックを最優先で安全な文字列操作を心がけてください。