このページでは、C言語の標準関数である memcpy
と strcpy
との違いについて解説していきたいと思います。
strcpy
は文字列のコピーに向いてるんだよね?
おそらく strcpy
関数が「文字列のコピー」に向いていることをご存知な方も多いと思います。
ただ、「文字列のコピーに向いている理由」をしっかり説明できる人は少ないのではないでしょうか?
strcpy
関数が memcpy
関数よりも文字列のコピーに向いているのは、当然この2つの動作に違いがあるためです。
このページではまず memcpy
関数と strcpy
関数についておさらいを行い、続いて本題である2つの関数の違いと2つの関数の使い分けについて解説していきたいと思います!
Contents
memcpy
関数と strcpy
関数
まずは memcpy
と strcpy
がどのような関数であるかについて説明していきたいと思います。
memcpy
も strcpy
もデータをコピーする関数
memcpy
関数も strcpy
関数も「データをコピーする」関数です。
ここは2つの関数で同じです。
例えば配列に格納したデータを他の配列にコピーしたりする時に便利な関数です。
スポンサーリンク
memcpy
関数
memcpy
関数の定義は下記になります。
#include <string.h>
void *memcpy(void *dst, const void *src, size_t n);
memcpy
関数の引数
memcpy
関数の引数は下記の3つになります。
- 第1引数
dst
:コピー先の配列やメモリのアドレス - 第2引数
src
:コピー元のデータが格納された配列やメモリのアドレス - 第3引数
n
:コピーするバイト数
第1引数と第2引数の型が void*
なので抵抗感ある人もいるかもしれませんが、要はアドレスならなんでも良いということです。
int*
型や char*
型のポインタなどを指定することができます。
void*
型については下記で説明していますので、詳しく知りたい方は是非読んでみてください。
また、第3引数はコピーする「バイト数」です。ついつい配列のサイズなどを指定しまいがちですので注意しましょう。
memcpy
関数の戻り値
戻り値はコピー先の配列やメモリの先頭アドレスになります。
つまり、第1引数 dst
と同じアドレスになります。
memcpy
関数の使用例
memcpy
関数を使用してデータのコピーを行うプログラムの例は下記になります。
#include <string.h>
#include <stdio.h>
int main(void) {
int a = 10;
int b;
int src[10] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
};
int dst[10];
int i;
memcpy(&b, &a, sizeof(int));
printf("%d\n", b);
memcpy(dst, src, sizeof(int) * 10);
for (i = 0; i < 10; i++) {
printf("%d\n", dst[i]);
}
return 0;
}
strcpy
関数
strcpy
関数の定義は下記になります。
#include <string.h>
char *strcpy(char *dst, const char *src);
strcpy
関数の引数
strcpy
関数の引数は下記の3つになります。
- 第1引数
dst
:コピー先の配列やメモリのアドレス - 第2引数
src
:コピー元のデータが格納された配列やメモリのアドレス
strcpy
関数では memcpy
関数とは異なり引数が2つのみです。
コピーするバイト数を指定する第3引数は不要です。
strcpy
関数の戻り値
戻り値は memcpy
関数と同様で、コピー先の配列やメモリの先頭アドレスになります。
つまり、第1引数 dst
と同じアドレスになります。
strcpy
関数の使用例
strcpy
関数を使用してデータのコピーを行うプログラムの例は下記になります。
#include <string.h>
#include <stdio.h>
int main(void) {
char src[] = "moziretsu";
char dst[10];
strcpy(dst, src);
printf("%s\n", dst);
return 0;
}
memcpy
関数と strcpy
関数の違い
両者とも「データをコピーする」関数ですが、動作として決定的な違いが2つあります。
この違いは下記の2つです。
- 「どこまで」コピーするかが違う
- ヌル文字の付加の有無が違う
それぞれの違いについて解説していきたいと思います。
スポンサーリンク
違い1:「どこまで」コピーするかが違う
まず前提として、データをコピーするためには「どこから」「どこまで」のデータをコピーするかの情報が必要になります。
ある範囲のデータをコピーするような場合は、この2つの情報が無いとコピーできないんです。
例えばあなたが文章をコピペする場合は、マウスなどで「どこから」「どこまで」の文章をコピーするかを指定すると思います。
これと同じです。
では memcpy
と strcpy
では「どこから」「どこまで」のデータをコピーするようになっているのでしょうか?
両関数とも「どこから」は引数で決まる
両関数とも「どこから」は引数によって決まります。
より具体的には第2引数で指定されるアドレスで決まります。
この指定されたアドレスからコピーが行われます。
では「どこまで」はどのようにして決まるでしょうか?
memcpy
では「どこまで」は引数で決まる
memcpy
の定義を思い出してみましょう!
#include <string.h>
void *memcpy(void *dst, const void *src, size_t n);
第3引数で「どこまで」の基になる「データをコピーするバイト数 n
」が指定可能ですね!
つまり、memcpy
では第2引数で指定されるアドレス src
から、第3引数で指定されるサイズの分だけがコピーされることになります。
より具体的には、src
から src + n - 1
のアドレスのデータがコピーされます。
strcpy
では「どこまで」はヌル文字の位置で決まる
一方で、strcpy
の関数定義は下記のようになっています。
#include <string.h>
char *strcpy(char *dst, const char *src);
memcpy
関数とは異なり、データをコピーするバイト数を指定する引数が存在しません。
では strcpy
関数では「どこまで」のデータをコピーするかがどうやって決まるでしょうか?
strcpy
関数では第2引数 src
のアドレスから、次に現れるヌル文字('\0'
)までのデータをコピーが行われます。「次に現れるヌル文字('\0'
)」とは src
のアドレスから1バイトずつアドレスを増やしながらデータを調べて行った時に最初に現れるヌル文字('\0'
)です。
つまり、strcpy
関数では「どこまで」は次に現れるヌル文字('\0'
)となります。
この「どこまで」のデータをコピーするかの違いが memcpy
と strcpy
との違いの1つ目になります
違い2:ヌル文字の付加の有無が違う
そして、memcpy
と strcpy
との違いの2つ目は、コピーしたデータの最後に「ヌル文字('\0'
)」が付加されるかどうかになります。
strcpy
ではコピーしたデータの最後がヌル文字('\0'
)になる
strcpy
ではコピー後のデータに文字列の終端を示すヌル文字('\0'
)が付加されます。これは strcpy
では「どこまで」のデータをコピーするかが次に現れるヌル文字('\0'
)によって決まることからも明らかだと思います。
ただし、strcpy
の第1引数 dst
のアドレスの先の配列やメモリのサイズはヌル文字('\0'
)を格納する分のサイズを考慮して設定する必要があることに注意です。
つまり、strcpy
の第1引数 dst
のアドレスの先の配列やメモリのサイズは「文字列長 + 1
」必要になります。
memcpy
ではコピーしたデータの最後がヌル文字('\0'
)になるとは限らない
一方で、memcpy
関数ではデータの最後がヌル文字('\0'
)になるとは限りません。
memcpy
関数では「どこまで」のデータをコピーするかが第3引数のデータをコピーするバイト数によってのみ決まります。
つまりデータの中身が全く関係ありません。
なので、コピーするデータの最後がヌル文字('\0'
)であればコピー先のデータの最後もヌル文字('\0'
)になりますが、それ以外の場合はヌル文字('\0'
)以外の文字になります。
memcpy
関数と strcpy
関数の使い分け
ここまで主に memcpy
関数と strcpy
関数の違いについて解説してきましたが、この違いを踏まえて同じ「データをコピーする関数」である memcpy
関数と strcpy
関数をどのようにして使い分けするかについて解説したいと思います。
スポンサーリンク
文字列をコピーするのであれば strcpy
を使う方が無難
文字列をコピーするのであれば strcpy
関数の方が無難です。
なぜなら、文字列の終端であるヌル文字('\0'
)がコピー先のデータの最後にも付加されるからです。
C言語では文字列の終端にはヌル文字('\0'
)を付けて扱うのが一般的です。
特にC言語の標準関数である strlen
や strcmp
などの文字列操作関数は文字列の最後にヌル文字('\0'
)が存在することを期待して動作します。
文字列操作関数の具体例は下記で紹介していますので、詳しく知りたい方はこちらをご覧ください。
【C言語】文字列操作関数(strlen・strcatなど)まとめ【目的から逆引き】逆にヌル文字('\0'
)が存在しない場合、文字列の終端がどこまでかが分からず配列外のデータまでアクセスしてメモリアクセス違反が発生する可能性があります。
なので、文字列をコピーする場合は、データの最後にヌル文字('\0'
)が付加されるとは限らない memcpy
関数よりも strcpy
関数を使用する方が良いです。
文字列以外をコピーする場合は memcpy
を使う
逆に文字列以外をコピーする場合は strcpy
関数は使いにくいです。
なぜなら、strcpy
関数の「どこまで」のデータをコピーするかがヌル文字('\0'
)の位置によって決まってしまうからです。
ここでヌル文字('\0'
)って実際どんなデータかというと、数値で考えると単なる 0
なんです。
0
を文字として考えるとヌル文字('\0'
)になり、文字列の中では終端を表す特別なデータになるのですが、数値として考えると単なる 0
です。
なので、数値を複数格納しているような配列をコピーするような場合、格納されている数値に 0
が存在するとその時点で strcpy
関数によるデータのコピーが終了してしまい、配列全体をコピーするようなことができません。
例えば画像データなどは 0
というデータが当たり前のように存在するデータになります。例えば黒色などは 0
から構成される色になります。なので strcpy
関数でのデータコピーは不向きです。
こういった理由から、文字列の終端である 0
以外を扱うようなデータのコピーには memcpy
関数の方が扱いやすいです。
memcpy
関数ではコピーするデータの内容に関わらず、第3引数で指定するコピーするデータのバイト数のみによってデータを「どこまで」コピーするかが決まります。
「どこまで」コピーするかがデータの中身に関係ないところが memcpy
関数の良いところです。
memcpy
でも文字列のコピーは可能
memcpy
関数は strcpy
関数よりも汎用的な関数であり、文字列のコピーを行うことももちろん可能です。
例えば strlen
関数で文字列の長さを指定し、その文字列の長さを memcpy
関数の第3引数に指定して memcpy
関数を実行すれば文字列をコピーすることもできます。
ただし、memcpy
関数の場合は前述の通りデータの最後にヌル文字('\0'
)が付加されるとは限りません。
ですので、コピー後のデータを文字列として扱いたいのであれば、コピーしたデータの最後の後ろにヌル文字('\0'
)を格納する、もしくは第3引数にヌル文字('\0'
)を考慮して文字列の長さ +1
したバイト数を指定する必要があります。
下記は memcpy
関数で文字列をコピーするプログラムの例になります。
#include <string.h>
#include <stdio.h>
int main(void) {
char src[] = "moziretsu";
char dst[10];
/* データの最後にヌル文字を自分で付加 */
memcpy(dst, src, strlen(src));
dst[strlen(src)] = '\0';
printf("%s\n", dst);
/* ヌル文字を考慮して第3引数を設定 */
memcpy(dst, src, strlen(src) + 1);
printf("%s\n", dst);
return 0;
}
えー?!
memcpy
関数でも文字列コピーできるんだ…
じゃあ全部データのコピーは memcpy
関数でいいじゃん
strcpy
関数を使う方がいいよmemcpy
関数で文字列をコピーできるのであればデータのコピーには毎回 memcpy
関数を使えば良いようにも感じますが、やっぱり文字列の strcpy
関数を使う方が良いと思います。
その理由は、memcpy
関数での文字列のコピーはバグりやすいからです。
前述の通り、memcpy
関数での文字列のコピーを行う場合、第3引数にヌル文字('\0'
)を考慮して文字列の長さ +1
したバイト数を指定するか、コピーし終わった後にヌル文字('\0'
)を自身で配列等の最後に格納する必要があります。
で、これ結構忘れがちです。
strcpy
関数であれば、strcpy
関数自体が文字列の終端にヌル文字('\0'
)を付加してくれるので、ヌル文字('\0'
)の考慮を忘れたとしても、文字列としてちゃんと扱うことができます。
スポンサーリンク
ループでのデータのコピーと memcpy
の違い
番外編になりますが、最後に for
文等のループで行うデータのコピーと memcpy
関数との違いについて解説しておきたいと思います。
うーん、
strcpy
関数も memcpy
関数も覚えるのめんどくさいや
データのコピーは for
文でも行えるからこれでいいや!
確かにデータのコピーは for
文でも可能だね!
だけど、for
文でデータをコピーするのに比べて memcpy
関数でのコピーの方がメリットがあるよ
データのコピーは、わざわざ関数を利用しなくても、ループ文の中で1つずつデータをコピーすることでも実現することが可能です。
例えば for
文でデータをコピーする例は下記のようになります。
#include <string.h>
#include <stdio.h>
int main(void) {
char src[] = "moziretsu";
char dst[10];
int i;
for (i = 0; i < 10; i++) {
dst[i] = src[i];
}
printf("%s\n", dst);
return 0;
}
データのコピーを行いたいだけであれば、このループ文でのデータコピーで十分です。
ただし、memcpy
関数でのデータコピーの方が処理が速いです。この処理速度の差ははデータサイズが特に大きい場合に顕著に現れます。
ですので、処理速度を重視したい場合は、面倒だとしても memcpy
関数を利用する方が良いです。
まとめ
このページでは memcpy
関数と strcpy
関数の違いについて説明しました。
この2つの関数の決定的な違いは下記の2つです。
- 「どこまで」データをコピーするか
- コピー先のデータにヌル文字(
'\0'
)が付加されるか
文字列をコピーしたいのであれば strcpy
関数の方が無難です。
一方で、文字列以外をコピーするのであれば strcpy
関数は使いにくく、memcpy
関数の方が使いやすいと思います。
単にデータをコピーしたいのであれば for
文などでのも行うことができますが、処理速度を考慮すると標準関数を利用した方が良いです。
今後はこのページで解説したことを考慮して、memcpy
関数と strcpy
関数とを使い分けて行っていただければと思います!