C4996警告 | Programming Place Plus Visual Studio編

トップページVisual Studio編

この章の概要

この章の概要です。


SDLチェック

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

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

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

プロジェクトを作った後で、[プロパティ] → [構成プロパティ] → [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);
}

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

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)
{
    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);  // 実行されない
}

代替関数を呼ぶたびに、これらの準備が必要なわけではなく、ハンドラ関数の登録、アサートの無効化は、プログラムの最初の方で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);
}

#pragma warning は、Visual Studio の独自機能です。“(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);
}

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

_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES で置き換える

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];

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

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

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

strcpy_s(s, 5, "abc");

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

#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT 1

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

参考リンク


更新履歴

’2019/2/12 VisualStudio 2015 の対応終了。

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

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



Visual Studio編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る