この章の概要です。
Visual Studio 2012 で、SDL (Security Development Lifecycle) チェックという機能が追加されました。
SDLチェックが有効な状態でビルドを行った場合、strcpy関数など、使い方によってはセキュリティ上の問題を起こすような旧式の関数の使用をコンパイルエラー📘として扱います。無効になっている場合は、警告📘だけになります。
Visual Studio 2017 から、SDLチェックはデフォルトでは「無効」になっています。
プロジェクトを作った後で、[プロパティ] → [構成プロパティ] → [C/C++] → [SDL チェック] の項目から、有効・無効の切り替えを行えます。
strcpy関数(⇒リファレンス)などの一部の関数を使うと、警告を発します。前述の SDLチェックが有効になっている場合には、エラーになります。
#include <stdio.h>
#include <string.h>
int main(void)
{
char s[5];
(s, "abc"); // C4996 warning
strcpy(s);
puts}
この警告は、使用を推奨されなくなった関数が呼び出された場合に発生しますが、C言語で使用機会の多い標準関数も含まれているため、当サイトのC言語編のサンプルプログラムでも、この警告が起こることがあります。これは、それらの関数には何かしらの危険性があるからです。
たとえば、strcpy関数は、第1引数で指定した受け取りバッファの領域を超えてしまうような大きさのコピーをおこなうと、バッファオーバーフロー📘(C言語編第6章)を起こして、メモリ📘の内容を破壊📘してしまいます。それを承知で、注意して使うというのが従来のやり方でしたが、より安全な方法へ置き換えることも検討するべきです。
この警告に対処する方法はいくつかあります。
推奨されなくなった関数には、普通はより良い代替関数がありますから、そちらへ移行すれば対処できます。
たとえば、strcpy関数であれば、strcpy_s関数という代替関数があります。
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // _countof
int main(void)
{
char s[5];
(s, _countof(s), "abc");
strcpy_s(s);
puts}
strcpy_s関数は、C言語の C11規格で標準ライブラリに加わりました。それより古い規格には存在しませんが、Visual Studio には独自の機能として用意されています。
strcpy_s関数は、第2引数に、コピー先のバッファの要素数を指定するようになっています。第3引数がコピー元になります。第2引数に指定した要素数以上のコピーは行われないようになり、バッファオーバーフローを起こさなくなります。
配列の要素数は、stdlib.h に定義された _countofマクロで算出できます(ワイド文字列(C言語編第47章)を使うことを考えると、sizeof演算子よりも、このようなマクロを利用する方が簡単です)。ただし、_countofマクロは Microsoft が独自で用意したものであり、C言語の標準規格には含まれていません。他のコンパイラではコンパイルできませんから、同一のコードを複数のコンパイラに対応させる場合には、何らかの方法で切り分ける必要があります。
先ほどのサンプルプログラムにおいて、コピーする文字列を “abcde” のように、バッファに収まらない大きさにして実行すると、strcpy_s関数内でエラーを検出します。
デバッグビルドでは、アサート📘(C言語編第30章)によって停止しますが、リリースビルド📘ではハンドラ関数が呼び出されます。ハンドラ関数は、_set_invalid_parameter_handler関数を使って、独自で用意したものを登録しておけます。登録しなければデフォルト📘の挙動を取りますが、その場合はプログラムを終了します。
なお、デバッグビルド時のアサートは、_CrtSetReportMode関数によって無効にできます。
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // _countof
#include <crtdbg.h> // _CrtSetReportMode
void invalid_parameter_handler(
const wchar_t* expression,
const wchar_t* function,
const wchar_t* file,
unsigned int line,
uintptr_t pReserved)
{
("error!!");
puts(1);
exit}
int main(void)
{
char s[5];
(invalid_parameter_handler); // ハンドラを登録
_set_invalid_parameter_handler(_CRT_ASSERT, 0); // デバッグ時のアサートを無効化
_CrtSetReportMode
(s, _countof(s), "abcde"); // invalid_parameter_handler が呼び出される
strcpy_s(s); // 実行されない
puts}
代替関数を呼ぶたびに、これらの準備が必要なわけではなく、ハンドラ関数の登録、アサートの無効化は、プログラムの最初の方で1回しておくだけです。
C言語の #pragma(C言語編第29章)を使用して、警告を抑制できます。
#pragma warning(disable:4996)
#include <stdio.h>
#include <string.h>
int main(void)
{
char s[5];
(s, "abc");
strcpy(s);
puts}
#pragma warning は、Visual Studio の独自機能です。“(disable:XXXX)” の XXXX の部分に警告の番号を記述することで、その位置より後方では、その番号の警告が抑制されます。
この方法は、警告を出さなくしているだけなので、危険性を排除できたわけではないことに注意しなければなりません。
C4996警告が、C言語の標準関数の利用によって起きているのであれば、_CRT_SECURE_NO_WARNINGS を define することで抑制できます。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char s[5];
(s, "abc");
strcpy(s);
puts}
#pragma で抑制するよりも、こちらの方が限定的な意味を持ちますが、これもやはり、警告を出さなくしているだけであって、危険性を排除できたわけではないことに注意しなければなりません。
strcpy関数を strcpy_s関数へ置き換えるように、各関数を代替関数へ置き換えれば、警告を対処できますが、すべてを書き換えていくことが大変であったり、別のコンパイラではそういった代替関数が無かったりもします。
Visual Studio の場合、_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES を使うことで、代替関数への置き換えを自動的に行ってくれます。
#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1
#include <stdio.h>
#include <string.h>
int main(void)
{
char s[5];
(s, "abc");
strcpy(s);
puts}
単に定義するのではなく、1 に置換するように使うことに注意してください。
この定義によって、strcpy関数を使っている箇所は、自動的に strcpy_s関数に置換されます。たとえば、先ほどのプログラムでの strcpy関数の呼び出しは、次のように置き換わります。
(s, 5, "abc"); strcpy_s
なお、strncpy関数(⇒リファレンス)のように、もともと文字数を指定するようなタイプの関数に対しては、追加で、次の1文が必要です。
#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT 1
この手法が一番望ましい対処ではありますが、C++ の機能を利用しているため、C言語では使用できません。
Programming Place Plus のトップページへ
はてなブックマーク に保存 | Pocket に保存 | Facebook でシェア |
X で ポスト/フォロー | LINE で送る | noteで書く |
RSS | 管理者情報 | プライバシーポリシー |