C++ 例外処理と構造化例外処理 (1)

これまでに構造化例外処理についていくつか書きましたが、ここでは構造化例外処理が「何ではないのか」 を書くことによって、構造化例外処理に関する理解を深めたいと思います。

対比する対象は C++ 例外です。

次のコードを見てください。

#include <iostream>
using namespace std;

class MyClass {
public:
     MyClass() {}
     void Foo() { 
          cout << "MyClass::Foo" << endl;
     }
};

int main(int argc, char* argv[] ) {

     cout << "Entering main()" << endl;
     
     try {

          cout << "try..." << endl;

          throw MyClass();

          cout << "try end" << endl;
     }
     catch (MyClass& ex) {
          cout << "catch!" << endl;

          ex.Foo();

          cout << "catch! end" << endl;
     }

     cout << "Exit main()" << endl;
     
     return 0;
}

C++ では try-catch ブロックを用います。try にて例外を throw し、catch では throw された型毎に例外をキャッチするブロックを記述することが出来ます。 上記の例では MyClass を try ブロックで throw して、catch(MyClass &ex) にてキャッチしています。

これは基本的な C++ の話です。

それでは、次のように 「strcpy で例外を発生」させたらどうでしょうか。

#include <iostream>
using namespace std;

class MyClass {
public:
     MyClass() {}
     void Foo() { 
          cout << "MyClass::Foo" << endl;
     }
};

int main(int argc, char* argv[] ) {

     cout << "Entering main()" << endl;
     
     try {

          cout << "try..." << endl;

          strcpy (NULL, NULL);

          cout << "try end" << endl;

     }
     catch (...) {
          
          cout << "catch!" << endl;

     }

     cout << "Exit main()" << endl;
     
     return 0;
}

strcpy(NULL,NULL) は以前みたように、アクセス違反例外を発生させます。しかしこれは C++ 例外ではありません。

makefile は次の通りです。これでビルドしてみてください。

TARGETNAME=cppeh2
OUTDIR=.\chk
LINK32=link.exe

ALL : "$(OUTDIR)\$(TARGETNAME).exe"

CPPFLAGS=\
	/nologo\
	/MT\
	/W4\
	/Fo"$(OUTDIR)\\"\
	/Fd"$(OUTDIR)\\"\
	/c\
	/Zi\
	/EHs\
	/D_WIN32_WINNT=0x0600
		
LINK32_FLAGS=\
	/nologo\
	/subsystem:console\
	/pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
	/machine:I386\
	/out:"$(OUTDIR)\$(TARGETNAME).exe"\
	/DEBUG
	
LINK32_OBJS= \
	"$(OUTDIR)\$(TARGETNAME).obj"

"$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS)
    $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)

"$(OUTDIR)" :
    if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"

.c{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $< 

.cpp{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $<

では上記のコードを実行してみましょう.. おそらくクラッシュしたのではないでしょうか。

私の環境では上記のようなダイアログが現れました。

では次に、makefile 内の /EHs/EHa に変えてリビルド (nmake -a) して、 再度実行してください。今度はクラッシュせずに例外がキャッチされたのではないでしょうか。

> chk\cppeh2.exe
Entering main()
try...
catch!
Exit main()

実は /EHs と /EHa は両方とも 「C++ 例外を有効にする」 というオプションなのですが、 /EHs は 「構造化例外なし」で、/EHa は 「構造化例外あり」 のオプションなのです。

/EHa オプションを指定すると、SEH における例外 (ソフトウェア例外及びハードウェア例外) 処理が必要になったときに、 それを、C++ 例外として try-catch ブロックで処理できるように例外を置き換えます。

/EHs の場合は、SEH は C++ 例外に変換されません。ですからこの場合、SEH はあくまで try-except ブロックで処理しなければなりません。このため、上記の例 /EHs のときは try-catch を書いているにもかかわらず、catch されなかったのです。

Visual C++ では SEH を C++ に変換する手段が用意されています。次回それについて書きたいと思います。 今回は SEH と C++ 例外は別物である、というお話でした。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説