閉じる

C言語 memcpy関数の使い方:メモリコピーの基本と注意点を正しく理解する

C言語のプログラミングにおいて、メモリを直接操作する能力は非常に強力ですが、同時に慎重な扱いが求められます。

その中でも「memcpy」関数は、メモリブロックをある場所から別の場所へコピーするための基本的な手段として、古くから多くの開発者に利用されてきました。

効率的なデータ処理や構造体のコピーなど、幅広い用途で活用されるこの関数の挙動を正しく理解することは、堅牢なプログラムを書くための第一歩となります。

本記事では、2026年現在の標準的な開発環境に基づき、memcpyの基本的な使い方から、実装時に陥りやすい注意点までを詳しく紐解いていきます。

memcpy関数とは何か

memcpyは、C言語の標準ライブラリである「string.h」ヘッダーに定義されている関数です。

この関数の主な役割は、メモリ上の指定された範囲を、バイト単位で別の場所にそのままコピーすることにあります。

文字列のコピーに使われるstrcpy関数とは異なり、memcpyは途中に「\0」(ヌル文字)が含まれていても、指定したサイズ分を忠実にコピーし続けます。

そのため、画像データや構造体、数値配列などのバイナリデータを扱う際に不可欠な存在となっています。

現代のプログラミングにおいても、低レイヤーの処理やパフォーマンスが要求されるシーンでは、この関数が頻繁に呼び出されています。

関数のプロトタイプと引数

memcpy関数のプロトタイプ宣言は以下のようになっています。

C言語
void *memcpy(void *restrict dest, const void *restrict src, size_t n);

この宣言に含まれる3つの引数には、それぞれ重要な役割があります。

第一引数のdestは、コピー先のメモリブロックを指すポインタです。

第二引数のsrcは、コピー元のメモリブロックを指すポインタです。

第三引数のnは、コピーするデータのサイズを「バイト数」で指定します。

戻り値として、コピー先ポインタであるdestがそのまま返されます。

ここで注目すべきは、引数がvoid*型であるため、どのようなデータ型のポインタでも渡すことができるという柔軟性です。

memcpyの基本的な使い方

memcpyを使用する際は、まずコピー元とコピー先のメモリ領域が適切に確保されていることを確認する必要があります。

最もシンプルな例として、整数の配列を別の配列にコピーする処理を見てみましょう。

配列の全要素をコピーする場合、要素数に型のサイズ(sizeof)を掛け合わせて総バイト数を算出します。

C言語
#include <stdio.h>
#include <string.h>

int main(void) {
    int source[] = {10, 20, 30, 40, 50};
    int destination[5];

    // 配列sourceの内容をdestinationにコピー
    memcpy(destination, source, sizeof(source));

    // 結果の確認
    for (int i = 0; i < 5; i++) {
        printf("destination[%d] = %d\n", i, destination[i]);
    }

    return 0;
}
実行結果
destination[0] = 10
destination[1] = 20
destination[2] = 30
destination[3] = 40
destination[4] = 50

この例では、sizeof(source)を用いることで、配列全体のサイズを正確に取得し、memcpyに渡しています。

このように、「コピーしたいサイズをバイト単位で指定する」という点が、memcpyを使いこなす上での基本となります。

構造体のコピーへの応用

memcpyは、自作した構造体のデータを別の構造体に一括でコピーする場合にも非常に便利です。

個別のメンバを一つずつ代入する手間が省けるため、コードの可読性が向上します。

C言語
#include <stdio.h>
#include <string.h>

struct User {
    int id;
    char name[20];
    double score;
};

int main(void) {
    struct User user1 = {1001, "Alice", 95.5};
    struct User user2;

    // 構造体ごとメモリコピー
    memcpy(&user2, &user1, sizeof(struct User));

    printf("ID: %d, Name: %s, Score: %.1f\n", user2.id, user2.name, user2.score);

    return 0;
}
実行結果
ID: 1001, Name: Alice, Score: 95.5

構造体の中にポインタメンバが含まれている場合は、ポインタのアドレス自体がコピーされる「浅いコピー」になる点に注意してください。

実体としてのデータをコピーしたい場合は、メンバごとに適切なメモリ操作を行う必要があります。

memcpyを使用する際の重要な注意点

memcpyは非常に強力な関数ですが、誤った使い方をすると致命的なバグを引き起こします。

安全なプログラムを作成するために、以下の3つのポイントを必ず守るようにしてください。

1. メモリ領域の重複に関する禁止事項

memcpyを使用する際、コピー元(src)とコピー先(dest)の領域が重なっていてはいけません。

プロトタイプ宣言にあるrestrictキーワードは、これらの領域が重ならないことをプログラマが保証することを意味しています。

もし領域が重なっている状態でmemcpyを使用すると、コピー中にデータが上書きされ、意図しない結果を招く「未定義の動作」が発生します。

領域が重なる可能性がある場合には、memcpyの代わりにmemmove関数を使用するのが鉄則です。

2. バッファオーバーランの防止

コピー先として指定したメモリ領域が、指定したバイト数(n)よりも小さい場合、バッファオーバーランが発生します。

これは隣接するメモリ領域を破壊し、プログラムの異常終了や、深刻なセキュリティ脆弱性の原因となります。

常にndestの確保されたサイズを超えないよう、実行前にチェックする習慣をつけましょう。

静的な配列であればsizeof演算子が有効ですが、動的に確保したメモリ(malloc等)の場合は、確保したサイズを別途管理しておく必要があります。

3. ヌルポインタのチェック

memcpyの引数にNULLポインタを渡すことは許されていません。

たとえコピーするバイト数(n)が0であったとしても、destsrcNULLを渡すと未定義の動作となります。

外部からの入力データや動的メモリ確保の結果を利用する場合は、必ず事前にNULLチェックを行ってください。

memcpyとmemmoveの違いと使い分け

メモリコピーを扱う関数には、memcpyの他にmemmoveが存在します。

初心者の方は「なぜ2つもあるのか」と疑問に思うかもしれませんが、これらには明確な違いがあります。

項目memcpymemmove
領域の重複許可されない(未定義動作)許可される(安全)
処理速度高速な傾向があるわずかにオーバーヘッドがある
主な用途異なる変数間のデータ移動同一配列内での要素ずらし

memmoveは、コピーに際して一時的なバッファを利用するか、コピーの方向を調整することで、領域が重なっていても正しくデータを転送します。

一方、memcpyは領域が重ならないことを前提に最適化されているため、より高速に動作する可能性があります。

「絶対に重ならない」と確信できる場合はmemcpyを使い、少しでも重なる懸念があるならmemmoveを選ぶのが、プロフェッショナルな使い分けです。

実践的なテクニック:部分的なコピー

memcpyは、データの先頭からだけでなく、オフセットを指定して部分的にコピーすることも可能です。

これはポインタ演算を利用することで実現できます。

例えば、大きなバッファの中間に特定のデータを埋め込むような処理が考えられます。

C言語
#include <stdio.h>
#include <string.h>

int main(void) {
    char buffer[20] = "Hello, World!";
    char insert[] = "C2026";

    // bufferの7バイト目から5バイト分を、insertの内容で上書き
    memcpy(buffer + 7, insert, 5);

    printf("Result: %s\n", buffer);

    return 0;
}
実行結果
Result: Hello, C2026!

このように、buffer + 7のようにポインタを加算することで、コピーの開始位置を自由に制御できます。

ただし、この際もコピー先がバッファの終端を超えないように細心の注意を払ってください。

2026年における安全なメモリ操作

近年のC言語開発では、安全性を重視する傾向がより強まっています。

C11規格以降では、より安全な「memcpy_s」という関数も導入されていますが、コンパイラやライブラリのサポート状況により使用できない場合もあります。

標準的なmemcpyを使い続ける場合であっても、静的解析ツールや動的解析ツールを活用し、境界外アクセスのミスを自動で検出するフローを導入することが推奨されます。

また、2026年のモダンな開発環境では、型安全性を高めるために、voidポインタを多用するmemcpyのラップ関数を自作し、特定の型専用のコピー関数を用意する手法も一般的です。

関数を単独で理解するだけでなく、開発プロセス全体の中でどのように安全性を担保するかを考えることが重要です。

まとめ

memcpy関数は、C言語におけるメモリ操作の核となる機能であり、そのシンプルさゆえに多くの用途で使用されています。

正しく活用すれば、高速かつ簡潔なコードを記述することができますが、「領域の重複」や「バッファサイズ」に対する配慮を怠ると致命的な問題を引き起こします。

基本的な文法をマスターした後は、常に「このコピーは安全か?」と自問自答する習慣を身につけましょう。

memcpy、memmove、strcpyなどの類似関数との違いを明確に把握し、状況に応じて最適な道具を選択することが、優れたプログラマへの道です。

本記事で紹介した基本と注意点を守り、安全で効率的なC言語プログラミングを実践してください。

URLをコピーしました!