C言語のコンパイラ警告 C4770について解説
この記事では、c言語のコンパイラ警告 C4770についてご紹介します。
C4770は部分的に検証された列挙型を数値として利用する際に発生する警告で、Visual Studio 2013以降の環境で確認できます。
具体例を交えて、警告の原因や解消方法について分かりやすく説明しており、c言語のコード改善の参考になります。
警告 C4770 の背景
Visual Studio 2013以降の仕様変更
Visual Studio 2013から、コンパイラは一部の列挙型が整数型にキャストされる場合に警告を出すようになりました。
もともと列挙型の値は暗黙のうちに整数型へ変換されるため、以前はあまり問題視されていませんでした。
しかし、Visual Studio 2013以降はコンパイラのチェックが厳密化され、値が負であったり、定義外の値になってしまう可能性がある場合に警告C4770が発生するようになりました。
なお、この警告は既定では有効になっておらず、レベル1の警告として利用するためにはコンパイラオプション「/w14770
」を指定する必要があります。
列挙型の扱いにおける変化
列挙型は基本的に整数型として扱われるため、暗黙の型変換で整数値に変換される際、元の列挙体に含まれていない値が計算に用いられるリスクがあります。
Visual Studio 2013以降では、部分的に検証された列挙型という扱いになり、キャスト時に値の範囲チェックが十分でない場合に警告が発生するようになっています。
この変更により、プログラム内で予期せぬ値や不正な値が演算に用いられるリスクがある場合に、事前に警告を通知してくれる仕組みが整えられました。
警告発生の原因
列挙型のキャストによる問題
列挙型は整数型への暗黙の変換が行われるため、変換後に元の列挙体に定義されていない整数値が現れる可能性があります。
たとえば、ユーザからの入力や外部パラメータによって生成された値がそのまま列挙型にキャストされると、期待した範囲を超えた値になってしまう恐れがあります。
このような状況下で、整数型として計算や比較を行うと、適切なチェックが行われず、結果として誤った動作を招く可能性があるため、警告C4770が発生します。
部分的に検証された列挙型の利用ケース
部分的に検証された列挙型とは、列挙体に定義されていない値が処理に混入する可能性があるケースを指しています。
たとえば、外部入力や不正なキャストによって、列挙型の値が定義された範囲以外の数値となってしまう場合があります。
このような場合、プログラム側で十分なチェックが行われないと、予期せぬ動作や計算結果につながることがあるため、コンパイラは警告を表示して、プログラマに注意を促す仕組みとなっています。
コード例による解説
警告発生コードの紹介
以下のサンプルコードは、警告C4770が発生する例です。
コード内では、コマンドライン引数から取得した値をatoi
関数を使って整数に変換し、その整数値をenum E
型にキャストしています。
それにより、実行時には定義された列挙体E
の範囲外の値が使用される可能性があり、警告が発生します。
#include <stdio.h>
#include <stdlib.h>
// 列挙型の定義。E_MAXは有効な範囲の上限
enum E { a = 0, b, c, E_MAX };
int main(int argc, char *argv[]) {
// コマンドライン引数を整数に変換し、列挙型にキャスト
// コメント:入力値が定義外の場合、警告C4770が発生する可能性があります
if (argc < 2) {
printf("Usage: %s <number>\n", argv[0]);
return 1;
}
const enum E e1 = (enum E)atoi(argv[1]);
// 値のチェックが十分でないため、範囲外の値が混入する可能性があります
if ((int)e1 >= E_MAX) {
return 0;
}
// 列挙型の値を用いた演算。この部分で警告が発生します
const int n = e1 + e1;
printf("Output: %s\n", argv[n]);
return 0;
}
Usage: sample_program <number>
警告発生理由の詳細
上記のコードでは、argv[1]
で取得した数値をそのままenum E
にキャストしています。
コンパイラは、キャストによって得られたe1
の値が負である可能性や、E_MAX
より大きい場合のチェックが十分でないと判断すると警告C4770を発生させます。
具体的には、キャストにより整数型として扱われる際、元の列挙体に定義されていない不正な値が含まれている場合、想定外の動作を引き起こす恐れがあるためです。
このため、ユーザの入力や外部パラメータをそのまま列挙型に変換する場合は、値チェックを厳密に行うことが重要となります。
警告回避の方法
キャスト修正による対応
警告C4770を回避するためには、キャスト時の値が正しい範囲内にあることを明示的に保証する必要があります。
以下に、その対応策をご紹介いたします。
unsigned intへのキャストによる修正
e1
をunsigned int
にキャストすることで、暗黙的な負の値の発生を防げます。
これにより、定義された範囲内の正の値のみが扱われることを保証できます。
#include <stdio.h>
#include <stdlib.h>
// 列挙型の定義。E_MAXは有効な範囲の上限
enum E { a = 0, b, c, E_MAX };
int main(int argc, char *argv[]) {
// コマンドライン引数を整数に変換し、列挙型にキャスト
if (argc < 2) {
printf("Usage: %s <number>\n", argv[0]);
return 1;
}
const enum E e1 = (enum E)atoi(argv[1]);
// unsigned int にキャストして範囲チェックを行うことで、負の値を防止
if ((unsigned int)e1 >= E_MAX) {
return 0;
}
const int n = e1 + e1;
printf("Output: %s\n", argv[n]);
return 0;
}
Usage: sample_program <number>
負の値チェックの明示的実装
あるいは、キャスト後の値が負でないことを明示的に条件文で確認する方法もあります。
これにより、範囲の上限だけでなく下限についてもチェックが可能です。
#include <stdio.h>
#include <stdlib.h>
// 列挙型の定義。E_MAXは有効な範囲の上限
enum E { a = 0, b, c, E_MAX };
int main(int argc, char *argv[]) {
// コマンドライン引数を整数に変換し、列挙型にキャスト
if (argc < 2) {
printf("Usage: %s <number>\n", argv[0]);
return 1;
}
const enum E e1 = (enum E)atoi(argv[1]);
// 負の値と上限を明示的にチェック
if ((int)e1 < 0 || (int)e1 >= E_MAX) {
return 0;
}
const int n = e1 + e1;
printf("Output: %s\n", argv[n]);
return 0;
}
Usage: sample_program <number>
Visual Studio の設定
警告オプション /w14770 の利用方法
Visual Studioでは、デフォルトでは特定の警告は無効化されています。
警告C4770に関しては、コンパイラオプション「/w14770
」を付与することでレベル1の警告として有効にできます。
プロジェクトのプロパティやビルド設定でこのオプションを追加することで、コンパイル時に警告C4770を確認できるようになります。
バージョンごとの警告設定の対応
Visual Studioの各バージョンにおいて、警告の扱いは若干異なる場合があるため、プロジェクトの対象バージョンに応じた設定が求められます。
具体的には、Visual Studio 2013以降では上記の警告が有効である一方、以前のバージョンでは発生しない可能性があります。
また、コマンドラインでのビルドの場合は、使用するコンパイラのバージョンに合わせたオプション設定が必要となります。
プロジェクト全体で一貫性を保つためにも、各バージョンの仕様をしっかりと確認し、適切なコンパイラオプションを利用してください。
まとめ
この記事では、Visual Studio 2013以降における警告C4770の背景と原因を解説しています。
列挙型が整数型にキャストされる際に定義外の値が混入するリスクや、部分的に検証された列挙型の問題点について具体例を用いて説明しました。
また、unsigned intへのキャストや負の値のチェックなど、警告回避の方法とVisual Studioの警告設定についても理解できます。