こんにちは、ナナです。
プログラムを行うと、データをコピーしたいシーンが必ず出てきます。
もちろん、データをコピーすること自体は変数の「代入」を行うことで、次のように簡単に複製することができます。
#include <stdio.h>
int main(void)
{
int num1 = 300;
int num2 = 0;
printf("コピー前 num1:%d num2:%d\n", num1, num2);
// num2 <-- num1 コピー処理
num2 = num1;
printf("コピー後 num1:%d num2:%d\n", num1, num2);
return 0;
}
コピー前 num1:300 num2:0
コピー後 num1:300 num2:300
しかし、このような代入だけではコピーできない(しづらい)ケースがあるのです。
データをコピーするための代表的な標準ライブラリ関数である「strcpy」と「memcpy」の使い方を解説します。
本記事では次の悩みを解消する内容となっています。
では、「strcpy」と「memcpy」の使い方と扱い方の違いを学んでいきましょう。
strcpy:標準ライブラリ関数を紹介
まずは、strcpyの標準ライブラリ関数を紹介しましょう。
#include <string.h>
char * strcpy(char * dest, const char * src);
strcpyとは「string:文字列」を「copy:複製」するための標準ライブラリ関数です。
strcpy関数の仕様について
strcpy関数は、2つの引数を受け取って文字列のコピーを行います。
includeファイル | string.h |
関数仕様 | char * strcpy(char * dest, const char * src); |
引数1 | 文字列をコピーする先へのポインタ |
引数2 | コピー元の文字列へのポインタ |
戻り値 | 引数1のポインタが返却 |
特記事項 | ヌル文字も含めてコピー先へ文字列をコピーする |
戻り値は存在しますが、引数1のポインタが返却されるだけであるため、利用する必要ありません。
文字列をコピーするプログラム
C言語における「文字列」とは、文字が連なって最後にヌル文字(’\0’)という終端記号が付いた情報です。文字列はchar型の配列で管理することになります。
このような文字列情報を別の文字配列にコピーするためには、反復処理にて順にコピーすることになります。
#include <stdio.h>
int main(void)
{
char hello[] = "Hello!"; // コピー元
char moji[10] = {0}; // コピー先
int i;
// 文字列のhello配列をmoji配列にコピーする
for (i = 0; hello[i] != '\0'; i++)
{
moji[i] = hello[i];
}
printf("moji:%s", moji);
return 0;
}
moji:Hello!
文字列は一度の代入処理では全てをコピーすることができません。そのため文字配列要素の全てを順にコピーすることになるのです。
文字情報をコピーするのにわざわざfor文を作らなければならないのは面倒ですね。そんな時にはコピー作業を請け負ってくれるstrcpy関数に依頼するのです。
strcpy関数を使ったサンプルプログラム
それでは文字列のコピーを「strcpy関数」で行ったプログラムを紹介しましょう。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char hello[] = "Hello!"; // コピー元
char moji[10]; // コピー先
strcpy(moji, hello);
printf("moji:%s", moji);
return 0;
}
moji:Hello!
for文を使うことなく、strcpy関数が文字列を指定配列にコピーしてくれます。
strcpy関数は、コピー元のヌル文字も一緒にコピーしてくれます。
「Hello」の文字列が、moji配列にコピーされたのがわかりますね。これが文字列をコピーするstrcpy関数の使い方です。
コピー先の配列はヌル文字も含めたサイズ以上の領域をあらかじめ確保しておく必要があることに注意しましょう。
「_CRT_SECURE_NO_WARNINGS」とは何か?
strcpy関数を利用するためには「string.h」のインクルードが必要です。
先ほどのプログラムをよく見てみましょう。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
「string.h」のインクルードを行う前に、「_CRT_SECURE_NO_WARNINGS」がマクロ定義されています。
VisualStudio環境において「strcpy関数」の使用は推奨されていません。
使用するためにはヘッダファイルのインクルードよりも先に「_CRT_SECURE_NO_WARNINGS」をマクロ定義しておく必要があるのです。
この定義をしていない場合、次のビルドエラーが発生します。
error C4996: ‘strcpy’: This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
strcpy関数は安全性に欠けるためstrcpy_s関数を使用しましょう。それでも使いたい場合は「
_CRT_SECURE_NO_WARNINGS」を定義しましょう、という内容です。
strcpy_s関数に関しては次の記事を参照するとよいでしょう。
》参考:strcpy_sへの移行【strcpyより安全なコピー方式】
strcpyの第1引数と第2引数がわからなくなる時の対処法
strcpyの引数はコピー元とコピー先の2つのポインタで構成されています。ここで結構悩みがちなのが、「どっちの引数がコピー元だっけ?」とわからなくなることです。
もちろん、調べれば答えは出るのですが、毎回調べるのが手間になることってありますよね。
そんな時には、次のように覚えておきましょう!
皆さんがstrcpyのような関数を作りたい場合も、このルールに従っておくと他の人が使いやすい関数になります。
memcpy:標準ライブラリ関数を紹介
続いて、memcpyの標準ライブラリ関数を紹介しましょう。
#include <string.h>
void * memcpy(void * dest, void const * src, size_t size);
memcpyとは「memory:メモリ」を「copy:複製」するための標準ライブラリ関数です。
memcpy関数の仕様について
memcpy関数は、3つの引数を受け取ってメモリのコピーを行います。
includeファイル | string.h |
関数仕様 | void * memcpy(void * dest, void const * src, size_t size); |
引数1 | メモリをコピーする先へのポインタ |
引数2 | コピー元のメモリへのポインタ |
引数3 | コピーするバイト数 |
戻り値 | 引数1のポインタが返却 |
特記事項 | なし |
memcpy関数とstrcpy関数との違いは、引数3としてコピーするバイト数が必要となることです。
文字列はヌル文字で終わるルールがあるため、strcpy関数側でコピーサイズを算出できます。
しかし、メモリには終端記号がないため、memcpy関数ではコピーしたいサイズを呼び出す側が与えないといけないのです。
memcpyの第1と第2引数の型はvoid型ポインタになっていますね。void型ポインタに関して知りたい方は『void型の意味と使い方【void型ポインタの扱い方も解説】』を見ておきましょう。
memcpy関数を使ったサンプルプログラム
それでは配列のコピーをmemcpy関数で行ったプログラムを紹介しましょう。
#include <stdio.h>
#include <string.h>
int main(void)
{
unsigned char mem1[] = {0x12, 0x34, 0x56, 0x78}; // コピー元
unsigned char mem2[5] = {0xff, 0xff, 0xff, 0xff, 0xff}; // コピー先
memcpy(mem2, mem1, sizeof(mem1));
printf("mem2[]:%x %x %x %x %x", mem2[0], mem2[1], mem2[2], mem2[3], mem2[4]);
return 0;
}
mem2[]:12 34 56 78 ff
このようにmemcpy関数を使うと指定したバイト数分のメモリをコピーしてくれます。
第3引数で「コピーするバイト数」を指定することに注意しましょう。
memcpyを使うシーンとは?
memcpyを使わないとデータがコピーできないシーンとは「文字列以外の配列データ」です。
C言語において配列とは、逐一配列要素をコピーする必要があります。そのコピー作業を肩代わりしてくれるのが「memcpy関数」なのです。
次の例は、short型で配列要素数が3のnum1変数を、num2変数にコピーするプログラムです。
#include <stdio.h>
#include <string.h>
int main(void)
{
short num1[3] = { 0x1234, 0x5678, 0x9abc };
short num2[3] = { 0 };
// short型×3個のバイト数をコピーする
memcpy(num2, num1, sizeof(short) * 3);
// コピー後のnum2配列を表示
printf("%hx %hx %hx", num2[0], num2[1], num2[2]);
return 0;
}
1234 5678 9abc
このプログラムでは6バイトのメモリコピーが実施されることになります。
第3引数の指定方法は他にもあります。「sizeof(num1)」といった方法でも6バイトを示すことになります。
sizeof演算子の使い方は『sizeof演算子【データサイズの算出と実践的な使い方】』を参照するとよいでしょう。
構造体変数のコピーにmemcpyは不要
構造体を変数として定義した場合に、構造体の情報をコピーしたいシーンが出てくることがあります。
次のように定義した構造体「pos1」変数の情報を「pos2」変数へコピーしたいというシーンにおいて、memcpy関数をわざわざ利用する方がいます。
#include <stdio.h>
#include <string.h>
typedef struct
{
double latitude; // 緯度
double longitude; // 経度
} Coordinate;
int main(void)
{
Coordinate pos1 = { 34.646100, 134.999100 };
Coordinate pos2;
// memcpyによる構造体変数のコピー
memcpy(&pos2, &pos1, sizeof(Coordinate));
printf("%lf %lf", pos2.latitude, pos2.longitude);
return 0;
}
もちろん、memcpy関数を使ってもコピーは可能なのですが、構造体変数は代入でコピーすることができます。
// 代入演算子による構造体変数のコピー
pos2 = pos1;
memcpy関数を使うよりも、代入演算子の方がすっきりと書けますね。
構造体は実際のシステム開発の中ではよく登場するので、「構造体は代入でコピーできる」ということは知っておくとよいです。
strcpy関数とmemcpy関数の使い分け
それぞれのコピー関数ですが、次のシーンに応じて使い分けをしましょう。
この2つのコピー関数は実践でもよく登場するので、しっかりと使い分けできるようにしておきましょう!