こんにちは、ナナです。
strcpy関数は文字列をコピーする標準ライブラリ関数です。
しかし、Visual Studio環境においてstrcpy関数を利用しようとすると、次のビルドエラーが発生します。
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関数を使用しましょう」という内容のものです。
- 安全のための「strcpy_s関数」とは何なのか? なぜ必要なのか?
- 「strcpy_s関数」の使い方とは? どうやって「strcpy関数」から移行すればよいのか?
を本記事では解説していきます。
strcpy関数について理解されていない方は、まず先に『strcpyとmemcpyの使い方【コピー方法の違いとは】』の記事を見てから本記事を読みましょう。
それでは「strcpy関数」から「strcpy_s関数」への移行方法と、その必要性を解説していきましょう。
strcpy_s関数の使い方とstrcpy関数からの移行方法
移行手順を知る前にまずは、strcpy関数とstrcpy_s関数の仕様の違いを把握しておきましょう。
#include <string.h>
char * strcpy(char * dest, const char * src);
errno_t strcpy_s(char * dest, rsize_t size, const char * src);
引数の数と戻り値の型が異なりますね。この引数の違いこそが文字列を安全にコピーするための情報なんです。
strcpy_s関数の仕様について
strcpy_s関数は、3つの引数を受け取って文字列のコピーを行います。
includeファイル | string.h |
関数仕様 | errno_t strcpy_s(char * dest, rsize_t size, const char * src); |
引数1 | 文字列をコピーする先へのポインタ |
引数2 | 引数1で指定したコピー先のサイズ |
引数3 | コピー元の文字列へのポインタ |
戻り値 | 正常/異常終了の種別 |
特記事項 | ヌル文字も含めてコピー先へ文字列をコピーする |
strcpy_s関数では、引数2にコピー先のサイズを指定する必要があります。
この第2引数こそが、コピー処理の安全性を生み出すための仕組みなのです。詳細は後ほど解説しましょう。
strcpy_s関数を使ったプログラム
それではstrcpy_s関数の使い方を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("%s", moji);
return 0;
}
strcpy_s関数
#include <stdio.h>
#include <string.h>
int main(void)
{
char hello[] = "Hello";
char moji[10];
strcpy_s(moji, 10, hello);
printf("%s", moji);
return 0;
}
使い方としてはほとんど変わらないのがわかりますね。呼び出し時に引数を追加することで移行させることができます。
strcpy関数からstrcpy_s関数への移行方法をイメージ図で解説
strcpy関数の呼び出し方をstrcpy_s関数へ差し替えるためには、次のように呼び出し方を変更します。
strcpy関数にはない第2引数にコピー先の配列要素数を設定することで、strcpy_s関数へ移行することができます。
strcpy関数の危険性とは
そもそもstrcpy_s関数が必要となる理由は何なのでしょうか?
それは、strcpy関数の利用は危険を伴うケースがあるためです。それではstrcpy関数の問題点について解説していきましょう!
コピー元の文字列次第で例外が発生する
strcpy関数とは、コピー元の文字列をコピー先の配列へコピーするサービスを提供しています。
それでは問題が起きるプログラムを紹介しましょう。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char hello[] = "Hello";
char moji[3]; // コピー先の配列が小さい
strcpy(moji, hello);
printf("%s", moji);
return 0;
}
このプログラムを実行すると、関数の処理が終わると同時に次のような例外が発生します。
Run-Time Check Failure #2 - Stack around the variable 'moji' was corrupted.
本プログラムではローカル変数によるスタック破壊が発生しています。
この例外内容は「変数mojiの周りのスタックが破損していました」という異常が発生したと通知しているのです。
「例外」とは想定外の異常状態を示します。必ず原因を調べて対処せねばなりません。
文字列コピーによる配列のオーバーランとは
この例外は「オーバーラン」と呼ばれる現象が発生したことを示しています。オーバーランとは、予約していないメモリ領域に対しての読み書きです。
「Hello」という文字列を3つ分しか予約していない配列にコピーしようとしたため、メモリが足らず文字列のコピーが意図せぬメモリ領域まで書き込んでしまったのです。
「オーバーラン」はC言語のプログラム初心者がよく起こす代表的な問題です。文字列を制御するときは非常に発生しやすいため、鍛錬を行い発生させない技術を身に付けることです。
静的メモリのオーバーランは例外が検知されない
先ほどのプログラムはスタックメモリに対してのオーバーランであったため、例外が発生しました。
しかし、次のようにグローバル変数に配列を用意した場合は、メモリが破壊されたことすら通知されず非常に解析困難な不具合が発生します。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
char hello[] = "Hello";
char moji[3];
int main(void)
{
strcpy(moji, hello);
printf("%s", moji);
return 0;
}
このプログラムは動かしても特に例外が発生することなく終了します。しかし、メモリを破壊している事実は変わりありません。
大規模システムにおいてこのようなプログラムがあると、予期せぬタイミングで問題が発生します。
メモリ破壊というのは非常に解析困難な不具合を生み出します。
strcpy_s関数の仕組み
strcpy_s関数を使用する目的とは、オーバーランの即時検知です。
それでは、strcpy関数が起こすオーバーランの問題をstrcpy_s関数ではどのように検知しているのかを示しましょう。
strcpy_s関数によるオーバーラン検知の仕組み
strcpy関数の問題とは、書き込み先の配列領域がどこまで予約されているかわからないため、コピー可能な範囲を知ることができないことです。
strcpy_s関数は第2引数において、コピー先の配列サイズを渡すことによりこの問題を解決しています。
コピー元の文字列の長さはstrcpy_s関数の中で調べることが可能です。その長さとコピー先のサイズを比べることで、コピー可能かを事前にチェックすることができるのです。
strcpy_s関数でオーバーランとなるケースの動作
それでは、もしもオーバーランとなるような引数を受け渡した場合、strcpy_s関数がどのような挙動となるのかを確認しておきましょう。
#include <stdio.h>
#include <string.h>
int main(void)
{
char hello[] = "Hello";
char moji[3] = { 0 }; // コピー先の配列が小さい
strcpy_s(moji, sizeof(moji), hello);
printf("%s", moji);
return 0;
}
このプログラムを動かすと、strcpy_s関数を呼び出した直後に次のメッセージがすぐに表示されます。
strcpy関数の場合は検知はされません。つまり、メモリが破壊されたことに気が付かないケースがあります。
オーバーランが発生するような問題のプログラムであっても、strcpy_s関数であればすぐに気づくことができます。