【C言語】memcpyとstrcpyの違いを解説

memcpyとstrcpyの違いの解説ページアイキャッチ

このページでは、C言語の標準関数である memcpystrcpy との違いについて解説していきたいと思います。

確か strcpy は文字列のコピーに向いてるんだよね?
なぜ文字列のコピーに向いてるんだろう?

なぜって言われると困る…

おそらく strcpy 関数が「文字列のコピー」に向いていることをご存知な方も多いと思います。

ただ、「文字列のコピーに向いている理由」をしっかり説明できる人は少ないのではないでしょうか?

strcpy 関数が memcpy 関数よりも文字列のコピーに向いているのは、当然この2つの動作に違いがあるためです。

このページではまず memcpy 関数と strcpy 関数についておさらいを行い、続いて本題である2つの関数の違いと2つの関数の使い分けについて解説していきたいと思います!

memcpy 関数と strcpy 関数

まずは memcpystrcpy がどのような関数であるかについて説明していきたいと思います。

memcpystrcpy もデータをコピーする関数

memcpy 関数も strcpy 関数も「データをコピーする」関数です。

データをコピーする様子

ここは2つの関数で同じです。

例えば配列に格納したデータを他の配列にコピーしたりする時に便利な関数です。

スポンサーリンク

memcpy 関数

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* 型については下記で説明していますので、詳しく知りたい方は是非読んでみてください。

voidとvoid*型の解説ページのアイキャッチ【C言語】void型とvoid*型(void型ポインタ)について解説

また、第3引数はコピーする「バイト数」です。ついつい配列のサイズなどを指定しまいがちですので注意しましょう。

memcpy 関数の戻り値

戻り値はコピー先の配列やメモリの先頭アドレスになります。

つまり、第1引数 dst と同じアドレスになります。

memcpy 関数の使用例

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 関数の定義は下記になります。

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 関数を使用してデータのコピーを行うプログラムの例は下記になります。

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つの情報が無いとコピーできないんです。

例えばあなたが文章をコピペする場合は、マウスなどで「どこから」「どこまで」の文章をコピーするかを指定すると思います。

これと同じです。

では memcpystrcpy では「どこから」「どこまで」のデータをコピーするようになっているのでしょうか?

両関数とも「どこから」は引数で決まる

両関数とも「どこから」は引数によって決まります。

より具体的には第2引数で指定されるアドレスで決まります。

この指定されたアドレスからコピーが行われます。

では「どこまで」はどのようにして決まるでしょうか?

memcpy では「どこまで」は引数で決まる

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 のアドレスのデータがコピーされます。

第3引数でmemcpyがどこまでコピーするかが決まる様子

strcpy では「どこまで」はヌル文字の位置で決まる

一方で、strcpy の関数定義は下記のようになっています。

strcpyの定義
#include <string.h>
char *strcpy(char *dst, const char *src);

memcpy 関数とは異なり、データをコピーするバイト数を指定する引数が存在しません。

では strcpy 関数では「どこまで」のデータをコピーするかがどうやって決まるでしょうか?

strcpy 関数では第2引数 src のアドレスから、次に現れるヌル文字('\0')までのデータをコピーが行われます。「次に現れるヌル文字('\0')」とは src のアドレスから1バイトずつアドレスを増やしながらデータを調べて行った時に最初に現れるヌル文字('\0')です。

つまり、strcpy 関数では「どこまで」は次に現れるヌル文字('\0')となります。

ヌル文字の位置でstrcpyがどこまでコピーするかが決まる様子

この「どこまで」のデータをコピーするかの違いが memcpystrcpy との違いの1つ目になります

違い2:ヌル文字の付加の有無が違う

そして、memcpystrcpy との違いの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言語の標準関数である strlenstrcmp などの文字列操作関数は文字列の最後にヌル文字('\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 関数で文字列をコピーするプログラムの例になります。

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 文でデータをコピーする例は下記のようになります。

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 関数とを使い分けて行っていただければと思います!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です