C言語の配列コピー完全ガイド|memcpy・for文・strcpyの違いと使い方を解説

目次

1. はじめに

C言語における「配列コピー」の重要性

C言語でプログラミングをしていると、配列の中身を別の配列にコピーしたいという場面にたびたび出会います。例えば、データのバックアップを取りたいときや、一時的な処理のために別の変数に値を保持したいときなどです。

しかし、C言語は高級言語と比べてメモリ操作に関するサポートが少なく、配列のコピーも自動で行ってくれないため、手動でコピー処理を書く必要があります。しかも、その方法を間違えると「意図しない動作」や「メモリ破壊」といった深刻なバグを招く可能性があります。

そのため、正しいコピー方法を理解し、安全かつ効率的に配列を扱うことは、C言語を学ぶ上で非常に重要なスキルのひとつです。

配列コピーに悩む人は多い

実際に「C言語 配列 コピー」というキーワードで検索する人は多く、それだけニーズが高いことがうかがえます。

  • memcpy を使えば一瞬でコピーできる?
  • strcpy との違いは?
  • for文で1つずつコピーする方が安全?
  • ポインタを使ったコピーはどう書くの?

こうした疑問に答えるために、本記事ではC言語における配列コピーの基本から応用までをわかりやすく解説していきます。

本記事で学べること

このページを読むことで、以下の知識を得ることができます。

  • C言語における配列の基本的な概念
  • 配列をコピーする複数の方法と、それぞれの利点・注意点
  • 文字列(char型配列)をコピーする際のコツと落とし穴
  • よくある疑問へのQ&A形式の解説

C言語初心者の方でも理解しやすいように、サンプルコード付きで丁寧に解説していきます。次のセクションから、まずは配列の基本について見ていきましょう。

2. 配列の基本概念

配列とは何か?

C言語における配列(Array)とは、同じデータ型の要素を連続して格納するための変数です。たとえば、5人分の点数を格納するために、int型の変数を5つ定義する代わりに、1つの配列でまとめて扱うことができます。

int scores[5];

このように定義された配列には、0番目から順にインデックスを指定してアクセスできます。

scores[0] = 80;
scores[1] = 75;
scores[2] = 90;
scores[3] = 60;
scores[4] = 85;

ここでは scores[0]scores[4] の5つの要素に整数を代入しています。インデックスは0から始まる点に注意しましょう。

配列の初期化方法

配列は宣言時に初期化することもできます。初期化とは、配列を作成すると同時に値を代入することです。

int scores[5] = {80, 75, 90, 60, 85};

このように記述することで、配列の各要素に順番に値が代入されます。なお、配列のサイズを省略することも可能です。

int scores[] = {80, 75, 90, 60, 85};  // 要素数は自動で5と判断される

逆に、要素数を明示しても値が足りない場合、残りの要素は自動的に 0 で初期化されます。

int scores[5] = {80, 75};  // scores[2]~scores[4] は0になる

配列のメモリ構造を理解する

C言語では、配列の要素はメモリ上に連続して配置されます。この特性により、forループやポインタを使って効率的に操作できます。

たとえば以下のようなコードでは、配列のすべての要素を順番に表示しています。

for (int i = 0; i < 5; i++) {
    printf("%d
", scores[i]);
}

このように配列の基本構造を理解しておくことは、後述する「コピー処理」にも大きく関係してきます。

3. 配列のコピー方法

C言語では、配列を代入演算子(=)で一括コピーすることはできません。以下のようなコードはコンパイルエラーになります。

int a[5] = {1, 2, 3, 4, 5};
int b[5];
b = a;  // エラー:配列同士の代入は不可

したがって、配列をコピーするには、明示的に1つずつ要素をコピーする方法や、標準ライブラリ関数を使う方法が必要です。ここでは代表的な3つの方法を紹介します。

forループを使った要素ごとのコピー

最も基本的で安全な方法は、forループを使って1つずつ要素をコピーする方法です。

#include <stdio.h>

int main() {
    int src[5] = {10, 20, 30, 40, 50};
    int dest[5];

    for (int i = 0; i < 5; i++) {
        dest[i] = src[i];
    }

    // コピー結果の表示
    for (int i = 0; i < 5; i++) {
        printf("%d ", dest[i]);
    }

    return 0;
}

この方法の利点は、「配列サイズを把握しやすく、制御しやすい」という点です。安全性が高く、初心者にもおすすめできます。

memcpy関数を使った高速コピー

より効率的に配列をコピーしたい場合は、標準ライブラリ <string.h> に含まれる memcpy 関数を使う方法があります。

#include <stdio.h>
#include <string.h>

int main() {
    int src[5] = {1, 2, 3, 4, 5};
    int dest[5];

    memcpy(dest, src, sizeof(src));  // srcのバイト数分をコピー

    for (int i = 0; i < 5; i++) {
        printf("%d ", dest[i]);
    }

    return 0;
}

memcpyの使い方のポイント:

  • 第1引数:コピー先ポインタ
  • 第2引数:コピー元ポインタ
  • 第3引数:コピーするバイト数(注意!要素数ではない)

注意点:

  • コピー元・先の配列サイズが異なるとバッファオーバーフローを起こす可能性があるため、サイズを必ず確認すること。
  • メモリの重なりがある場合には使用不可(次項で解説)。

memmoveとの違いと使い分け

memcpyと似た関数にmemmoveがあります。両者の違いは、「コピー元とコピー先が重なっているときの挙動」です。

  • memcpyメモリが重なっていない場合に使用。重複すると未定義動作。
  • memmove重なっていても安全にコピーできる

使用例:

char str[] = "ABCDE";

// 2文字目以降を1文字目から上書きコピー(重なりあり)
memmove(str + 1, str, 4);
str[5] = ' ';  // null終端

printf("%s
", str);  // 出力:AABCD

使い分けの基本ルール:

使用状況推奨関数
メモリが重ならないmemcpy
重なる可能性があるmemmove

配列操作においては通常 memcpy を使えば十分ですが、文字列操作などで配列の一部を移動するようなケースでは memmove を使うべきです。

4. 文字列(char配列)のコピー

C言語では、文字列はchar型の配列として扱われ、数値の配列とは少し異なる点に注意が必要です。文字列コピーには専用の関数が用意されており、memcpyのようなバイナリコピーとは異なり、「終端文字 を含めてコピーする」という特徴があります。

strcpy関数で文字列をコピーする

C言語の標準ライブラリ <string.h> に含まれる strcpy 関数は、ヌル終端()まで文字列をコピーしてくれる便利な関数です。

#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Hello, world!";
    char dest[50];

    strcpy(dest, src);

    printf("コピー結果:%s
", dest);

    return 0;
}

このコードでは、srcの文字列(終端の含む)をdestにコピーしています。

注意点:

  • destのサイズが小さいとバッファオーバーフローの原因になるため、十分なサイズを確保する必要があります。
  • コピーされる文字数はsrcの長さに依存します。

strncpyで安全にコピーする

strcpyの代わりに使える関数がstrncpyです。こちらは「指定した文字数分だけコピーする」という仕様になっており、安全性が高くなります。

#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "C language";
    char dest[10];

    strncpy(dest, src, sizeof(dest) - 1);  // 最後の1バイトは 用に残す
    dest[9] = ' ';  // 念のため終端文字を明示

    printf("コピー結果:%s
", dest);

    return 0;
}

strncpyの特性:

  • 指定した長さ分だけコピーする。
  • コピー元が短い場合、残りの部分はNULLで埋められる(環境による)。
  • 終端文字が自動で付加されるとは限らないので、自分で明示するのが安全。

日本語(マルチバイト文字列)を扱うときの注意

日本語などのマルチバイト文字(UTF-8、Shift-JISなど)を扱う場合、strcpystrncpyで途中のバイトを切り取ってしまうと、文字化け表示エラーの原因になります。

たとえば「こんにちは」を3バイトだけコピーすると、中途半端な状態になることがあります。こうした場合は、文字単位で扱えるライブラリや、ワイド文字(wchar_t)の使用を検討する必要があります。


文字列コピーのベストプラクティスまとめ

関数名特徴注意点
strcpy終端文字までコピーバッファサイズの確認必須
strncpy指定長さ分だけコピーできる終端文字が保証されないことも
memcpy任意のバイト列をコピーできるバイナリコピー。文字列向きでない
strdupコピーした文字列の新しいメモリを確保(非標準)使用後はfreeが必要

5. 配列コピー時の注意点

配列のコピーは一見シンプルな処理に思えますが、正しく扱わないと重大なバグやセキュリティホールを生む恐れがあります。このセクションでは、C言語における配列コピー時に特に注意すべきポイントを解説します。

バッファオーバーフローに注意

最も頻繁に見られるミスが、コピー先の配列サイズを超えてデータを書き込んでしまう「バッファオーバーフロー」です。

例:危険なコピー処理

char src[] = "This is a long string.";
char dest[10];

strcpy(dest, src);  // destのサイズを超えてコピー → メモリ破壊の可能性

このようなコードは、不正なメモリ領域へのアクセスを引き起こし、プログラムがクラッシュしたり、脆弱性の原因になります。

対策:

  • strncpymemcpyを使い、必ずサイズを制限する。
  • 終端文字 を手動で追加することを忘れない。
  • 配列のサイズを定数やマクロで管理する。

コピー対象のサイズを正確に把握する

memcpymemmoveでコピーする際は、要素数ではなく「バイト数」で指定する必要があります。

安全なサイズ指定の例:

int src[5] = {1, 2, 3, 4, 5};
int dest[5];

memcpy(dest, src, sizeof(src));  // src全体のバイト数を指定

このように sizeof(src) を使えば、自動的に配列全体のサイズ(バイト数)が得られるため安全です。

ただし、関数の引数として配列を受け取るときは sizeof が期待通りに動作しません(配列はポインタに退化するため)ので注意が必要です。

ポインタ操作時の注意点

C言語では、配列はポインタとして扱われることが多く、間違ったポインタ操作によってメモリを破壊する可能性があります。

よくあるミス例:

int *src = NULL;
int dest[5];

memcpy(dest, src, sizeof(dest));  // NULLポインタをコピー元に指定 → クラッシュ

ポイント:

  • ポインタが有効なアドレスを指しているか、NULLチェックを行う。
  • メモリ確保(mallocなど)後にコピーする場合、確保サイズとコピーサイズの整合性を確認する。

コピー元とコピー先の領域が重なるときの対策

すでに解説した通り、memcpy重複したメモリ領域のコピーに対応していません。配列の一部を別の位置にコピーするような場合は、memmoveを使用するのが鉄則です。

char buffer[100] = "example";

// 一部の文字列を自分自身の中で移動
memmove(buffer + 2, buffer, 4);  // 安全に重複コピー

配列サイズの定義と管理

安全なコピー処理を行うためには、配列サイズの一元管理が有効です。以下のようにマクロで定義しておくことで、コードの保守性と安全性が向上します。

#define ARRAY_SIZE 10

int arr1[ARRAY_SIZE];
int arr2[ARRAY_SIZE];

memcpy(arr2, arr1, sizeof(arr1));

安全な配列コピーのためのまとめ

  • コピー時にはサイズ(バイト数)を正確に把握する。
  • strncpymemmoveなどの安全な関数を選ぶ。
  • コピー元・先のサイズ整合性を常に確認する。
  • ポインタ操作には特に注意し、NULLチェックや境界チェックを行う。

6. FAQ(よくある質問)

C言語における配列のコピーについて、初学者から中級者までがよく疑問に思うポイントをQ&A形式で解説します。ちょっとした違いや仕様を正しく理解することで、バグの予防やコードの品質向上にもつながります。

Q1. memcpymemmoveの違いは何ですか?

A. メモリ領域が重なっている場合に、動作の安全性が異なります。

比較項目memcpymemmove
重なりに対する安全性×(未定義動作になる可能性)◎(内部で一時バッファを使って処理)
処理速度高速(オーバーヘッド少)やや遅い(安全対策あり)
用途配列の完全なコピーなど同一配列内でのデータ移動など

補足:重なりがないとわかっている場合はmemcpyでOKですが、迷ったらmemmoveを選ぶのが安全です。

Q2. strcpystrncpyの違いと使い分けは?

A. 安全性と柔軟性のトレードオフがあるため、使い分けが必要です。

  • strcpy(dest, src)
    srcの終端()までを全てコピー。ただし、destが小さいとバッファオーバーフローの危険あり
  • strncpy(dest, src, n)
    → 指定した最大バイト数nまでしかコピーしない。安全性は高いが、が自動で付与されるとは限らない

おすすめの使い分け:

  • 確実にサイズが足りていることが分かっているなら strcpy
  • 安全第一・不特定文字列を扱うときは strncpy+手動で を付加

Q3. 配列を関数の引数として渡すときの注意点はありますか?

A. 配列はポインタに「退化」するため、sizeofでサイズが取れなくなります。

void copy_array(int arr[], int size) {
    printf("%zu
", sizeof(arr));  // ← ポインタのサイズ(例:8)になる
}

このように、配列の実サイズが取得できなくなるため、関数に渡す際はサイズも引数として一緒に渡すのが基本です。

void copy_array(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        // 処理内容
    }
}

Q4. memcpyで構造体の配列をコピーしても問題ない?

A. 基本的には可能ですが、「ポインタを含む構造体」には注意が必要です。

memcpyバイナリ単位のコピーを行うため、構造体にポインタが含まれていると、ポインタの参照先(実データ)まではコピーされません

例(浅いコピーになる):

typedef struct {
    char *name;
    int age;
} Person;

Person a[3];
Person b[3];

memcpy(b, a, sizeof(a));  // ポインタ自体のコピーになる(参照先は共有)

このような場合、データの二重解放や不整合が発生する可能性があります。対策としては、ポインタ部分を個別にmallocstrcpyなどで深くコピーする必要があります。

Q5. 配列をまとめてコピーしたいけど、毎回for文を書くのは面倒です。共通関数にできますか?

A. はい、関数化することで再利用性が高まり、コードもスッキリします。

void copy_int_array(int *dest, int *src, int size) {
    for (int i = 0; i < size; i++) {
        dest[i] = src[i];
    }
}

このように、型や用途ごとに汎用的なコピー関数を作成しておくことで、開発効率と可読性が向上します。

7. まとめ

本記事では、C言語における「配列のコピー」について、基本から応用まで体系的に解説してきました。最後に、重要なポイントを振り返りつつ、実務での活用に向けたまとめを行います。

配列コピーの基本は「安全性と目的の明確化」

C言語では、配列同士をそのまま=で代入することはできません。そのため、コピーを行うには明示的な処理が必要です。

  • forループ:最も基本的でわかりやすい。配列サイズを明示する必要あり。
  • memcpy:バイナリ単位で高速コピー可能。サイズミスに注意。
  • memmove:コピー元とコピー先が重なるときに使用。
  • strcpy / strncpy:文字列(char配列)用の関数。安全性を考慮して使い分け。

安全なコピーには「サイズ管理」が欠かせない

  • 配列サイズのオーバーラン(超過コピー)は、クラッシュや脆弱性の原因になります。
  • sizeof() を活用して正確なバイト数を把握することが重要です。
  • 関数で配列を扱う場合は、ポインタに退化することを理解し、サイズも引数として渡すようにしましょう。

よくある落とし穴も理解しておこう

  • strncpyは安全だが、終端文字が付かないことがあるので、手動で追加する癖をつけましょう。
  • 構造体のポインタを含む配列は、memcpyでは正しくコピーできないことがあります。
  • マルチバイト文字列(日本語など)を扱う場合、文字数とバイト数の違いにも注意が必要です。

実務で活かすために

  • プロジェクト内で頻繁に配列コピーが発生する場合は、専用のユーティリティ関数を作っておくと便利です。
  • コピー処理はテストしやすい単位なので、ユニットテストなどで安全性を確認することもおすすめです。

最後に

C言語は低レベルな言語であるがゆえに、配列のコピーひとつとっても奥が深いです。しかし、今回紹介した基本的な知識とテクニックを押さえておけば、さまざまな場面で応用が利くようになります。

ぜひ、この記事を参考に「正しく、安全なコピー処理」をマスターし、より信頼性の高いC言語プログラムを作成してください。