C言語とC++におけるC4843警告の原因と対策について解説
Visual Studio 2017以降のコンパイラで表示されるC4843警告は、例外ハンドラーに配列や関数型の参照を使うと発生します。
参照を使うと意図通りに例外オブジェクトと一致せず、捕捉できない可能性があるため、ポインタ型を利用するなどの対応が推奨されます。
警告C4843の原因
C4843警告は、例外ハンドラーに配列型や関数型のリファレンスを指定した場合に発生する問題です。
Visual Studio 2017 バージョン15.5以降では、これらの例外ハンドラーはどの例外オブジェクトとも一致せず、実際に処理が行われることがないため警告が出されます。
以下では、配列型と関数型それぞれの特徴について詳しく説明します。
配列型例外ハンドラーの問題点
配列型の例外ハンドラーは、例外処理の際に意図した例外オブジェクトと一致しない問題を抱えています。
例えば、次のようなコードでは、配列型のリファレンスをcatchブロックとして使用していますが、このハンドラーは実行時に決して呼ばれることがありません。
#include <iostream>
int main() {
try {
throw "";
}
catch (int (&arrayRef)[1]) { // このハンドラーは到達しない
std::cout << "Caught array reference" << std::endl;
}
return 0;
}
上記のコードは、配列型の例外ハンドラーを使用しているため、コンパイラは「例外ハンドラーに到達できない」という警告C4843を出力します。
配列型は直接例外オブジェクトとして扱われないため、例外のキャッチ処理としては不適切と判断されます。
関数型例外ハンドラーの特徴
関数型の例外ハンドラーも同様の問題を抱えています。
関数型のリファレンスを指定すると、例外オブジェクトと一致しないため、ハンドラーが実行されることはありません。
以下の例では、関数型リファレンスを使ったcatchブロックを示します。
#include <iostream>
void sampleFunction() {}
int main() {
try {
throw "";
}
catch (void (&funcRef)()) { // このハンドラーも到達しない
std::cout << "Caught function reference" << std::endl;
}
return 0;
}
この例でも、関数型リファレンスの例外ハンドラーは例外オブジェクトと一致しないため、実際の例外捕捉にはならず、C4843警告が発生します。
Visual Studioの仕様変更と影響
Visual Studio 2017 バージョン15.5以降、コンパイラは例外ハンドラーに対する仕様を厳しくしており、配列型や関数型のリファレンスを使用したcatchブロックに対して警告C4843を出力するようになりました。
これにより、予期せぬ例外処理の失敗を未然に防ぐ意図があります。
C4843警告導入の背景
従来、配列や関数型のリファレンスを例外ハンドラーとして使用するコードが存在していましたが、これらはどうしても例外オブジェクトと一致しないため、実際の例外捕捉にはならないことが判明していました。
Visual Studio 2017 バージョン15.5以降では、このような誤った例外ハンドラーの使用をコンパイラ段階で警告することにより、コードの安全性と信頼性を高める狙いがあります。
/Zc:strictStringsオプションによる影響
さらに、/Zc:strictStringsオプションを有効にすると、文字列リテラルchar*
やwchar_t*
に対しても厳密な型チェックが行われるため、これまで暗黙の変換が可能だったケースもエラーとなる可能性があります。
たとえば、文字列リテラルを例外ハンドラーとして指定していた場合、文字列リテラルは配列型として扱われ、上述の配列型の問題と同様の警告が発生します。
警告回避の修正方法
C4843警告を回避するためには、例外ハンドラーの型を適切な形に変更する必要があります。
一般的には、配列型の場合はポインタ型に、関数型の場合は関数ポインタ型に変換することで、例外オブジェクトとの一致を正しく行わせることができます。
例外ハンドラー型の変更手法
配列型からポインタ型への変換
配列型の例外ハンドラーは、そのままでは例外オブジェクトと一致しないため、ポインタ型に変換して例外を捕捉する必要があります。
以下のコードは、配列型のハンドラーをポインタ型に変更した例です。
#include <iostream>
int main() {
try {
throw "";
}
catch (int (*pArray)[1]) { // 配列型からポインタ型に変換してキャッチ
std::cout << "Caught array via pointer" << std::endl;
}
return 0;
}
Caught array via pointer
上記の例では、配列型の例外ハンドラーをポインタ型に変更することで、正しく例外が捕捉されるように修正されています。
関数型から関数ポインタへの変換
関数型の例外ハンドラーも、関数ポインタ型に変換することで、例外オブジェクトとの一致が可能になります。
以下のサンプルコードは、関数型リファレンスを関数ポインタ型に変更した例です。
#include <iostream>
void sampleFunction() {}
int main() {
try {
throw "";
}
catch (void (*pFunc)()) { // 関数型から関数ポインタ型に変換してキャッチ
std::cout << "Caught function via pointer" << std::endl;
}
return 0;
}
Caught function via pointer
このように、関数型の場合も関数ポインタ型に変更することで、例外ハンドラーが正しく動作するよう修正することが可能です。
サンプルコードに見る具体例
以下は、配列型と関数型の両方の例外ハンドラーを、適切なポインタ型に変換したサンプルコードです。
実際にコンパイルして動作を確認することができます。
#include <iostream>
// 配列型例外ハンドラーをポインタ型に変換
void handleArrayException(int (*pArray)[1]) {
std::cout << "Array exception caught" << std::endl;
}
// 関数型例外ハンドラーを関数ポインタ型に変換
void handleFunctionException(void (*pFunc)()) {
std::cout << "Function exception caught" << std::endl;
}
int main() {
try {
// 配列型の例外を投げる
throw "";
}
catch (int (*pArray)[1]) { // 配列型からポインタ型へのキャッチ
handleArrayException(pArray);
}
catch (void (*pFunc)()) { // 関数型から関数ポインタ型へのキャッチ
handleFunctionException(pFunc);
}
return 0;
}
Array exception caught
状況に応じて、投げる例外やcatchブロックの順序を変更することで動作を確認してください。
注意点と検証のポイント
例外ハンドラーを修正する際には、Visual Studio のバージョンやコンパイラオプションの設定が影響するため、環境に合わせた検証が必要です。
以下に、検証時の注意点をいくつか挙げます。
バージョン別の挙動の違い
Visual Studio のバージョンによっては、C4843警告が発生しない場合もありますが、Visual Studio 2017 バージョン15.5以降では警告が出力されることが確認されています。
各バージョンの挙動を確認し、異なる環境でのテストを行うことが重要です。
例えば、/W4(警告レベル4)を指定したコンパイル環境では、さらに厳密なチェックが実施されるため、注意が必要です。
発生パターンと対処の留意事項
- 例外ハンドラーに配列型または関数型のリファレンスを使用している場合、期待した例外が捕捉されない可能性があるため、必ずポインタ型へ変更する必要があります。
- 修正後のコードは、意図した例外オブジェクトが正しくキャッチされるか、十分にテストするよう心掛けてください。
- コンパイラのオプション(例:/Zc:strictStrings)により、文字列リテラルが従来の動作と異なる場合があるため、設定内容に合わせた見直しが求められます。
以上の点に注意しながら、コードの修正と検証を行うことで、C4843警告を回避し、より安全で信頼性の高い例外処理を実現できます。
まとめ
この記事では、例外ハンドラーに配列型や関数型のリファレンスを使用すると例外オブジェクトと一致しないためC4843警告が発生する原因を解説しています。
Visual Studio 2017以降の仕様変更と/Zc:strictStringsオプションの影響、また警告回避のために配列型はポインタ型、関数型は関数ポインタ型へ変換する具体的な修正方法が理解できます。