
C言語入門|メモリ操作の基本関数
ここまで本当によく頑張りました。
配列、ポインタ、添え字、アドレス……
正直、C言語の中でも一番ややこしいところを通り抜けてきましたね。
そして今、ようやく
「ポインタを引数に取る標準関数」
を、安全に使うための準備が整いました。
C言語の標準ライブラリには、
メモリを直接扱うための便利な関数がいくつも用意されています。
今回はその中でも、実務で最重要クラスの関数を入口として紹介します。

なぜメモリ操作用の関数が必要なのか
C言語では、配列や文字列について次のような大原則がありました。
- 配列は = で代入してはいけない。
- 文字列も丸ごと代入してはいけない。
- 関数の引数や戻り値で直接やり取りしない。
これは不便に見えますが、理由はとてもシンプルです。
= は「値のコピー」ではない
int a[4];
int b[4];
b = a; // これはできないこの書き方が禁止されているのは、
- 配列名はアドレスに化ける。
- b = a は「アドレスの代入」を意味してしまう。
からです。
でも現実のプログラムでは、
- 配列を丸ごとコピーしたい。
- 構造体の中身をまとめて複製したい。
という場面が頻繁にあります。
そこで登場するのが、メモリ操作の標準関数です。
メモリ操作関数を使う準備
これから紹介する関数を使うときは、
必ずソースコードの先頭に次を記述します。
#include <string.h>string.h という名前ですが、
文字列だけでなく メモリ操作全般 を扱う関数が含まれています。
memcpy ― メモリ領域をまるごとコピーする
最初に紹介するのは、もっとも基本で、もっとも重要な関数です。
memcpy の役割
あるメモリ領域の中身を
別のメモリ領域に、指定したバイト数だけコピーする。
配列・構造体・バッファなど、
形や型を問わず使える汎用コピー関数です。
memcpy の書式
void* memcpy(void* dest, const void* src, size_t len);引数の意味
| 引数 | 内容 |
|---|---|
| dest | コピー先の先頭アドレス |
| src | コピー元の先頭アドレス |
| len | コピーするバイト数 |
戻り値
- dest と同じアドレス
戻り値はあまり使われませんが、
関数の連結などで利用できます。
size_t とは何か
len に指定する size_t 型は、
- 0以上の整数
- メモリサイズや配列サイズを表すための型
です。
感覚的には、
サイズ専用の int
と思っておけば問題ありません。
const void* の意味(軽く)
src が const void* になっているのは、
- コピー元のメモリ内容を
- 関数内で変更しない
という約束を示しています。
安全のための仕組みなので、
「そういうもの」と受け取ってOKです。
配列を memcpy でコピーしてみよう
次の例では、int型配列を丸ごとコピーします。
サンプルプログラム
プロジェクト名:10-7-1 ソースファイル名: sample10-7-1.c
#include <stdio.h>
#include <string.h>
int main(void)
{
int src[3] = {8, 16, 24};
int dst[3];
memcpy(dst, src, sizeof(src));
printf("src[1]=%d (%p)\n", src[1], &src[1]);
printf("dst[1]=%d (%p)\n", dst[1], &dst[1]);
return 0;
}実行結果(例)
src[1]=16 (0x7ffee2c1a4c4)
dst[1]=16 (0x7ffee2c1a4b4)何が起きているのか
- src と dst は 別々の配列
- アドレスも完全に異なる。
- 中身だけがコピーされている。
これが、
正しい配列コピー
です。
配列名を引数に渡していますが、
実際には「先頭アドレス」が渡されており、
memcpy はその番地から指定バイト数分を
そのまま複製しています。
浅いコピーと深いコピーの落とし穴
ここで、重要な注意点があります。
浅いコピーとは
- ポインタを含む配列や構造体を
- memcpy でそのままコピーすること
この場合、
- 外側の構造は複製される。
- 中のポインタが指す先は共有される。
という状態になります。
浅いコピーが引き起こす問題
- 片方で free すると、もう片方が壊れる。
- 意図せず同じデータを操作してしまう。
- バグの原因が非常に分かりにくい。
このようなコピーは 浅いコピー(shallow copy) と呼ばれます。
深いコピーという考え方
深いコピーでは、
- 構造体や配列を複製
- 内部のポインタが指す先も
- 新しい領域を確保してコピー
します。
手間は増えますが、
安全で、意図どおりの動作
を保証できます。
メモリ操作関数を使う心構え
メモリ操作系の関数は、とても強力です。
その一方で、
- サイズ指定を間違える。
- コピー先が足りない。
- 重なった領域を扱う。
と、簡単にバグや未定義動作を引き起こします。
覚えておきたい鉄則
- バイト数は必ず意識する。
- sizeof を積極的に使う。
- 「とりあえず memcpy」はしない。
C言語では、
便利さと危険さは常にセット
