はじめに
Microsoft Visual Studio Community 2017を使用して、C++コンソールプロジェクトでプログラムを作成しています。
C++警告レベルはデフォルト(レベル3 (/W3))を使用しています。
セキュア関数を使用しないで、strcpy関数で文字列をコピーした場合
以下のようなstrcpy
関数を使用したサンプルコードをビルドしてみます。
#include "stdafx.h" #include <iostream> int main() { char name[8]; printf("First Test Start\r\n"); strcpy(name, "1234567"); printf("First Test OK\r\n\r\n"); printf("Second Test Start\r\n"); strcpy(name, "12345678"); printf("Second Test OK\r\n"); return 0; }
ビルド結果は以下のとおり。警告レベルがデフォルトでも、エラーが発生してビルドが成功しません。
セキュア関数(strcpy_s)を使用して、文字列コピーをした場合
セキュア関数strcpy_s
を使用した場合は、ビルドが通りました。
コピー1回目は正しく実行されましたが、2回目が失敗しています。
これは、コピー元の文字列の要素数は、NULL文字を含めると9です。
しかし、コピー先の文字列の要素数は、8なので、バッファオーバーランが発生して、アサーションエラーによるメッセージダイアログが表示されます。
#include "stdafx.h" #include <iostream> int main() { char name[8]; printf("First Test Start\r\n"); strcpy_s(name, _countof(name), "1234567"); printf("First Test OK\r\n\r\n"); printf("Second Test Start\r\n"); strcpy_s(name, _countof(name), "12345678"); printf("Second Test OK\r\n"); return 0; }
バッファオーバーランが発生すると、アサーションエラーが発生して、以下のような感じに、エラー処理を書いても、通りません。 そこで、無効パラメータハンドラーを使用することによって、エラー処理を通るようにしたいと思います。
if (strcpy_s(name, _countof(name), "12345678")) { // エラー処理 printf("\r\n\r\nError!! [file : %s] [line : %d] [Second]\r\n\r\n", __FILE__, __LINE__); } else { printf("Second Test OK\r\n"); }
無効パラメータハンドラー・セキュア関数(strcpy_s)を用いた文字列コピー
無効パラメータハンドラーmyInvalidParameterHandler
を定義した以下のプログラムを実行してみます。
実行結果を見ると、正しくエラー処理が実行されています。
プログラムの説明をします。まず、自作した無効パラメータハンドラーを登録します。
2回目のstrcpy_s
実行時、バッファオーバーランが発生します。
これまでは、エラー通知はデバッグ メッセージ ウィンドウに送られますが、
_CrtSetReportMode
で、0(モードなし)を設定することで、デバッグメッセージウィンドウへの通知を無効にできます。
その場合は、エラー通知は、自作した無効パラメータハンドラーに送られます。
無効パラメータハンドラーによる処理が実行された後、プログラム処理は、元の2回目のstrcpy_s
の処理に戻ります。
無効パラメータハンドラーを用いることで、プログラムを停止せずに、正しくエラー処理を実行できました。
また、登録した無効パラメータハンドラーの設定は必要な箇所のみにしておき、必要がなくなれば、
登録解除して、デフォルトのエラーハンドラーを使用させるようにしておくことをお勧めします。
その際、_CrtSetReportMode(_CRTDBG_MODE_WNDW, 0);
で、エラー通知をもとのデバッグ メッセージ ウィンドウに送る設定に戻してください。
#include "stdafx.h" #include <iostream> void myInvalidParameterHandler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) { wprintf(L"Invalid parameter detected in function %s." L" File: %s Line: %d\n", function, file, line); wprintf(L"Expression: %s\n", expression); } int main() { char name[8]; _invalid_parameter_handler oldHandler, newHandler; newHandler = myInvalidParameterHandler; // 無効パラメータハンドラーを登録する oldHandler = _set_invalid_parameter_handler(newHandler); // Disable the message box for assertions. _CrtSetReportMode(_CRT_ASSERT, 0); printf("First Test Start\r\n"); if (strcpy_s(name, _countof(name), "1234567")) { printf("\r\\r\nnError!! [file : %s] [line : %d] [First]\r\n\r\n", __FILE__, __LINE__); } else { printf("First Test OK\r\n\r\n"); } printf("Second Test Start\r\n"); if (strcpy_s(name, _countof(name), "12345678")) { // エラー処理 printf("\r\n\r\nError!! [file : %s] [line : %d] [Second]\r\n\r\n", __FILE__, __LINE__); } else { printf("Second Test OK\r\n"); } // Enable the message box for assertions. _CrtSetReportMode(_CRTDBG_MODE_WNDW, 0); // 無効パラメータハンドラーの登録を解除する oldHandler = _set_invalid_parameter_handler(oldHandler); return 0; }
https://msdn.microsoft.com/ja-jp/library/a9yf33zb.aspx
以下の情報を参考にしました
_set_invalid_parameter_handler
_CrtSetReportMode