C言語の構造体をコピーする
- 作成日: 2022-01-04
- 更新日: 2024-03-19
- カテゴリ: C言語
C言語の構造体をコピーする
C言語では構造体を扱うことができます。
構造体はメンバ変数で構成されたデータのまとまりです。
今回はこの構造体をコピーする方法について具体的に解説します。
以下を見ていきます。
- 構造体変数への代入によるコピー
- memcpy()を使ったコピー
- memmove()を使ったコピー
- memcpyとmemmove, どちらが速い?
- deepcopy()関数を定義してコピーする
まずは構造体変数への代入によるコピーからです。
C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。
関連動画
C言語の構造体をコピーする方法 - YouTube
構造体変数への代入によるコピー
構造体で定義する変数を構造体変数と言います。
この構造体変数は変数ですので、別の変数に代入することができます。
たとえば↓のようにです。
#include <stdio.h>
// 動物を表す構造体
struct Animal {
int age; // 年齢
char name[40]; // 名前
};
int main(void) {
struct Animal cat = { 20, "Tama" }; // 構造体変数catを定義する
struct Animal dog = cat; // 代入してcatをdogにコピー
// dogのメンバを出力
printf("dog age[%d] name[%s]\n", dog.age, dog.name);
// 出力結果↓
// dog age[20] name[Tama]
return 0;
}
代入先の変数が代入元の変数と同じ構造体であれば、このように代入式で構造体変数をコピーできます。
このコピーは構造体のメンバがすべてコピーされます。
ただし動的なメモリ確保をされた変数は別です。
動的なメモリ確保で得られたアドレスを保存してるポインタ変数は、そのアドレス値が単純にコピーされるだけです。
これはどういうことかと言うと、メモリが持つ実体としてのデータはコピーされないということです。
あくまでもコピーされるのはポインタ変数だけになります。
↓のコードをご覧ください。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 動物を表す構造体
struct Animal {
int age; // 年齢
char *name; // 名前(動的にメモリ確保される)
};
int main(void) {
// cat変数を初期化する
struct Animal cat;
cat.age = 20;
cat.name = malloc(sizeof(char) * 40); // 動的メモリ確保
strcpy(cat.name, "Tama");
struct Animal dog = cat; // 代入してcatをdogにコピー
// dogのメンバを出力
printf("dog age[%d] name[%s]\n", dog.age, dog.name);
// 出力結果↓
// dog age[20] name[Tama]
// catとdogのnameのアドレス値を比較
printf("cat name address[%p]\n", cat.name);
printf("dog name address[%p]\n", dog.name);
// 出力結果↓
// cat name address[0xb37010]
// dog name address[0xb37010]
free(cat.name);
return 0;
}
dog
変数にcat
を代入文でコピーしているのは変わりません。
cat
のname
はmalloc()
関数で動的にメモリが確保されています。
dog
のname
を参照すると「Tama」と出力されますので、文字列がコピーされているように見えます。
しかしこれはポインタ変数がコピーされているだけです。
その証拠にcat
のname
とdog
のname
のアドレス値は同じものになります。
ですのでメモリの解放もcat.name
の方だけにしています。
仮にdog.name
もfree()
で解放すると、これは同じメモリを2回解放することになります。
いわゆるダブルフリーというバグになります。
つまり動的なメモリ確保を行っているメンバが構造体に存在してる場合、普通の代入文のコピーではうまくいかないということになります。
これを解決するには後述するdeepcopy()
などのコピー用の関数を定義する必要があります。
memcpy()を使ったコピー
string.h
をインクルードすると使えるmemcpy()
関数でも構造体をコピーすることができます。
#include <string.h>
// dest ... コピー先のポインタ
// src ... コピー元のポインタ
// n ... コピーするバイト数
// 返り値 ... destへのポインタ
void *memcpy(void *dest, const void *src, size_t n);
memcpy()
関数の特徴は、memmove()
と違い、コピー元とコピー先のポインタが同じだった場合に正常に動作しない点です。
memcpy()
は内部でコピー元のコピーを作らないので、ポインタが指すメモリが同じだった場合に期待しない動作をすることがあります。
#include <stdio.h>
#include <string.h>
// 動物を表す構造体
struct Animal {
int age; // 年齢
char name[40]; // 名前
};
int main(void) {
struct Animal cat = { 20, "Tama" }; // 構造体変数catを定義
struct Animal dog; // コピー先のdog変数
// catをdogにコピーする
memcpy(&dog, &cat, sizeof(cat));
// dogのメンバを出力
printf("dog age[%d] name[%s]\n", dog.age, dog.name);
// 出力結果↓
// dog age[20] name[Tama]
return 0;
}
↓のコードの部分でコピーを実行しています。
// catをdogにコピーする
memcpy(&dog, &cat, sizeof(cat));
memcpy()
の第1引数にdog
変数のアドレスを渡しています。
第2引数にはcat
変数のアドレスを渡します。
第3引数にはcat
変数のバイト数をsizeof
演算子で求めて渡しています。
こうするとdog
変数にcat
変数がcat
変数のバイト数だけコピーされます。
cat
とdog
は同じAnimal
構造体の変数なので、そのバイト数は同じです。
それからこれはmemcpy()
とmemmove()
の両方に言える点ですが、これらの関数はコピー先のサイズを気にしません。
仮にコピー先のサイズがコピーするバイト数よりも小さかった場合は、意図しないメモリ上のデータを上書きする場合があります。
この辺は注意が必要です。
あと動的確保されたポインタ変数はポインタ変数だけがコピーされるので注意が必要です。
ポインタの先のメモリのデータはコピーされません。
memmove()を使ったコピー
string.h
をインクルードすると使えるmemmove()
関数も構造体をコピーすることができます。
#include <string.h>
// dest ... コピー先のポインタ
// src ... コピー元のポインタ
// n ... コピーするバイト数
// 返り値 ... destへのポインタ
void *memmove(void *dest, const void *src, size_t n);
memmove()
もmemcpy()
と使い方は同じです。
#include <stdio.h>
#include <string.h>
// 動物を表す構造体
struct Animal {
int age; // 年齢
char name[40]; // 名前
};
int main(void) {
struct Animal cat = { 20, "Tama" }; // 構造体変数catを定義
struct Animal dog; // コピー先のdog変数
// catをdogにコピーする
memmove(&dog, &cat, sizeof(cat));
// dogのメンバを出力
printf("dog age[%d] name[%s]\n", dog.age, dog.name);
// 出力結果↓
// dog age[20] name[Tama]
return 0;
}
memmove()
はmemcpy()
と違って、コピー元の領域を一旦コピーしておきます。
そのためdestとsrcの領域が重なっていても期待した動作になります。
そのためセキュアなプログラムを作りたい場合は理屈の上ではmemcpy()
を使うよりもmemmove()
を使ったほうが良いということになります。
memcpyとmemmove, どちらが速い?
memcpy()
はmemmove()
と比べてコピー領域が重なっていた場合に意図しない動作をする分、危険な関数と言えますが、その分余計なことをしないためはやく動作しそうです。
果たしてこの仮説は本当なんでしょうか?
↓のコードでmemcpy()
とmemmove()
の処理速度を比較できます。
#include <stdio.h>
#include <string.h>
#include <time.h>
// 動物を表す構造体
struct Animal {
int age; // 年齢
char name[3000]; // 名前
};
int main(void) {
struct Animal cat = { 20, "Tama" }; // 構造体変数catを定義
struct Animal dog; // コピー先のdog変数
const long n = 1000000000; // 試行回数
// memcpy()の処理速度の計測
{
clock_t start,end;
start = clock();
for (long i = 0; i < n; i += 1) {
memcpy(&dog, &cat, sizeof(cat));
}
end = clock();
printf("memcpy: %.2f秒かかりました\n", (double) (end - start) / CLOCKS_PER_SEC);
}
// memmove()の処理速度の計測
{
clock_t start,end;
start = clock();
for (long i = 0; i < n; i += 1) {
memmove(&dog, &cat, sizeof(cat));
}
end = clock();
printf("memmove: %.2f秒かかりました\n", (double) (end - start) / CLOCKS_PER_SEC);
}
return 0;
}
上記のコードを筆者の環境で実行する(最適化なし)と以下のような結果になります。
memcpy: 29.58秒かかりました
memmove: 29.54秒かかりました
なんとビックリ仰天玉手箱、memmove()
の方が速いという結果になりました。
🦝 < memcpy()良いとこないやん
🐭 < memcpy()はいらない子・・・?
しかしこのテスト結果は不思議な結果です。
理屈ではmemcpy()
のほうが速いはずですが……。
deepcopy()関数を定義してコピーする
構造体のメンバに動的にメモリが確保されたアドレスを持つポインタ変数があった場合、代入文やmemcpy()
, memmove()
では期待した動作、つまり全体的なデータのコピーが実現できません。
これを解決するにはコピー用の関数を独自に定義する必要があります。
たとえば↓を見てください。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Animal {
int age;
char *name;
};
// ディープコピーを行う関数
void deepcopy(struct Animal *dst, struct Animal *src) {
// 年齢のコピー
dst->age = src->age;
// 名前のコピー
size_t byte = sizeof(char);
size_t size = byte * strlen(src->name) + byte;
dst->name = malloc(size); // エラー処理は省略
strcpy(dst->name, src->name);
}
int main(void) {
// catの初期化
struct Animal cat;
cat.age = 20;
cat.name = malloc(sizeof(char) * 40); // エラー処理は省略
strcpy(cat.name, "Tama");
// dogにcatをコピー
struct Animal dog;
deepcopy(&dog, &cat);
// dogのメンバを出力
printf("dog age[%d] name[%s]\n", dog.age, dog.name);
// メモリを解放
free(cat.name);
free(dog.name);
return 0;
}
↑のdeepcopy()
関数は構造体Animal
をコピーする関数です。
内部ではsrc
のname
をdst
に動的にコピーしています。
このようなコピー用の関数を定義しておくと、構造体のメンバの動的メモリ確保にも対応できます。
ただしこのようなディープコピーを行う関数はけっこう遅いので、頻繁に使う場合は注意してください。
プログラムのボトルネックになる可能性も持っています。
おわりに
今回はC言語の構造体のコピーについて解説しました。
一番簡単なのは代入文によるコピーですが、臨機応変にmemcpy()
やmemmove()
も使えます。
動的なメンバはdeepcopy()
などのコピー用関数を定義して対応しましょう。
🦝 < コピーは奥が深いね
🐭 < パソコンの基礎機能だしね