閉じる

C言語のstrcpyで文字列をコピーする正しい使い方と注意点

C言語で文字列を扱う際、文字配列に別の文字列をコピーする場面は頻繁に出てきます。

そこで使う代表的な関数がstrcpyです。

便利な一方で、サイズチェックがないため誤用すると深刻な不具合につながります。

本記事では、正しい使い方と注意点、用途に応じた代替関数の選び方まで、C言語初心者の方にも分かりやすく解説します。

strcpyとは

文字列をコピーする関数

strcpyは、ヌル終端文字列(終端に'\0'があるchar配列)を、src(コピー元)からdst(コピー先)へ、終端の'\0'を含めて丸ごとコピーする関数です。

コピー先の領域サイズを一切チェックしないため、プログラマが十分な領域を確保しておく必要があります。

振る舞いの要点

  • コピーは'\0'に出会うまで継続し、終端を含めてコピーします。
  • 戻り値はdstへのポインタです。連結などの式に組み込めます。
  • コピー元とコピー先の領域が重なると未定義動作になります。
  • サイズ超過時はバッファオーバーフローになり得ます。

使うヘッダ

strcpy#include <string.h>で宣言されます。

C89以降のISO C標準で利用できます。

インクルード例

C言語
#include <string.h>  // strcpy, strncpy など

書式と引数

以下が標準的な宣言です。

C99以降では最適化に関係するrestrict修飾子が付く実装もあります。

C言語
/* C標準ライブラリの宣言(実装によりrestrictが付くことがあります) */
char *strcpy(char *restrict dst, const char *restrict src);
/*
  引数:
    dst : コピー先の先頭アドレス(書き込み可能な領域)
    src : コピー元の先頭アドレス(ヌル終端文字列)
  戻り値:
    dst と同じポインタ(式で連結して使える)
  重要:
    ・dst のサイズは十分でなければならない
    ・dst と src が重なると未定義動作
*/

strcpyの正しい使い方

基本の例

最小限の安全条件(十分なバッファ、書き込み可能な配列)を満たしていれば、strcpyはシンプルに使えます。

C言語
#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が便利です。

C言語
#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'を含めてコピーします。

終端を含むことを可視化して確認してみます。

C言語
#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

コピー先は書き込み可能な配列

文字列リテラルは書き込み不可です。

次の「悪い例」は未定義動作で、環境によってはクラッシュします。

C言語
/* 悪い例: 絶対に実行しないこと
int main(void) {
    char *dst = "immutable";     // リテラルは読み取り専用領域に置かれる
    strcpy(dst, "OK");           // 書き込もうとして未定義動作
    return 0;
}
*/

安全なコードは、書き込み可能な配列動的に確保した領域を使います。

C言語
#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はサイズを確認しません

コピー先より長い文字列を渡すとバッファオーバーフローになります。

下は安全に回避する一例です。

C言語
#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を使います。

C言語
#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を渡すと未定義動作です。

防御的にチェックしましょう。

C言語
#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常に全体をコピーします。

先頭の数文字だけ欲しい場合は、手動でヌル終端を付けるか他の関数を使います。

C言語
#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の安全な代替と選び方

最優先はサイズを把握してチェックすることです。

そのうえで、用途に応じて以下の選択肢を検討します。

比較表(概要)

関数引数(主要)サイズ指定ヌル終端保証オーバーラップ時戻り値規格/入手性
strcpydst, srcなしあり未定義dstISO C
strncpydst, src, nnで上限切り詰め時は保証なし未定義dstISO C
strlcpydst, src, dstszdstszで上限あり(サイズ>0時)未定義src長BSD系/一部環境
strcpy_sdst, dstsz, srcdstszで上限あり(失敗時は空文字に)未定義errno_tC11 Annex K/主にWindows

※ いずれも重なり領域非対応です。

重なる場合はmemmoveを使います。

strncpyの基本と注意点

strncpy最大n文字までをコピーしますが、切り詰めが起きると終端'\0'を付けません

実用では明示的に終端を保証する書き方を徹底します。

C言語
#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、いくつかの環境で利用できます。

C言語
/* 参考用: 対応環境でのみコンパイル可
#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_sMicrosoft拡張(Annex K系)で、バッファサイズを引数に取り、失敗時は空文字にするなど、より防御的な挙動をします。

主にWindows/MSVCで利用します。

C言語
/* 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"

サイズチェックを最優先

代替関数を使っても、「コピー先のサイズを把握してチェックする」ことが最重要です。

小さなヘルパー関数でミスを減らせます。

C言語
#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引数は未定義動作になること、を確実に守る必要があります。

用途に応じてstrncpystrlcpy(対応環境)、strcpy_s(Windows)といった代替も検討し、常にサイズチェックを最優先で安全な文字列操作を心がけてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!