サーカスで命綱を使ったら笑いものになる

条件コンパイル文はプログラム開発の分岐点に相当する.この分岐点がゼロ個になった状態が「正式版」であるとして,その個数を削減する作業を進めてきた.「再開発スタート版」の時点で206個あった値を持たない#define定義文は現在131個まで削減された.さらにこれらを整理して以下の4つのヘッダファイルで区分管理することにした.

  1. nodule.h SPECIFICATION:(16), OPTIONS:(11), PENDING:(0), INCOMPLETE:(0)
  2. comdebug.h COMDEBUG:(36)
  3. Bobjecth DEBUG:(23), VERIFY:(5)
  4. coupling.h TEST:(11)
  5. *.cpp LOCAL:(10)
  6. *.h _H_ (26)
  7. total (138)

mmm… 合計が一致しない!定義文の個数は正規表現を使って検索している.なにか漏れがあるのだろうか?漏れがあるとしても,漏れているものを特定できないことには対策の打ちようもない.VSの検索では検索結果をテーブル形式で出力できるので,それを表計算に持ち込んで並び替えなどしてみれば何か分かるかもしれない.開発機をネットに出して,Open Officeをダウンロードしてこよう.⇒横文字に慣れるために今回は英語版を使ってみることにした,ところまではよかったのだが,連続データの入力ができない.普通表計算ではドラッグして連続した数値を自動入力することができるのだが,なぜか同じ数字の並びになってしまう.Open Office のバージョンは4.1.8だ.ネットを見ても「できる」としか書いてない.仕方ないので,日本語版に差し替えてみた.

インストールしてまだ空のファイルを開いた状態で一番上のセルに1と入力し,右下の+をつかんで下にドラッグするときれいに1, 2, 3… の数字が入る.これは締めたと思い,COMDEBUG: という文字列を含む行だけフィルタリングしてもう一度ナンバリングしようとすると,元の状態に戻ってしまっている.つまり,同じ数字が入ってしまう.しかし,最初はできていたのだからできない訳はないだろうと考えて,フィルタリングの結果を別のシートにコピーしてもう一度試すと今度はうまくいった.確かにフィルタリングした状態というのは一部データだけを表示した状態なので,その一部データに適切な番号を割り当てるというのは多少問題のある操作かもしれない…

これで検索テーブルのデータとタグで直接検索した結果を突き合わせた結果,どこで問題が発生しているかを大体突き止めることができた.まず.値を持っているマクロ定義文にタグを付けていた.たとえば

#define probe {}   // COMDEBUG: ← タグ 

のような感じだ.探しているといろいろなアラも見えてきた.ヘッダファイルの冒頭には必ず,以下のような行を入れて,ヘッダファイル全体を囲むようにしているが,_H_の後ろの「_」が欠けているのが複数箇所で見つかった.

#if !defined(_◯◯◯_H_) ◯◯◯はヘッダファイル◯◯◯.hの語幹部
#define _◯◯◯_H ← 「_」が落ちている
 ︙ヘッダ本体
#endif

そのほかにも

#define { \ ← 「{」というマクロを定義したことになる?

のようにキーワード自体が消失しているマクロすらあった.それらを修正したり,などいろいろやっているうちにいつの間にか comdebug.h の大掛かりな書き直しに発展してしまった.実際,今回のセッションではnodule.hからcomdebug.hにはかなりの定義文を移動しているが,comdebug.hにはまったく手を付けていなかったのでいずれやらざるを得ない仕事ではあったのだが… そんな訳で検索で抽出した定義文の個数と個別にタグを検索した結果が完全に一致するまでに丸一日掛かってしまった.最終的な結果は定義文131個で変わらず,内訳は下記の通り.★はデフォルトON,☆はOFF.■はリリース版でON,□は混合,・はデバッグ用でリリース版ではOFF.

  1. ■ nodule.h ★SPECIFICATION:(16), ☆OPTIONS:(11), PENDING:(0), INCOMPLETE:(0)
  2. □ comdebug.h COMDEBUG:(28)
  3. ・Bobjecth ☆DEBUG:(23), ★VERIFY:(5)
  4. ・coupling.h ☆TEST:(11)
  5. ・*.c LOCAL:(10)
  6. *.h _H_ (27)
  7. total (131)

SPECIFICATIONはデフォルトONで確定している(つまり分岐なし),OPTIONSはデフォルトOFFで未確定だが,安定版ではOFF,COMDEBUGはモードにより変わる,DEBUG,VERIFY,TESTはデバッグ時のみでリリース版ではすべてOFF.この整理によって条件コンパイル文は一意に確定し,リリース版の分岐はゼロになったと認定できる.この過程でこのシステムには4つのモードがあることが分かった.

  1. デバッグ版モード(Debug) _DEBUG
  2. 疑似リリース版モード(Debug) _RELEASE
  3. 非公式版モード(Release) 定義なし
  4. 公式版モード(Release) FORMALVERSION

Visual Studioでは開発時には(Debug)モードでビルドし,配布版をビルドするときには(Release)モードでビルドする.(Debug)モードに切り替えると,システムが自動的に_DEBUGを定義してくるので(定義済マクロ),それをソースコードの中で使って

#ifdef _DEBUG
︙データをダンプしたりなど,デバッグ時に行うこと…
#endif

のようなことができる.これでほとんどのバグはシューティングできるのだが,まれにリリース時にのみ起きるバグというのが発生することがある.(2)疑似リリース版モードというのはこのような事態に対処するためのモードで,_DEBUGを#undefすることによって,デバッグ時にのみ走るコードを抑制してリリース版と同じ条件で走らせるというモードだ.このようなことが起きるのはデバッグ用コードに動作に影響するような部分が混入しているということを意味するが,それを目視で切り分けるのはかなり難しい.

非公式版モードと公式版モードの違いは所内版と一般公開版の違いと言える.非公式版はデバッグ版から_DEBUGのブロックを除いただけなので,ほぼ同じものと考えて間違いない.デバッグ版ではエラーが発生するとSTOP文で停止するようになっているが,リリース版では割り込みは使えないのでパネルを出して停止するというくらいの違いだ.

ReleaseモードでDLLをビルド中BugReportDialog.cppのコンパイルでエラーが発生した

1>d:\zelkova\zelkovadll\src\bugreportdialog.cpp : fatal error C1001: コンパイラで内部エラーが発生しました。
1>(コンパイラ ファイル ‘d:\agent\_work\18\s\src\vctools\compiler\utc\src\p2\main.c’、行 187)
1> この問題を回避するには、上記の場所付近のプログラムを単純化するか変更してください。詳細については、Visual C++ ヘルプ メニューのサポート情報コマンドを選択してください。またはサポート情報 ヘルプ ファイルを参照してください。
1>  link!InvokeCompilerPass()+0x2f79a
1>  link!InvokeCompilerPass()+0x2e9f8
1>  link!CloseTypeServerPDB()+0xd5266

このファイルは開いてもいないので,もちろんどこもいじったりなどしていない…もう一度単独でコンパイルしたら,何のエラーもなく終わった.いや,ビルドすると再発する.⇒クリーンビルドしたら解消した.どうもオプティマイザで出しているエラーのようだ.

FORMALVERSIONではSTOPで停止する代わりにエラーパネルを出した後,例外をスローするようにしたが,SUWのパネルが出てしまう.⇒ERR_SHOWUNDERWEARとERR_ABORTPROCでエラーコードがかぶっていた.別のコードを割り当てることでSUWは出ないようになったが,最後のパネルが出るまでにエラーパネルが数段に渡って出る.「警告パネルを重複表示しない」というオプションはSPECIFICATIONに入っているのだが,さっぱり効いていないようだ.⇒このオプションではbugflagを操作しているが,置いてある箇所がSaveRescueFileの中で,リリースモードでは無効になっているブロックだ.

AlartPrintはこのフラグを見てリターンするようになっている.alartprintも同様の動作になっている.このフラグは「障害発生時の緊急退避ファイル保存の場合は例外をスローしない」という趣旨のもので一般的にエラーパネルが多段に出ることを抑制するものではない.⇒STOPでパネルを出さずに例外をスローするだけにして,パネルはcouplingが出しているものだけになった.

image

この後,VBでもエラパネルを出してくるが,まぁそれくらいはよいのではないかと思う.

image

アプリはアボートしないでその後も動作可能だが,DLL側で終了時に参照カウントの残留エラーが発生して,複数回エラーパネルが表示される.どうしたらよいか?このパネルはalartprintで出しているものだ.bugflagが立っている場合には表示されないのだが…FORMALVERSIONのときはalartprintを出さないようにすると,上のVBのパネルしか出ないようになる.これはこれでもよいような気はする…

参照カウントの残留エラーはDECOMPOSITIONフェーズで出ているので,これ以下のフェーズではパネルを出さないというようにしてみよう.⇒これでエラーパネルはcouplingとVBで出すものだけになった.最終的な形は分からないが,一応エラーが発生したときの道筋は通ったのではないかと思う.FORMALVERSIONのときのエラー処理は一応目処が立ったが,一つだけ問題がある.

FORMALVERSIONと_DEBUGが共存できない.あるいは,FORMALVERSIONと_RELEASEでもよいのだが,リリースモードではブレークで止めたりなどのことができないので,デバッグに相当な制約がある.問題はこのような設定だと,「warning C4702: 制御が渡らないコードです。」のような警告が出て(警告をエラーとして扱っているため)ビルドが通らないというところにある.リリース版でも状況は同じだと思うのだが,リリース版ではこの警告は出ない.いや,もちろん下記のようなコードでこのエラーが出るというのはわかるけど,なぜそれがリリース版では出ないのか?というところがわからない…※

※理由は簡単,STOPで例外をスローしているのは「公式版」だけだ.

if (gene != gbox->boxgene) {
     STOP; ← ここで例外がスローされる
     gbox->setGene(gene); ← ここには制御が渡らない
}

FORMALVERSIONの場合は,ここでSTOPすると例外をスローするので,次の行に到達できない.FORMALVERSIONでなければ,一旦停止したあと,処理を再開することもできる.実際,いま設置しているSTOP文は軽い気持ちで入れているところは多いので,アプリ実行時に予定していないところでアボートしてしまうということも大いにありそうだ.この意味ではFORMALVERSIONで例外をスローする場所をもっと狭める必要があるのではないか?基本的にASSERTIONで監視している条件は100%致命的なので,ここで例外をスローするというのは当然であり,必要と思われる.それ以外は無視でもよいのではないだろうか?現行ではASSERTIONはどのモードも共通に

#define ASSERT_NEVER(assertion) {if (assertion) STOP }

となっているが,これを

#define ASSERT_NEVER(assertion) {
 if (assertion) {
#ifdef FORMALVERSION
  throw ERR_ABORTPROC;
#else
  STOP
#endif
 }
}

のような感じで書き換えればよいのではないだろうか?それではSTOPでは何をすればよいのか?STOPは元々デバッグ時に止めるという趣旨で入れているのだから無動作でよいはずだ.その代わり,SUWなどの動作はデバッグ時と完全に同じになるから,ShowUnderWearで下着を見られてしまう可能性もある.それはそれでよいのではないだろうか?一般公開とは言えまだしばらくは一部ユーザに使ってもらう段階だし…

逆に言うと,これまでASSERTIONで停止したことはただの一度もないと思われるので,逆にFORMALVERSIONではASSERTIONを完全に無動作にしてしまってもよいのではないかとさえ思えるのだが…高いところで仕事するのに命綱を使うのは悪いアイディアではないが,サーカスで命綱を使ったら笑いものになる…とは言え,サーカスでもブランコの下にはセーフティネットが張られていたかもしれない… マイクロソフトのASSERTIONはリリース版でも作動していたような気もする…

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA