VisualStudio - C4996警告

先頭へ戻る

この章の概要

この章の概要です。


SDLチェック

VisualStudio 2012 で、SDL (Security Development Lifecycle) チェックという機能が追加されました。

SDLチェックが有効な状態でビルドを行った場合、strcpy関数など、使い方によってはセキュリティ上の問題を起こすような旧式の関数の使用をエラーとして扱います。無効になっている場合は、警告だけになります。

VisualStudio 2017 の場合

VisualStudio 2015 までは、新規プロジェクトを作成する際に、有効にするかどうかを選択する項目がありましたが、2017 では無くなっているようで、デフォルトでは「無効」になっています。

プロジェクトを作った後で、[プロパティ] → [構成プロパティ] → [C/C++] → [SDL チェック] の項目から、有効・無効の切り替えを行えます。

VisualStudio 2015 の場合

新規プロジェクトを作成する際、「Win32 アプリケーションウィザード」の「アプリケーションの設定」のところで設定できます。デフォルトではチェックが入っています。

SDLチェックの設定画面

なお、この設定は、プロジェクトを作った後でも、[プロパティ] → [構成プロパティ] → [C/C++] → [SDL チェック] の項目から変更できます。

C4996警告

strcpy関数(⇒リファレンス)などの一部の関数を使うと、警告を発します。前述の SDLチェックが有効になっている場合には、エラーになります。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char s[5];

    strcpy(s, "abc");  /* C4996 warning */
    puts(s);

    return 0;
}

この警告は、使用を推奨されなくなった関数が呼び出された場合に発生しますが、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];

    strcpy_s(s, _countof(s), "abc");
    puts(s);

    return 0;
}

strcpy_s関数は、第2引数に、コピー先のバッファの要素数を指定するようになっています。第3引数がコピー元になります。第2引数に指定した要素数以上のコピーは行われないようになり、バッファオーバーフローを起こさなくなります

配列の要素数は、stdlib.h に定義された _countofマクロで算出できます(ワイド文字列(C言語編第47章)を使うことを考えると、sizeof演算子よりも、このようなマクロを利用する方が簡単です)。

ただし、strcpy_s関数、_countofマクロはいずれも、マイクロソフトが独自で用意したものであり、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)
{
    puts("error!!");
    exit(1);
}

int main(void)
{
    char s[5];

    _set_invalid_parameter_handler(invalid_parameter_handler); /* ハンドラを登録 */
    _CrtSetReportMode(_CRT_ASSERT, 0);  /* デバッグ時のアサートを無効化 */

    strcpy_s(s, _countof(s), "abcde");  /* invalid_parameter_handler が呼び出される */
    puts(s);  /* 実行されない */

    return 0;
}

代替関数を呼ぶたびに、これらの準備が必要な訳ではなく、ハンドラ関数の登録、アサートの無効化は、プログラムの最初の方で1回しておくだけです。

警告を抑制する設定

コンパイラオプションを使って、C4996 警告が報告されないように抑制できます。

この方法は、こちらのページを参考にしてください。

#pragma で抑制する

C言語の #pragma(C言語編第29章)を使用して、警告を抑制できます。

#pragma warning(disable:4996)

#include <stdio.h>
#include <string.h>

int main(void)
{
    char s[5];

    strcpy(s, "abc");
    puts(s);

    return 0;
}

#pragma warning は、VisualStudio の独自機能です。"(disable:XXXX)" の XXXX の部分に警告の番号を記述することで、その位置より後方では、その番号の警告が抑制されます。

この方法は、警告を出さなくしているだけなので、危険性を排除できた訳ではないことに注意しなければなりません。

_CRT_SECURE_NO_WARNINGS で抑制する

C4996警告が、C言語の標準関数の利用によって起きているのであれば、_CRT_SECURE_NO_WARNINGS を define することで抑制できます。

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <string.h>

int main(void)
{
    char s[5];

    strcpy(s, "abc");
    puts(s);

    return 0;
}

#pragma で抑制するよりも、こちらの方が限定的な意味を持ちますが、これもやはり、警告を出さなくしているだけであって、危険性を排除できた訳ではないことに注意しなければなりません。

_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES で置き換える

strcpy関数を strcpy_s関数へ置き換えるように、各関数を代替関数へ置き換えれば、警告を対処できますが、全てを書き換えていくことが大変であったり、別のコンパイラではそういった代替関数が無かったりもします。

VisualStudio の場合、_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];

    strcpy(s, "abc");
    puts(s);

    return 0;
}

単に定義するのではなく、1 に置換するように使うことに注意してください。

この定義によって、strcpy関数を使っている箇所は、自動的に strcpy_s関数に置換されます。例えば、先ほどのプログラムでの strcpy関数の呼び出しは、次のように置き換わります。

strcpy_s(s, 5, "abc");

なお、strncpy関数(⇒リファレンス)のように、もともと文字数を指定するようなタイプの関数に対しては、追加で、次の1文が必要です。

#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT 1

この手法が一番望ましい対処ではありますが、C++ の機能を利用しているため、C言語では使用できません

参考リンク



更新履歴

'2018/8/31 警告番号を使って抑制する方法を「警告を抑制する設定」の項として追加。

'2018/7/24 新規作成。
もともとあった、VisualStudio 2015、2017 のページを統合して再構成。



VisualStudio のトップページへ

資料集 のトップページへ

Programming Place Plus のトップページへ


はてなブックマーク Pocket に保存 Twitter でツイート Twitter をフォロー
Facebook でシェア Google+ で共有 LINE で送る rss1.0 取得ボタン RSS
管理者情報 プライバシーポリシー