文字列を格納したchar型配列を複製しようと思ったところ、memcpy()を使えばスッキリかけることが分かったのでメモ。C言語には似たような関数(memmoveやstrcpy)があるので、それらとの違いもまとめてみる。
ちなみに、配列のポインタについては以下の記事で詳しく書いています。
【C言語】配列のポインタについて
使い方
array2に格納された文字をarray1にコピーする例を考えます。
matlabみたいな感覚でarray1=array2とかやってしまいたいところなのですが、そうはいかないのがC言語。配列の代入は許されていません。
配列の複製は何も考えずに単純な発想だと以下のようになるかと思います。
1 2 3 4 5 6 7 8 9 10 11 12 |
int main(){ int i=0; char array1[8]; char array2[8]="HOGEHOGE"; while(i<8){ array1[i]=array2[i]; i++; } return 0; } |
ループを回して配列の1要素ずつコピーしていく。しかし見るからにイケてない。
ループなんか回さずに1行のコマンドでビシッとキメたいところ。
そこで、memcpy()という関数を使用します。
memcpy()は指定した範囲のメモリをコピーする関数。これを利用して配列をコピーすることができます。
以下のように使用します。
1 2 3 4 5 6 7 8 |
int main(){ char array1[8]; char array2[8]="HOGEHOGE"; memcpy(array1, array2, sizeof(array2)); return 0; } |
memcpyは第一引数がコピー先、第二引数がコピー元、第三引数が指定バイト数です。
最初の例でループにしていた部分がすっきり1行にまとまりました。
もう少し詳しく
memcpyの仕様を見てみます。
void *memcpy(void *buf1, const void *buf2, size_t n);
第一引数が出力となるポインタ、第二引数にはconstがついているので入力となる汎用ポインタ, 最後の引数はサイズです。
みてわかるとおり、memcpyはメモリのアドレスbuf2とサイズを受け取り、別のアドレスbuf1へbuf2のメモリの要素をコピーするような関数になっています。
memcpyはその名の通りメモリをコピーしていて、アドレス buf2からnサイズ分のメモリを別のアドレスbuf1へコピーする処理を行います。
引数には配列の名前array1, array2などを入力していました。C言語始めたての頃は「配列の名前」を引数に渡していると考えがちです。
しかし、これは何も配列の名前を入力していた訳ではなく、配列の先頭要素のポインタを入力していたのです。
配列array2[]の場合は、”array2″と記述することで先頭要素のアドレス(つまり”&array2[0]”)を示します。
ということで、memcpyへは引数として配列を渡すと思いがちですがこれは誤り。正確には配列のポインタを渡してメモリのコピーを行っているということになります。
memmoveとstrcpyとの違いは?
Cの標準ライブラリの関数で似たような使い方ができる関数としてmemmove()とstrcpy()があります。これらとの違いを見ていきます。
違いとしては大きく二点
- コピー元とコピー先の領域の重なりが許されるか
- null 文字終端を認識するか
コピー元とコピー先の領域重複 | null文字終端 | |
memcpy | × | × |
memmove | 〇 | × |
strcpy | × | 〇 |
コピー元とコピー先の領域重複について(memmove())
コピー元のアドレスとして0x4000, コピー先のアドレスとして0x4006, コピーするメモリサイズに8byteを指定したときを考えてみます。
すると上の図のように、当然のことながらコピー元とコピー先の領域が重なってしまうことになります。
このような場合どうなるかというと、言語仕様としては特に定義されていないため、重複領域の値がどうなるかはわかりません(笑)
そんなん気利かせてなんとかしてくれよと。
そんなときに使えるのがmemmove()。領域の重複が発生していても正常にコピーを行うことができます。
null文字終端について(strcpy())
strcpy()はその名の通り文字列を扱うのに最適化されたメモリコピーの関数です。
プログラミングにおいて、通常文字列の最後にはnull文字(‘\0’)が挿入されていて、これによって文字列の終わりがどこなのかを認識することができます。
strcpyはmemcpyやmemmove同様、引数としてコピー元のアドレスとコピー先のアドレスを渡します。違う部分としてはサイズの指定の引数がない事です。
コピー元のアドレスは文字列の先頭要素のアドレスを渡すことになっています。先に述べたように、文字列は最後がnull文字で終端されているため、先頭要素からnull文字までの要素を数えれば、サイズを入力しなくともコピーすべきサイズを自動的に認識することができます。
コピー元と同様に、コピーされた先でも最後の要素はnull文字で終端されます。
まとめ
配列をコピーする際に使えるmemcpy()と、それと似た使い方ができるmemmoveとstrcpyについてまとめました。
最初の方で、ループで回す方法が効率悪いと書きました。確かに、行数は多くなりますから見た目としてはスマートではありませんが、1つ1つの要素を検査して処理を入れたい場合などはループでコピーをするような形になるかと思います。
memcpy() とmemmove()はよく似ていて、一件memmove()の方が上位互換のようにも思えます。考えてみるとmemmoveは領域重複のチェックが入りますから、memcpyよりも速度は遅くなりそうなものですが、今後実際に実験して確認をしてみます。