NODULE::operator []で例外が発生した 枝番はSEIZEGROUND

従来論理ではCOUPLING:EraseFamilyTreeでFAMILYTREEの下にあるすべてのスロットをパージしていた.この中にはTOPOLOGYやUNDOSYSTEMも含まれる.EraseFamilyTreeはデータファイルをクローズするたびに実行されているが,実際にはNOPで抜ける動作になっていて終了時に一度だけ実行されるようになっていた.これは関数の趣旨からしておかしいので,EraseFamilyTreeではシステムに1個しか存在しないようなオブジェクトはパージしないように仕様変更し,同時に冒頭で(PHASE <= INITIALSTATE)によって抜ける論理を廃止した.

TITLEBOXはシステムに1個しかないオブジェクトだが,描画オブジェクトはEraseFamilyTreeの中ですべてパージされるようになっているので,その規定に従って削除されるように変更した.この修正は「タイトル枠無しで動作する@20201119」でオプトアウトできる.

TITLEBOXが空でも動作するようになったが,削除されたTITLEBOXをどこかで補充しなくてはならない.どこでそれをやればよいのだろう?ファイルを開いたときどこかでTITLELINKを初期化ないし設定している場所があるはずだから,そこで行えばよいのではないか?TITLELINKクラスのメンバー関数には大したものが入っていない.

いや,その前にFAMILYTREE::InitializeFamilyTreeで(topology->PDB->getmaxrecn() || topology->MDB->getmaxrecn())が起きているという件を先に片付けておこう.これがそもそもの発端だ.EraseFamilyTreeは毎回実行されるようになったが,PDB,MDBが始末されていない.これはTOPOLOGYが管理しているものだ.その直前でTOPOLOGY::initializeが実行されているが,この関数は働いていない.

nodule::operator newのcheck文で(*moto)[edan]をチェックしようとしたところ,NODULE::operator []で例外が発生した.枝番はSEIZEGROUND.⇒SEIZEGROUNDはサポート範囲外.(*goo)[SEIZEGROUND]ではnullを返すようにした.

新規ファイルで起動→サンプルを開いて,COUPLING:CloseFamilyBase→…TREEVIEW::EraseTreeView…→TRIBEBOXのデストラクタで非参照カウントの残留が発生 TRIBELISTのスロット2からの参照が解決していない.bottom2からの参照だ.TRIBEBOXのデストラクタで始末するか,ないしTRIBELISTを先にキャンセルするか,あるいはその両方をやる必要がある.TRIBEBOX::DisposeではTRIBELIST::CleanSansyoを実行している.⇒LIST::CleanSansyoでbottom2をクリーンアップしていない.⇒対処した.

PDB,MDBをクリアする件に関してはEraseFamilyTreeとTOPOLOGY::initializeの両方に責任がある.しかし,TOPOLOGYはPDBとMDBの管理者ではあっても,所有者ではない.所有者であるLINKTABLEには何かチャンスがあるのだろうか?どこかにPDB,MDBを初期化する処理があってしかるべきなのだが… LINKTABLEにはdeleteCardData,DeleteMarriage,DeleteCardなどの個別操作を行う関数はあるが,テーブル全体をクリアするという操作はない.BASETABLEにはcleartableという関数はあるが,これは単純にカウンタをリセットしているだけだ.

CARDTABLEとMARGTABLEはBASETABLEから単純に派生しているので,BASETABLEにClearTableを作るのが早いと思う.CleanSlotで掃除してから,cleartableを実行すればよい.その上で,LINKTABLEでPDBとMDBをClearTable.これをEraseFamilyTreeから呼び出せば片が付く.⇒単純化し過ぎている.ARRAYスロット配列はそれでもよいが,BASETABLEは追加スロットとしてlongtable *lookupというのを持っている.これはカウントをリセットすればそのまま再利用できるが,オブジェクトを削除してしまうとまた,トラブルの元になる.⇒lookupが空になっているときにBASETABLEがどう振る舞うかを見ておこう.

lookupはBASETABLEのコンストラクタで生成されているだけなので,これが空になることは想定されていないと思う.LINKTABLEはMakingLookUpで初めてloopupテーブルを操作する.ここで停止するか,ないし新たに生成するべきだろう.⇒MakingLookUpではlookupテーブルをゼロクリアしてから使っているので,ClearTableでは放置でもよい.この暫定修正でPDBとMDBのクリアはできるようにあったが,(PDB->getmaxrecn() || MDB->getmaxrecn())で停止するという状況は変わらない.これはgetmaxrecnの仕様に問題がある.現行では

long getmaxrecn(void) { return min(maxrefnum, tablesize); }

という仕様になっているが,かなり疑問だ.BASETABLEはmaxrecnというメンバ変数を持っているのだから,素直にこれを返せばよいのではないかと思うのだが… ⇒同じ轍を踏まないように注意した方がよい.「その場の思いつき命取り」が怖い.明らかにこのロジックではレコード番号と参照番号という2つの番号を扱っている.どちらもテーブルのインデックスだが,おそらくレコード番号は連番で参照番号というのは飛び番になっているのではないかと思う.通常,getmaxrecnとmaxrecnの値は一致していると考えているが,何かの理由でそうなっていないのではないか?そもそも,lookupテーブルというのはどんな必要があって使われることになったのか?どういう使われ方をしているのか?⇒これは一覧表出力に関係している.

いずれにしても,上記getmaxrecnの定義は正しいと考えるしかない.従って,必要なことはcleartableでmaxrefnumをリセットすることだ.⇒これで一応動作するようになったが,lookupを残すように修正しておこう.BASETABLE::ClearTableで一律CleanSlotではなく,

ARRAY<bnum>::CleanSlot

のようにスロット配列だけのクリアに変更する.これで大体収まった.BASETABLE::lookupをMakingLookUpで復活させたように,TITLEBOXにも再生処理を作ってみよう.⇒TILELINK::gettitleboxは描画フェーズでTREEVIEW::Refreshから呼び出される他は,COUPLING::SetTitleBoxからの呼び出ししかない.従って,ここで再生するというのが最適と思われる.TITLEBOXを必要としているのは,TREEVIEWなので,TREEVIEW::gettitleboxで生成というのでよいのではないだろうか?⇒いや,TITLEBOXはTITLELINKのコンストラクタで生成されているので,やはり,TITLELINKの位置で生成するのがよいと思う.事前にTITLELINKが呼び出されるようなタイミングがあればよいのだが,どうも見当たらない,ということになればTILELINK:gettitleboxで生成するしかないだろう.

TREEVIEW::InitTreeViewというのがある.これは毎回呼び出されているのではないか?OpenFamilyTreeではInitCouplingとInitializeFamilyTreeを実行している.InitTreeViewはInitCouplingの中で実行される.TREEVIEWはCOUPLINGの所有物だ.⇒いや,おかしい.いつ入れたのだろう.TREEVIEW::InitTreeViewの中にはすでにその処理が組み込まれている.だとしたら,「タイトル枠無しで動作する@20201119」というオプションは元々不要だったということになるのだが… しかし,何らかの必要があっての修正だったはずだ… 例外をスローして初めて発現する障害だった可能性もある.

FORMALVERSIONのテストなのでまず,その設定に戻してみよう.⇒何も障害が発生していないのに,新規ファイルを開いてTITLEBOXが空になっている.これはかなりおかしい.TREEVIEW::InitTreeViewで生成されたTITLEBOXはどこに消えてしまっているのだろう?⇒タイトル枠設定で「タイトル枠を表示」をONにできない.それ以外のパラメータもすべて変更できない状態になっている.

▲★ASSERT_NEVER★ PHASE=22 funcname=nodule::Connect scount=447534 line=591, filename=nodule.cpp MakePairListを実行中,RepairPairBoxでノード対のチャンネル移動を実施してtakeout→ nodl_float→ Connectで(moto == nodl)が発生している motoとnodlはともに空

「増設スロット(EXTRA_ANCHOR)の子ノード」を親ノードに接続し,ゼロスロットノードをそのノードのゼロスロット末尾に移動しようとしている.ノード対リストではノード対を管理するために増設スロットを使っている.(EXTRA_ANCHOR)は①端点共有ノード対接続チェーンと②SymmetricActionでブロック移動実行時に使われるリストを一時的に接続するために使われている.どちらも参照ではなく接続チェーンで,①の場合はPAIRBOX,②の場合はnlist<Bobject>が接続される.

nodule::nodl_floatの論理は明らかに誤っている.というか混乱している.混乱しているのは「増設スロットの仕様」だ.増設スロットは一時的にある特定クラスのオブジェクトがそのグループ内で使うものだから,「どう使われてもよい」というようなところがあったのではないだろうか?どちらも「接続」だが,接続のトポロジーは異なる.

ノード対の場合は増設スロットを使ったチェーンになっているが,SymmetricActionの場合は増設スロットに接続されるのはnlistでその下にゼロスロットを使ったリストがぶら下がるという構成になっている.従って,nodl_floatではこれらを一律に扱うことができないばかりではなく,ノード対の場合には接続に使うスロットをゼロスロットと誤認している.自ノードのXスロットに接続するノードは前方ノードのXスロットに接続し,自ノードのZスロットに接続するノードは前方ノードのZリスト末尾に接続するというのが原則だ.

nodl_floatは全面的に書き改めるしかない.⇒修正完了したが,スタックオーバフローが起きている.完全参照リスト管理の操作を誤解していたかもしれない.いや,いまの修正は参照リスト管理とは関わりがないはずなのだが… どうもどこか完全に壊してしまったようだ…

例外処理を再整備する

条件コンパイル定義式の中にはASSERTIONマクロが含まれる.ASSERTIONは通常(つねにとは限らない)関数の入口に置かれて,引数の整合性などをチェックする一種の関門だ.ゼルコバの木ではASSERT_NEVERとDEBUG_NEVERという2つのマクロを使っている.マイクロソフトのASSERTIONは肯定型の言明で「かならず~であること」を確認するようになっているが,我々の場合には「決して~ではない」ということを検証する※.この違いは欧米的な意識と日本的意識の相違なのではないかという気がする.欧米感覚では「かくあるべき」という規範意識が強いような気がするが,日本的感覚だと,「こういうことは許されない」がそれ以外に関しては言及しない(なんでもよい).

※ASSERTIONとASSERT_NEVERは相互に転換可能だ.ASSERT(X) という言明は ASSERT NEVER EVER(~X)と等価である.

ASSERT_NEVERもDEBUG_NEVERもどちらも決して起きてはならないが,論理的にはまず起こらないと推定されるものだ.しかし,プログラムでは何が起こるか分からないので一次防禦線として張られている.DEBUG_NEVERはデバッグ時にのみ実行される内部検査で,仮に起きたとしても軽微なエラーとしてそのまま続行可能,ASSERT_NEVERは続行不能な致命的障害を意味する.ASSERT_NEVERはリリース版でもアクティブになっているので,事後処理つまり,例外処理が不可欠だ.例外処理はどうしても手抜きになってしまうところだが,いずれ整備しなくてはならない.旧版では「バグレポートの提出要請」という形で完全な例外処理が実装されていた.現行でも一部まだその機構は残っているが,これを再整備する必要がある.

昨日の終業間近に出ていたメモリアクセス違反を見ておこう.MARGBOX::setGenerationで例外をスローした後,新規ファイル生成不可→アプリ終了しようとしてGP例外が場外で発生する.ASSERT_NEVERが2回スローされているが,これは後で見ることにして… いや,2回どころか数回発生している.最後はCARDLINK:CleanSansyoで発出されたもので,例外はここで起きているものと見られる.ビルドはReleaseで,FORMALVERSION.Debugモードで再現できるかどうかを見てみよう.⇒GP例外は発生しない.その代わり,

Debug Error!
Program: D:\ZELKOVA\debug\ZelkovaTree2021.exe
abort() has been called

のような表示が出ている.「Debug Error!」という語句はゼルコバの木のものではないので,VSのデバッガが出しているのだろう.これでは手がかりにならないので,cleansansyo.cppの238行目からトレースしてみる.⇒~COUPLING→ ~FAMILYTREE→ ~CARDTABLE→ ~ARRAY→ CleenSlot→ ~CARLINK→ CleanSlot→ clearslot→ CleanSansyo のように進んだところで,RETURNIFCLEAN というマクロによってアボートしている.

#define RETURNIFCLEAN \
     ASSERT_NEVER (object == this && !(checkcid(‘n’) && ((NAMEBOX*)object)->getghostbits(SYMMETRICMARG|REALGHOST) == (SYMMETRICMARG|REALGHOST))) \
     if (!object || !object->Refcount()) return 0;

このマクロは各クラスのCleanSansyoの冒頭で実行されている.このマクロの趣旨は「objectが空かobjectの被参照カウントがゼロならゼロ復帰する」という妥当なものだが,その前に実行しているマクロが曲者だ.これは実行順序が逆だと思う.⇒修正したが,解決しない.⇒ASSERT_NEVERが成立して例外をスローすることでアボートしている.おそらく,VBが「プログラムを終了します」を出した時点でアプリ終了になっているはずだ.しかし,DLLには特に通知を送っていないので,それを検知することはできない.

いや,認識しているのではないか?PHASEはENDOFAPPLICATIONという状態になっている.~COUPLINGが実行されているのがその証拠だ.⇒ENDOFAPPLICATIONフェーズでは例外をスローしないとしておこう.確かに,それしか方法がないが,ではENDOFAPPLICATIONのときにはどうしたらよいのか?そのまま続行できないとすれば,リターンする以外ないが戻り値は各関数によってまちまちだ.⇒return 0; としてみたところ,void型の関数から一斉にクレームがあがった.

_ASSERT_NEVERというのがあるが,これはどういう用途で作られたものだろう?⇒デバッグモードではSTOPするか,_STOPかの違いだ.つまり,停止するか?警告パネルを出すかの違いだ.非公式版ではどちらも警告パネルを出すようになっている._ASSERT_NEVERを使っている事例を見ると主要なコンポーネントが存在することを確認するような使い方になっているので,これらを区別する必要性はあまりないような気がする.⇒_ASSERT_NEVERをvoid型で使うマクロとして再定義するというのでよいのではないか?やってみよう.

_ASSERT_NEVERが141個あった.⇒「コンストラクタは値を返せません」というのまで出てきた.確かにコンストラクタの中でASSERTIONするというのは自己矛盾であるような気もする.「オレは誰だ~」と言っているようなものだ.コンストラクタはそれ自身で完結しなくてはならない.⇒DEBUG_NEVERなら許されるだろう.⇒整数値以外を返す関数もある→3個.⇒付け替え完了し,ノーマルに終了できる._ASSERT_NEVERは定義を含めて367箇所,ASSERT_NEVERは2114箇所ある.Releaseビルドしたら漏れが見つかって,_ASSERT_NEVERが406個,ASSERT_NEVERが2075個に変わった.合計2481.

ReleaseビルドのFORMALVERSIONを起動して冒頭で例外が発生した TRASHCAN:MakeTrashCanの中で起きているように思われる

Debugビルドでは問題なく立ち上がってくるので,ASSERTIONの修正が関係している可能性が高い.「_DEBUGマクロを未定義にする」でどうなるか見てみよう.原因は分かった.コンストラクタの中で実行しているASSERT_NEVERをすべてDEBUG_NEVERに変えてしまったからだ.これらの中には単なる検査ではなく,

new (this, COUPLsKAKEIZU) KAKEIZU

のように実動作を伴うものがある.このような場合にはDEBUG_NEVERではなく,CONFIRM_NEVERを使わなくてはならない.TITLELINKでも同様のことが起きている.しまった.どのクラスのコンストラクタを修正したかメモっていない.以下のようなエラーが出ているので,これを片付けないと前に進めない.

image

ゼルコバの木で使っているクラスは49種ある.それを一つづつチェックするよりは,むしろ,DEBUG_NEVERを検索してしまった方が早い.DEBUG_NEVERは372個あるが,マクロの引数をチェックすれば修正が必要か否かはすぐに分かる.というか,このような誤りは別の場所にも存在する可能性があるので,全点検査はいずれにしても必要だ.DEBUG_NEVER (CheckFreeDirectSubTribe(funcname))のように検査関数を(検査モードで)呼び出すだけのものは見逃してもよい.しかし,これはかなりまずいのではないか?

DEBUG_NEVER (getkukan(primenod, realnode, true).Width())

getkukanの戻り値はBRectだ.いや,違う.この条件式はその矩形の幅がゼロか否かを見ている.⇒全点検査した.問題ないように思われる.⇒動作するようになった.上の不良はランニング中にソースコードを編集して続行したためではないかと思われる.⇒アプリを終了しようとしているのに,FAMILYTREEでInitializeFamilyTreeを実行しようとしているのはなぜか?このとき,

(topology->PDB->getmaxrecn() || topology->MDB->getmaxrecn())

が起きてASSERT_NEVERが再発動する.InitializeFamilyTreeの実行はアプリ終了後ではない.起動→新規ファイル→サンプルオープン→例外発生→検定失敗→新規ファイルオープンで実行される.しかし,起動時に新規ファイルが開けているのに,障害発生後にそれができなくなるというのもおかしい.ファイルのクローズを実行していないのではないか?⇒いや,実行されている.しかし,テーブルは空になっていない.

COUPLING::EraseFamilyTreeではフェーズがINITIALSTATE以下では入口で復帰している.これを廃止すればテーブルは空になるが,別のところでエラーが発生するようになる.Coupling::OpenFamilyBaseでpartialmap不在が発生する.COUPLING::OpenFamilyBaseではpartialmapが存在するものと仮定している.PARTIALNAMEはどこで誰が生成しているのか?FAMITREEの所有で,FAMILYTREE:InitializeFamilyTreeで生成され,FAMILYTREE:Disposeで削除される.TOPOLOGYも同様だ.CARDTABLEとMARGTABLEはLINKTABLEのコンストラクタで生成されている.

それではどこでPARTIALNAMEは消されているのだろう?⇒COUPLING:EraseFamilyTreeでFAMILYTREEのすべてのスロットをCleanSlotしている.COUPLINGがPARTIALNAMEを必要としているのは,ファイルの読み込み時に使っているからだ.部分図データはファイルのヘッダ部に保存され,PARTIALNAMEに直接ロードされる.それだけではない.TOPOLOGYもUNDOSYSTEMも,EraseFamilyTreeで消去しているほとんどすべてのオブジェクトが必要だ.ということは実質,EraseFamilyTreeは終了時以外は実行されていないということになる.呼び出しているフリはしているが,

if (PHASE <= INITIALSTATE) return;

でほぼ常時無動作でリターンしている.不要なときに空動作で戻るのはよいとしても,必要なときに動作しないのではまずい.見た限りではPARTIALMAPやTOPOLOGYは初回生成された場所以外では再生成されないので,EraseFamilyTreeで消去するというのは不当であるように思われる.これらのオブジェクトを消去しないという仮修正を入れて動作を見てみることにする.⇒かなりの修正が必要になったが,最後にTITLEBOXが存在しないという問題が残った.TITLEBOXはNAMEBOXやMARGBOXと同格の描画オブジェクトだが,かなり特殊な扱いになっていて,TREEVIEW直下のTITLELINKというのが管理している.

EraseFamilyTreeでは一旦すべての描画オブジェクトをパージしてしまうので,その中にTITLEBOXが含まれているため,TITLEBOX不在が至るところで発生することになる.⇒この件に関してはむしろ,まずタイトル枠が存在しない状態でも動作するように仕様変更した方がよいと思う.タイトル枠の仕様はまだ,今後も変化する可能性があるのでそのオブジェクトが一つ欠けているだけでシステムが停止してしまうというのはあまりよい仕様ではない.⇒一応エラーなしに動作するようになったが,画面が真っ白のままだ.メニューなどは操作できるので,システム的には動作しているようだが,画面上に何も描画されていないことは拡張選択しても何も選択できないことからも間違いない.

原因は分かった.COUPLING:SetTitleBox→ TITLEBOX:SetDispParmを呼び出していない.この中にはTREEVIEWの計算が入っている.TREEVIEWに関わる部分を切り出して別途実行する必要がある.系図外枠の計算を行っているTREEVIEW::GetTitlePositionの修正も必要だ.⇒一通り修正を入れたつもりだが,画面が出ない…タイトル枠を表示しないというモードがあるくらいなのだから,タイトル枠がなければ描画できないということはないはずだ.

!描画できた!

また条件コンパイルマクロの集計が合わなくなった

comdebug.hでCOMDEBUG: タグ付けの誤りが見つかったので修正した.これでまた条件コンパイル定義文の集計が合わなくなった.原因を調べる必要がある.正規表現で値なし定義文を検索した結果は131件で変わらない.COMDEBUG: は1個減って27個になっている.どこかで間違えているはずだ.⇒昨日はフィルタリングした状態では連続データの入力(ナンバリング)ができなかったが,メニューから実行する方法がある.また英語版に戻しているが,Edit→Fill→Seriesが使える.ただし,実行する前に複数セルを選択しておかないとコマンドが出てこない.⇒以下の手順で簡単にチェックできる.

  1. VSで値なし定義文を正規表現を使って検索し,テーブルに出力
  2. (1)をOpenOfficeに読み込み,COMDEBUG: でフィルタリング
  3. VSでCOMDEBUG:で全文検索し,テーブルに出力
  4. (3)を(2)の後ろに貼り込み
  5. (4)をソートして,ダブっていない行を探す

いや,おかしい.COMDEBUG:で検索した結果も27で合っている.上の手順で比較しても完全に一致している.ということは別のタグで不一致ないし増減が起きていると考えるしかない.調べてみよう.SPECIFICATION:は16で昨日と同じだ.OPTIONS:(11),DEBUG:(23),VERIFY:(5),TEST:(11),LOCAL:(10),_H_ (27)ですべて同じだ.トータル130で1個過不足がある.つまり,#define文の方が多いということになる.これを見つけるのは容易だ.タグがついてないものを探せばよい.あった.HOSTCMD.H冒頭のマクロだ.

#if !defined(__HOSTCMD_)
#define    __HOSTCMD_

これは,

#if !defined(__HOSTCMD_H_)
#define    __HOSTCMD_H_

のように書かれなくてはならない.ヘッダファイル冒頭のマクロは以下の式で正規表現のオプションで検索している.

検索:#define.*_H_         (H)ヘッダファイル冒頭の定義文を検索

これで_H_の個数は28となり,トータル131で完全に一致した.昨日の数字が合っていたのも,単なる偶然ということになる.やれやれ…

ダメだ.まだ合っていない.上記の検索:#define.*_H_の行が検索結果に含まれてしまっている.これを除くと27個,まだ総数ではあっていない.この行がヒットしないように検索キーを次のように変えてみた.

検索:#define\s+.*_H_   (H)ヘッダファイル冒頭の定義文を検索

これで件数は27という正しい数字になったが,タグ付きの総数は130で定義文の個数131と合わない.ヘッダファイル定義のミスがもう1件あった.Lineage.hで_LINEAGE_Hになっている.⇒これで完全に一致した._H_の個数は28が正しい.

さて,「制御が渡らないコードです」の問題を片付けなくてはならない.昨日のログでは,後付けで「※理由は簡単,STOPで例外をスローしているのは「公式版」だけだ」としているが,リリースモードでFORMALVERSIONとしてもこの警告は表示されない.リリースモードとデバッグモードでは警告の出方が多少異なるので「そんなものか」とも思うが,普通はリリースモードの方が厳格になっているのに,その逆というのが納得いかないところだ.

警告レベルを下げるなどの対処法は芳しくないので,昨日述べたように,例外をスローするのはASSERTIONに限定することにする.⇒実装してみたが,悪くないように思われる.Stopはどうだろう?デバッグモードではSTOPと同じ効果だ.内部でSTOPを呼び出している.Stopは引数で文字列を渡せるので,STOPより詳細な情報を得ることができる.公式版の場合はデバッグ時と同様の動作でよいのではないか?Stopは関数でマクロではない.どこかでデータ退避.ZELへの書き込みが発生している.⇒呼び出されてはいるがパスしている.SAVERESCUEFILEがデバッグ時しか有効でないためだ.なぜだろう?理解できない.

公式版ではalartprintでパネルを表示しないようにしたので,STOPと_STOPを公式版だけ別扱いする必要はなくなった.この2つは共通定義でよいのではないか?⇒いや,そういう訳にはゆかない.デバッグモードでは停止しなくてはならない.⇒STOP,_STOP,Stop の動作はまずまずなのではないかと思う.デバッグモードではある程度情報を取得できるようにした.ただし,「_DEBUGマクロを未定義にする@20170811」ではほとんど何の情報も得られない.アプリは停止しないのでまるで問題なく走っているかのようだ.

ASSERT_NEVERを試してみた.なんとあの懐かしいパネルが飛び出してきた.標的型攻撃を受けてお払い箱になっていたあのカマキリだ.

image

しかし,このあとが相当悪い.パネルを開くと同時にユーザ会サイトのバグレポートにアクセスして「インターネットに接続されていません」になるのはよいとしても,パネルを閉じた後にSTOPパネルを2回開いたあと,以下のパネルが順番に出る.

image

image

image

image

image

新規ファイルのオープンに失敗した場合には打つ手がないのでアボートするというのでよいと思うので,新規ファイルだけは作れるような場所で例外が発生するようにしてみよう.PARTIALNAME:PartialMapCommandに例外を仕掛けてみたが,パスが通っていないようだ.どこかで握りつぶされている.mmm…今度はパネル1枚出さないでアプリ終了してしまった.この例外は全くトラップされずにストレートにGCまで戻ってしまっているようだ.

上の現象は再現できない.確かにmZelkova::mPartialMapCommandにはエラートラップが仕掛けられていない.GCでは61個の関数にエラートラップが設置してあったのだが,肝心なところが抜けていた.エラーを返してやっても以下のパネルが出るだけだ.

image

これはVB側の判断でエラーで帰るとこのパネルを出して抜けるようになっている.ここでは,⇒「検定に失敗しました」を出してもらいたいところなのだが…検定に失敗しましたは,InitializeDisplayで出しているものだ.系統並び替え実行後はかならずここを通るので,ここで「検定に失敗しました」を出すのは妥当だろう.ユーザは何度も「部分図タイトルを選択してください」が出れば,おかしいと思って問い合わせしてくるかもしれない.しかし,その前にユーザにASSERTIONで失敗したということが伝えられていなければならない.

GCではエラーをキャッチしてはいるが,エラーの種別には無頓着なのでエラーコードを取り出すこともできない.これはやはりDLL側で受けるしかないのではないだろうか?GCとDLLを接続する関数は1対1になっているので,対応するにはすべての場所で個別対応するしかないが,幸い部分図はコマンド処理になっていてすべての操作が一つの関数を通るようになっているので,対応は容易だ.

PARTIALNAME::PartialMapCommandの後半部,実際にコマンドを実行している部分にはすでにトラップは仕掛けられているので,前半部にだけ掛けてみよう.おかしい.なぜだろう?キャッチできない.どこかでSTOPしていることは確かだが,FORMALVERSIONなので停止しない.⇒エラートラップにSTOPが置いてあった.

ここでもう一度例外をスローしてそれがGCに入ってくる.例外をそのまま転送するというのはそれでよいとしても,ここで何らかの処理をしないと何が起きているのか分からない.一番早いのはASSERT_NEVERで直接処理することだろう.ASSERT_NEVERの内部で処理するとしたら,マクロではなく関数化した方がよいのではないだろうか?しかし,関数化してしまうと,障害が発生した場所が分からなくなる.関数名くらいなら渡してももよいが…⇒マクロはマクロとして残して内部処理の部分を関数化しておくというのが扱い易いのではないか?情報はマクロから関数の引数で渡してやればよい.⇒それがよいもしれない.

ERR_ABORTEXPERIMENTというのがすでにある.何をやっているのか見てみよう.⇒こんなことがあるのではないかと思っていたが,やっぱりエラーコードのダブリがあった.ERR_CARDTABLEOVERとERR_SHOWUNDERWEARがかち合っていた.エラーコードは一応カテゴライズされているのだが,なぜか大きい番号があちこちに散らばっている.⇒ERR_SHOWUNDERWEAR,ERR_ABNORMALGENEGAP,ERR_ABORTEXPERIMENTなどをキャッチしたときには,例外を先送りしている.ERR_ABORTEXPERIMENTをキャッチしている2箇所で,ERR_ABORTPROCも同じ扱いになるようにした.

PartialMapCommandの前半と後半の2つのトラップは一つにまとめてよいと思う.一応の段取りはできたが,最初のエラーパネルが多重に出てくる状態を再現できるだろうか?例外を部分図のロードに仕掛ける前だから,MakeUpTreeかないしその前と思われる.⇒場所が見つからない.上記でエラートラップ2箇所では例外を先送りするように修正しているので,処理されているのかもしれない.⇒少なくとも処理されずにゼロ復帰している箇所はある.すべてのエラートラップで例外を先送りするようにしておく必要がある.catchで検索すると207箇所もある.整数型以外のエラーをキャッチした場合には警告パネルを出すようになっているので,整数型だけ見ればよいと思う.

THROWFATALERRORというマクロを作って,ERR_SHOWUNDERWEAR,ERR_ABNORMALGENEGAP,ERR_NOMEMORY,ERR_ABORTEXPERIMENT,ERR_ABORTPROCでは例外を先送りするようにしてみよう.整数型エラーをキャッチしているところは74箇所ある.⇒TRASHCAN::throwCanのようにすべての例外を投げ出しているところはそのままとする.⇒対応修正完了した.

▲potential.cppの707行目で例外を発生させるコードをリリース版で走らせると,COUPLING::TopologicalSortのエラー→検定に失敗しました→新規ファイルのオープンに失敗しました→プログラムを終了しますの後にどこか不明の地点で例外が発生する.

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

条件コンパイル文はプログラム開発の分岐点に相当する.この分岐点がゼロ個になった状態が「正式版」であるとして,その個数を削減する作業を進めてきた.「再開発スタート版」の時点で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はリリース版でも作動していたような気もする…

エクスプローラでOpen Live Writerの全文検索ができない

ログの作成・投稿に使っているOpen Live Writerをストアアプリからデスクトップ版に切り替えた.ストアアプリではOpen Live Writerをインストールしたフォルダを見つけることができない.マイクロソフトではストアアプリの方を売り込みたいらしく,Open Live Writerのウェブサイトは閉鎖されてしまっているが,ようやくGitHubにある Open Live Writer の現物を見つけた. EXEのあるフォルダが分からないとファイルの拡張子とアプリケーションをリンクすることができない.また,データファイルのデフォルトアプリが決まっていないと,エクスプローラでコンテンツ検索ができないことになっている.

いままでOpen Live Writerを重用してきたのは,過去記事の全文検索がローカルで可能だったからなので,もしそれができないとなればOpen Live Writerを使うメリットは半減してしまう.デスクトップ版のOLWをインストールして,*.wpostをOLWとリンクし,ファイルのアイコンを叩けばOpen Live Writer が起動するところまではできたのだが,全文検索がどうしてもできない.かなり悩んでいる.Meryなどのエディタには全文検索機能(grep)が付いているが,これは対象ファイルがプレーンテキストでないと動作しないので,まったく使えない.

現行の系列再配置処理はかなり場当たりに作られているので相当冗長な部分があるかもしれない.ファインチューニングすれば倍速くらいまでは向上できそうな感触があるが,しばらくは現状で進むしかないだろう.「純血統図をサポートし上流が養親系だけの場合の下流検定の動作を保証する」というところがいまいち分明でないので,もう少し詰めてみる.これが片付くとnodule.hで預かっているすべてのオプションが片付いたことになり,あとはBobject.hが抱えているDEBUGとVERIFYタグの項目を点検するだけになる.かなり整理されてきた.

オプションPUREBLOODSUPPORTではTOPOLOGY::getkinshipDegreeで以下のようなことを行っている.

  1. 基準ノードの親数をカウントする 
  2. もし,親数がゼロでなければ,先祖ノードを列挙し,先祖ノードを起点に親等計算を行う ただし先祖ノード不在で親数1の場合は基準ノードを起点に親等計算を行う
  3. 親数ゼロの場合は基準ノードを起点に親等計算を行う

PUREBLOODSUPPORTがONのときとOFFのときの違いは,OFFでは(1)を実行していないこと,(2)で先祖ノード不在の場合を見ていないこと,(3)の親数ゼロのケースが存在しないことが挙げられる.しかし,このようなことは純血統図でなくても起こり得ることであり,もしそれが処理できていなければ従来論理は誤っているということになる.オプションをOFFにして動作を確認してみよう.⇒特に問題はないように見える.先祖ノードからの下流検定ループで先祖ノードが空になった場合には必ず基準ノードからの下流検定を実施するようになっているから,漏れが発生するおそれはないように思われる.

この修正が導入された事情を知りたいので過去ログを探してみた.純血統図が導入されたのは2014年の3月頃と思われるが,結構てこずっていたようだ.PUREBLOODSUPPORTに直接該当する記事は見つからなかったが,3月20日の記事で,

「親等計算はつねに全域的に計算し,どこで計算しても同じ値になるべきであると考えられるから,親等計算で養親系を登らないようにするというのは多少問題があるようにも感じる.上記のような検定漏れが生じることを防止するのならむしろTABLESORT::getkinshipDegreeで対処した方がよいのではないかとも思われる.⇒その方向で再修正した.」

とあるのが近いのではないかと思う.「親等計算はつねに全域的に計算し,どこで計算しても同じ値になるべきである」とするのなら,一番確実な計算方法は親族範囲によってカードの有効・無効を決定する前に全体図上で親等計算を実施するというのが解ではないかと思う.親等計算は基準ノードさえ決まればあとは自動的に計算可能だから,系統並び替えを実施する前に実施することができる.この方式なら「親等不一致」は原理的に起こり得ない.しかし,純血統図の場合などはむしろ養親系を除外したときの数字が欲しいのではないだろうか?

親等計算をどこで実施するか?については再考の余地があると思う.PUREBLOODSUPPORTに関しては棄却とするか,ないしDEFECTEDではないだろうか?⇒一旦棄却でフィックスしてしまおう.

そればかりではなく,TOPOLOGY::getkinshipDegreeで先祖ノードが空のとき実行している基準ノードからの親等計算も不要と思われる.実際,モジュール構成図 TEST2の全体図テストではこの論理を完全に止めても問題なく動作している.現行ではsearchForefatherで先祖ノードが空の場合には基準ノードを返しているためと思われる.⇒例外が発生した場合には停止するように仕掛けてしばらく仮修正で運用する.

▲親等計算論理の解析中,ゼルコバの木モジュール構成図 TEST2の全体図テストで TEST2.ZELをノード=21 UndoChainのとき,PAIRBOX::repairCommonEndPointの出口で (CheckSamePoint())により停止した.反例サンプルは作られていない.反例を取れるようにしたが,再現しない.⇒リリース版だからではないか?確かにIDE上では停止する.反例サンプルはBUG20-11-16 16-50-27.ZEL.これは後で見ることにしよう.

DEBUG_NEVERでも反例を出力した方がよい.⇒対処した.⇒ただし,起動するたびに反例サンプルが生成されるというのは流石に過剰だ.パネルを出して,「保存」でファイルを生成というのがよいかも…

おかしい.66点サンプルの全体図テストが6面で終わってしまう.⇒一覧表上で現在選択されているカードから最後までをテストするようになっているようだ.⇒「仕様」ということにしておこう.

Bobject.hに置いてあるDEBGU: とVERIFY: タグのオプションの整理に掛かろう.すでにTRIBEBOXではシフト禁止リストというのを廃止しているのに,NAMEBOXにはSHIFTPROHIBITEDという属性が残っている.これは使われているのだろうか?シフト禁止は主に長い尻尾がオーバーシュートした場合の回数をカウントして上限を超えた場合にはシフト禁止にするなどの動作になっているが,長い尻尾長オーバーなどの事象はほぼ根絶されたものと思われる.ただし,カードシフト操作自体はSHIFTPROHIBITEDのステージ【5.1】絶対世代番号に基づいてカードシフトでShiftDirectAbsoluteとして実行されている.おそらく発現する可能性はゼロと思われるが,とりあえず現行のままとする.

まず最初にデフォルトでONになっている項目をチェックしておきたい.考え方としてはDEBUGはすべてデフォルトOFF,VERIFYはすべてデフォルトONというのがノーマルな状態と思うので,まず,DEBUGにあるデフォルトONから見ることにしよう.たとえば,NOSEGMENTSILENT(セグメント値ゼロでダンプしない)というのがある.この反対は「セグメント値ゼロでダンプ」だが,これならDEBUG: になる.「セグメント値ゼロで停止」ならVERIFYになるだろう.これはやはり正論理に改めておいた方がよいのではないか?

これに関連するブール値として,NODISPNOSEGMENT // セグメント値を持たないノードは描画しないというのがある.しかし,この値はFALSEに固定で実際には作動していない.過去の遺物と言ってもよい代物だが…セグメント値ゼロで描画まで来るということはまずないと考えられるから,この辺りはコードから一掃してもよいのではないか?余分なコードがなくなるとプログラムもかなりメンテし易くなる.NODISPNOSEGMENTとNODISPNOSEGMENT はともに廃止とする.

属性値は大文字で表記することが多いので,属性を返す関数も大文字表記にするとわかり易いのでないか?たとえば,IsElderWifeならISELDERWIFEとする.ただし,このような関数は副作用を持ってはならない.⇒関数型プログラミングに向かう方向だ.

INVESTIGATEVSPLITS(検出した垂直スプリットを画面に表示する)は廃止でよいはずだ.垂直セグメント検定はすでに廃止されているのだから…いや,垂直検定とは別に「全域垂直スプリット検査」というのがある.おそらくこれが作動することはないと思われるが…水平スプリット検査にはINVESTIGATEHSPLITSというのがある.これらはVERIFYでよいのではないか?「残留参照カウントの警告パネルを表示する」も監視という意味ではVERIFYだろう.

PRINTPARAMETER(人名枠にノード属性を表示する)というのはデフォルトでONということもないが,デバッグ時はつねに作動しておくものなのかもしれない.VERIFYに置くデフォルトでONの項目も通常はデバッグ時限定と考えられるので,_DEBUGで囲んでおけばよい.

DRAWTRIBEGENEBOX(世代枠矩形領域を描画する)は現状でフィックスでよいのではないか?系図画面設定パネルで切り替えできるようになっているが,これがOFFでは表示できないし,系列枠にはそのようなものはない.いや,これはオプションかも知れない.ONではGENEBOX::Drawを呼び出すようになっているが,OFFではXORで矩形領域を塗りつぶすようになっている.

世代枠反転色というのを試して,何も出なかったので廃止と決めたのだが,確かに作動はしている.ただし,なにかバグがあるらしく,米粒くらいにしか見えない.⇒これもなにかの役に立つ可能性はあるので温存しておこう.どこに置けばよいか?設定パネルでは「世代枠反転色」とあるので,もともとはこれが仕様だったのだろう.「背景色」としないで「反転色」としたのは,ユーザがバグと間違えないようにというつもりだったのだろう.⇒世代枠が表示されないのは矩形領域に値が入っていないためかもしれない.⇒とりあえず,OPTIONSに移しておこう.系列枠を描画すると上書きされてしまうのは欠陥だ.もし,ONでリリースするのなら,反転色を背景色に改めなくてはならない.

「WriteNonStopSampleListを一時停止@20161026」がデフォルトでONというのも芳しくない.デバッグ用のログ出力などはほとんどフラグを使って操作しているので,その方式に改めた方がよい.⇒WriteNonStopTestLogというフラグを新設した.DEBUG: には現在デフォルトOFFの項目が38個ある.このうちのDEBUG:で始まる11個はすべて廃止してcheck文の中に押し込むことにする.⇒片付いた.

「CheckWasteCountを呼ぶ」というのがある.ゴミ箱の中身をダンプするというものだと思うが,むしろ,呼び出し側ではなく,OFFのときはCheckWasteCountから無動作で戻るという動作の方がすっきりする.「CheckWasteCountを実行する」に改めておこう.

「端点共有ノード対をカラー表示」というのがある.ちょうど今その反例サンプルが手元にあるので試してみよう.⇒BUG20-11-16 16-50-27.ZELを開いてみたが,特に色の付いている線分は見当たらない.GREENないしBLUEで塗ることになっているのだが…解消してしまっているのだろうか?端点共有というのはノード対の属性なので,これがリセットされているということは解消しているということではないか?

「ノード対区間ゼロで端点共有不可」というのと「端点一致でsamepoitゼロ」というのが起きている.ただし,前者はエラーにはなっていない.障害は[ノード対]:#634:#308 MDB(1)→#191 MDB(0)で起きている.MDBは親を3組持っている.topologyとlinktable, namesortだ.

image

左から入ってくるnamesort→MDB(イエロー枠)と直上から下がるlinktable→MDBはわかり易いが,確かに右から入ってくるtopology→MDBを識別するのは難しい.右隣りのPDBも同じように親を3組持っているが,こちらははっきりと識別できる.「端点一致でsamepoitゼロ」というのが何を意味するのかよくわからないが,多少不都合であることだけは確かだ.それにしても,「端点共有属性」をどこで落としているのかが問題だ.⇒これは,後で見ることにする.

「相対座標系から絶対座標系への変換結果をチェックする」というのがある.これは絶対座標系に変換する前にあらかじめTREEVIEWとの原点距離から測定した絶対距離を保存しておいて変換後に比較するというものだ.異同があれば,停止するようになっている.使うチャンスがあるかどうかは別として置いておくでよいだろう.

「選択人名枠の背景色描画でXORを使わない」というのがある.どんなことになるのかやってみよう.⇒これはかなりひどいことになる.一度クリックすると元に戻らない.

image

XORで描画する以外の方法があるのかないのか分からないが,これは現状でフィックスでよいのではないだろうか?あるいはSPECIFICATIONに移動することもできるが…単に矩形枠を塗り潰しているだけなので捨ててもよいと思う.「画面中央不動点をカラー表示する」というのもある.最近この必要性を感じていたところだ.すでに試みていたとは…その割には成功していないような感じもするが…

「HasMultiOccurrence:隠蔽カードを検出する」というのは,前にも出てきていたような気がするが…⇒いや,これはどこからも参照されていない.すでに廃止されていると思う.

DISREGARDLIMITSIZEOVER(描画矩形領域オーバーフローを無視する)これは仕様的にまずいと思う.オーバーフローを無視するにしても対処するにしても,オーバーフローが発生していることを検知しなければ始まらない.現状では「描画矩形領域オーバーフローを無視する」ONにすると検査自体を実行しないことになってしまう.

これは表現を変えて,「描画矩形領域サイズオーバーを監視する」CHECKDRAWSIZEOVERとしてデフォルトONでSPECIFICATIONに移すことにする.⇒これでDUMP: タグの中はすべてデフォルトOFFになった.23件になった.あとはVERIFYだけだ.こちらは,デフォルトOFFから見てゆくことにしよう.

PRINTABNORMALGENEGAPはcheck文で代替できる.

TOTALCONFLICTION,ALLOWMARGGAP,UPSTREAMSYMMETRY,SYMMETRICPRIMEMOVE,DONTAPPLYLOCALBEND,OYASANSYOPATH,CHECKCROSSINGJOINT,CHECKSYMMETRICPOINT,LOCALBENDCONSTRAINT,SELECTOLDEST,CHECKTRIBECOMPLETE,SINGLESPECIALLINE,INITILIZEPAPERSIZE,_DEBUG_VACUUM,ISALIGNTIGHT,NONRIGIDSYMMETRY,LINEAGE_PARTIALTREE,MOVELOWERBLOCK

は参照されていないので廃止.これらを除くとDEBUG: から降りてきたのを除いて,VERIFYに残ったのはデフォルトONが7件,OFFが3件になった.計10件になった.いや,廃止になるものがまだ残っていた.AVAILABLECHANNEL,MOVENEUTRAL,COMPLETETREE.

「写真がゴミ箱の中にあれば停止」というのがある.どういうことだろう?カードの中にあるbitmapinfo,cBitmap,notepageは任意サイズのオブジェクトだが,これらがゴミ箱には入らないというのはどういう理由なのか?確かに写真イメージなどはアプリ終了時にまとめて始末していたような気もする…UNDOでバックアップするときに複製を取るのはコストが掛かり過ぎるという理由だろうか?20180215という日付があるので,ログを調べてみよう.タイトルは「写真イメージ貼り込み・削除のUNDOをサポートする」となっているので,やはりその辺りが理由になっているようだ.

freeblockで取得したメモリはすべてアプリ終了時まで保持されている.これは他のオブジェクトでも同じだが,delete xxx を実行すると,オブジェクトxxxはゴミ箱に入る.写真や記録ページはカードに付帯しているものだが,明示的に delete されていないので,そのままfreeblockにキープされた状態になっている.「画像を貼り付けてもUndoで消えてしまう」という問題が発生したために,もしかしてゴミ箱の中ではないかと考えてこのトラップを仕掛けたのだろう.実際には入っていなかったので外してもよいのではないかと思う.

オブジェクトがゴミ箱の中に入っているか否かをチェックする関数IsInTrashCanはあるが,オブジェクトのメモリ上のアドレスからfreeblockの位置を取得する手続きを実行している例はこれ以外にない.そういう関数を一つ作っておいてもよいと思うが,小さ過ぎて見つけるのは結構難しいかもしれない.⇒そこまでやる必要もないだろう._getblock_という関数は別のところでも使われている.⇒廃止でよいと思う.INITIALIZEPRINTERも廃止でよい.このプラグマでは何もしていない.InitializePrinter自体は複数の別の位置で実行されている.

SORTLOOSEBOX(ルーズな結婚枠配列をソートする)は廃止してもよいだろう.現在はアクティブではないし,MAXIMALGRAPH:SortLooseBoxも使われていない.この関数はルーズな兄弟枠を世代順に並び替えるもので,コンパクトなレイアウトを作り出すための効果的な手続きを案出しようとする試みだが,あまり成功したとは言えない.この関数自体廃棄でよいと思う.INTERSECTRECT(BRect:InterSectRect関数で接触を交差に含める)とあるが,現状では「接触を交差に含めない」動作になっている.この関数は広範に使用されているので,現状でフィックス以外の選択はない.

HasMultiOccurrence:隠蔽カードを検出しない@20190131は検出しないとしながら,実際はhiddenで停止するようになっている.有効で可視のカードが隠蔽されているというのはやはりおかしいので停止でよいのではないだろうか?⇒DEBUG_NEVERで置き換えることにしよう.CHECKRINGCOUNTはデフォルトOFFなのでDEBUGに移動する.SYMMETRICMOVELOOP(HeapSymmetryBoxでCheckMargPointループを実行する)はすでに2016-11-14に 廃止が決定している.

終わった!VERIFYにはデフォルトONが5個残った.まだ少し残っている.DEBUG: の方はすでに参照されていない項目のチェックをやっていない.PRINTEMPTYPAGE,PRINTPHASENAME,はすでに参照されていないので廃止.これですべてだ.DEBUG: はすべてデフォルトOFFで22件,VERIFY: がすべてONで5件となった.OPTIONS: はすべてデフォルトOFFで9件,SPECIFICATION: がすべてONで15件だ.いや,OPTIONSにDEBUG: のタグでデフォルトONというのが混じっている.

DRAWTRIBEGENEBOXだ.裏面の反転表示は動作不良になっている.デフォルトONというのは規定ではSPECIFICAITONかVERIFYになるところなのだが… 動作的に考えるとSPECIFICATIONに置くしかないような気がする.裏面を補修して元の仕様に戻すことも考えられるが,なぜ現在の仕様になっているのかも考えなくてはならない.あえて分類すれば,INCOMPLETEになってしまうのだが…(一応現状で動いているし)それほど重要な項目でもない…⇒SPECIFICATIONに入れておいた.ONでなければ動かないという条件があるのでここに置くしかない.

「直属系列落ち」というオプション

ゼルコバの木では本人(基準ノード)の(もっとも遠い)先祖の直系血族とその配偶者の集合を標準家系図と呼んでいる.系図全体をこのような標準家系図(系列)の合成とみなすというのがゼルコバの木の基本的な立ち位置だ.ある先祖ノードの標準家系図は系列であり,先祖ノードの数だけ系列が存在する.この意味で系列の位置関係を決定することがゼルコバの木の課題であると言える.これを系列再配置問題と呼ぶ.

各系列において標準家系図の基準ノードに相当する人名を系列優先ノードとする.人名は複数の先祖を持つ場合があり,また他系列のノードと婚姻関係を持つことがあるので,同じ人名が複数の系列の中に登場する場合がある.もし二つの系列の間にこのような重複する人名が存在しないとすれば,その二つの系列には何のつながりもない.

通常は全体図に含まれるほとんどの系列は何らかの繋がりを少なくとも他の系列に一つは持っている.始系列(基準ノードの標準家系図)を除く(孤立していない)すべての系列の優先ノードはこのような重複人名カードの中から選択される.このような重複するカード(多重カード)を図面上から消去して多重ゼロの状態にすることが系図作成の目標であり,系図の品質を評価する最大の基準である.

系列優先ノードは複数の系列と関係を持っている可能性はあるが,それらのうち始系列にもっとも近いものを選択してその系列の主系列とし,その系列を(主系列の)従系列とする.系列を節点とし,系列の主従関係を枝とするグラフが木になることは容易に示すことができる.(ただしここではグラフは連結であることを仮定する)

系列再配置の基本は系列を主系列の近傍に配置すること,主系列に出現する系列優先実ノードと従系列に出現する仮ノードの世代を一致させることの2点だが,それだけでは多重を解消することはできない.場合によっては優先ノードを切り替える必要が生じることもある.

多重カードを解消するための配置を決定するという問題はとても難しく,当初はいわゆる日本的職人芸(Japanese craftmanship)で解決しようとしていたのだが,どうしても埒が明かないということが分かったので,方向転換し,もう少し「数学的」に考えることにした.

その結果,①オブジェクトの移動には垂直と水平の2方向があり,これらは独立に操作できること(なぜなら「直交」しているから),②多重に関係するのは垂直方向の操作だけであることが分かった.さらに,比較的最近になってから③重婚同類グラフが循環することが多重の原因であり,重婚同類循環が存在する場合にはどんなことをしても多重を解消できないということが明らかになった.

重婚同類グラフ検定はグラフの静的な検査なのでメインループに入る前の予備検定でストレートに計算を完了することができる.従って,系列再配置は水平方向の調整を行うだけのものになった.水平方向の移動では2つのことが起きる.一つは系列間の衝突であり,もう一つは乖離だ.

このような問題の最適解を得る方法は知られていないので,基本的には漸近法を適用して解を求めるしかない.これを行っているのが,ApproximationとSolveBondedTribeCollisionという2つの関数だ.前者は系列間の乖離の解消を担当し,後者は衝突の回避を図るものだが,両者は互いに競合するので最終的に静定するまでループする.

さて,今日は「直属系列落ち」とは何か?というところから始めなくてはならない.一方では「ここでは直属系列落ちを許容する」がONとなっているのに,他方では「直属系列落ちを無視する」がOFFという矛盾した状態になっている.そもそも「直属系列」とは何か?これは昨日調べたように,①HASNOMINORであるか,ないし②BOTTOMEMPTYでかつ,HASNOMINOR以外の下位系列を持たないような系列と定義される.ここで,HASNOMINORとは「始系列を除き下位系列を持たない系列」,BOTTOMEMPTYとは「最下段世代枠が空で最下段世代に消去された系列優先仮ノードが存在する系列」である.

つまり,主系列にごく近い直参旗本のような系列と考えてよい.従って,これらの系列は主系列の近傍に配置されるべきというのが「直属系列」の趣旨であるように思われる.一方BONDEDTRIBE(束縛系列)という概念がある.これは「すべての下位系列と交叉しかつ上位系列と交叉している系列」とされるので,おそらく主系列(および自系列の下流系)との位置関係が確定した系列と考えてよいだろう.ただし,IsBondedTribeでは(BONDEDTRIBE | MAGARIHOSEI | SPOUSESENZO)を返しているので,もう少し広い概念だ.束縛系列の反対概念が自由系列で IsFreeTribeBox は !IsBondedTribeに等しい.

何が問題なのか?それが問題だ.現状では「直属系列落ちを無視する@20181001」はOFFなので,!IsBondedTribeのとき,IsDirectSubTribeを検査している.無視と言いながら検査しているところもおかしいが,!IsBondedTribeのときIsDirectSubTribeとは何を意味しているのかを調べてみよう.この条件を書き下ろすと次のようになる.

!(BONDEDTRIBE | MAGARIHOSEI | SPOUSESENZO) && (MAGARIHOSEI | BOTTOMEMPTY | SPOUSESENZO | HASNOMINOR)

これは結局,

(!BONDEDTRIBE & !MAGARIHOSEI & !SPOUSESENZO) & (MAGARIHOSEI | BOTTOMEMPTY | SPOUSESENZO | HASNOMINOR)

だから,MAGARIHOSEI とSPOUSESENZO の項は消えて,

!BONDEDTRIBE & (BOTTOMEMPTY | HASNOMINOR)

のようになると考えられる.(BOTTOMEMPTY | HASNOMINOR)は直属系列の定義にやや近いので,これは(広義の)直属系列でかつ(狭義の)束縛系列でないものと読める.しかし,にも関わらずここでは停止していないので,一体何がこのパスを通過しているのか分からなくなってしまう.⇒(事実上)何も通っていない.SolveBondedTribeCollisionでは冒頭でCheckAllBondedTribeを実行した後,エラーなしでゼロ復帰している.つまり,SolveBondedTribeCollisionはCheckAllBondedTribeを実行するだけのものになっている.

実際,CheckAllBondedTribeの中では自由系列がなくなるまでループを回している.ただし,自由系列が存在した場合には戻り値を返すので最初から自由系列は存在していなかったということになる…つまり,CheckAllBondedTribeが正しく動作することが保証されているのなら,SolveBondedTribeCollisionの後段は不要になるはずだ.さらに言えば,もしかすると,衝突検定を実施しているSolveTribeCollisionさえ不要になっている可能性がある.⇒いや,むしろ逆にSolveTribeCollisionの中からApproximationが呼び出されている.SolveTribeCollisionは自身を再帰実行しているので,この関数一つあればこと足りるのではないか?

どうもCheckAllBondedTribeは何もしていないように思われる.もし,それが本当ならおそらくSolveBondedTribeCollisionも不要になるはずだ.どうもその気配が濃厚になってきた.この処理を完全に止めたバージョンを作ってみよう.⇒とりあえず,問題なく動作している.20180717に「SolveBondedTribeCollisionを実行関数化する」という修正を行っている.この頃のログは残っているだろうか?「テント村」は2018年12月に開設されているのでそれよりも前の話だ.My Weblog Postsのバックアップでも2016-02-08から2018-12-14の間は空白になっている.確かにこれは正確にネットが止まっていた期間だ.

通常ブログが使えないときでも,自己宛てにメールを送ってログ代わりにしていたはずなのだが,メールサーバにアクセスできなければ,それもできない… それ以前からWindows Live Writerを使っているのだから,オフラインで下書きに保存くらいのことはできたのではないか?確かにその通りだ.下書きに残っている.2018-07-17のタイトルは「AccidentalCollisionを三検査関数で書き換える」だ.

「CheckHorizontalOrderを三検査関数で書き換えるというのはやはり無理があるとしても,AccidentalCollisionを三検査関数で書き換えることは不可能ではないと思われる.試してみることにしよう.」

三検査関数というのは,CheckAllBondedTribe,CheckAllCollision,DetectSplitだ.これらは検査用関数だが,それを実行関数に仕立てるということをやっている.「すべての束縛系列がブロック内で衝突していなければ,あとは自由系列ブロックの水平移動だけ」という立場から衝突検定をCheckBondedTribeCollisionに限定するという方針で,ある程度時間を短縮するという効果が出ている.「SolveBondedTribeCollisionを実行関数化する」というのはその一環として実施されたものだろう.

この頃は先祖並び自動オフというところに焦点があったようで,現在テストしているサンプルは先祖並び自動オンだから,これで動いたから安心とは言えない…現在のバージョンにはCheckBondedTribeCollisionという関数は残っていない.多分この関数をリネームしたものがSolveBondedTribeCollisionなのだろう.それをいま削除しようとしているのだが…おそらく,このときの修正は検査関数をそのままの位置で実行関数に格上げするようなやり方だったのではないだろうか?つまり,オリジナルの処理で漏れてしまうところを検査関数をある意味で強化することによって解決したということなのではないか?

「Approximationを廃止する@20180712」というコメントは残っているが,実際には存続している.SolveBondedTribeCollisionを停止すると今度はAccidentalCollision自身がほとんど何もしていないことになってしまう.確かに,この関数を止めても問題なく動いている.ことのついでにこの関数も廃止してみよう.⇒少し時間が掛かるが,渋沢の全体図テストをやってみることにしよう.⇒出口検査のCheckAllCollisionで停止した.さすがにこれは無理かもしれない.スプリットも発生している.AccidentalCollisionを復活させてSolveBondedTribeCollisionだけ停止というモードでテストしてみたが,やはり同じところで停止する.

基準ノード=214 穂積重樹だ.⇒SolveBondedTribeCollisionを復活させ,CheckAllBondedTribeだけを実行して復帰するように改造して同じエラーになった.つまり,SolveBondedTribeCollisionの後段は必要ということになる.反例は#214 穂積重樹だ.いや,その前にAccidentalCollisionが無動作になっていた.

SolveBondedTribeCollisionでCheckAllBondedTribeだけを実行するというバージョンを走らせて,基準ノード=300 三島美禰で停止した.CheckAllBondedTribeで初めて変異が発生した.基準ノード=302 堀切良平などでも同様の動作になるが,最終的には解決しているようだ.CheckAllBondedTribeで衝突はすべて解決しているのでSolveBondedTribeCollision後段の処理はまったく不要であるようにも思えるが,現状のままとしてバックアップに戻ることにしよう.

現在のインストール版V2.2.0.015は源氏を開いてエラーを出すので差し替えておこう.⇒V2.2.0.017_R20201115をリリースした.

「直属系列落ちを無視する@20181001」と「ここでは直属系列落ちを許容する@20181007」をとりあえずフィックスしておこう.SolveBondedTribeCollisionの去就についてはパフォーマンスの問題として別途取り組むこととする.「SELECTFOREFATHER」は先祖ノードのどちらが遠いかを判定する関数の仕様に関するもので,世代を比較するものと親等を比較するものがある.現行は世代比較になっているが,これを仕様としてSPECIFICATIONに移動.「WriteNonStopSampleListを一時停止@20161026」はファイルオープンテスト中にログをファイルに書き込むための関数.デバッグ用途なのでDEBUBタグに移動.

「衝突検定で計算誤差を許容する@20180930」→現状でフィックス.このプラグマではMAXKEISANGOSA という定数を使っている.現在,MAXKEISANGOSAは75箇所から参照されているが,一方で「厳密値を適用する」ないし「厳密値を返す」などとしているところが18箇所ある.厳密値を返すというところはおそらく,上位関数で判定しているためだろう.ただし,許容範囲に等号を含むか否かではぶれがある.

「NAMEBOX:Drawへ移動@20190131」→現状でフィックス.「MakeYokogakiBoxをパス@20180328」→現状でフィックス.MakeYokogakiBoxは横書きタイトルを描画する際の矩形領域を計算する関数で,タイトル枠には,①タイトル行,②作成者名,③作成日付,④コメントの4つのテキストボックスがあるが,コメント枠だけは別計算しているためMakeYokogakiBoxで計算しないという趣旨だ.

(配偶者の性別不問とした@20180201)→このオプションには問題がある.このプラグマは以下のようなコードを無効化している.

// 新規作成した父母の個人情報で性別情報が設定されていない場合があるので,ここで補充しておく.
if (Father) Father->sex = true;
if (Mother) Mother->sex = false;

前に延べた「親子関係を設定」しただけで女性が父になってしまうという現象はおそらく,これが原因になっていたものと思われる.この部分の論理はもう少し詳しく調べなくてはならない.ここでは,暫定的にOPTIONのタグに移動してデフォルトOFFとしておく.これでINCOMPLETEタグは一掃できたが,PENDINGに出戻ってきた項目が一つある.PUREBLOODSUPPORT(純血統図をサポートし上流が養親系だけの場合の下流検定の動作を保証する)というものだ.多分これは動作上必要な措置と考えられるので現状でFIXということになると思われるが,純血統図に関してはもう少し考えてみたい.

純血統図(親族図)では養親系を表示しないようにするということが考えられるからだ.現行では直系血族図を表示したとき,養親系も同時に表示されることになるが,純血統図とすると養親系を除外した血族図になるというイメージなのだが…⇒いや,現状でもそうなっているのではないか?実際,システム構成図はほとんど養親子関係しか存在しないため,純血統図を選ぶと基準カードだけになってしまう.つまり,動作している.何か勘違いしているか,あるいは別の問題があるのか?

いや,そもそもこのオプションは本当に必要なのか?システム構成図で見た限りではOFFでも特に問題なく動作しているように見える.

メインループはすでに不要になっている?

メインループは当初(と言ってもかなり後になってからのことだが)タイムアウトでブレークするようになっていた.描画要素のレイアウト計算が無限ループに陥ってないし発散して停止しなくなる場合に備えての措置だ.その後はデバッグモードではループ回数オーバーでブレークするというオプションが導入されたが,リリース版では以前としてタイムオーバーを監視している.しかし,実際の動作ではつねにループ回数2回で脱出しているように思われる.これはすでにループ初回のラウンドで処理が完結していることを意味すると考えてよい.

言い換えれば,ここでは単純に処理を直列に実行するだけでよい状態になっていると言えるのではないか?ループを一回空回しするための時間コストがどの程度のものか分からないが,余分な処理はできる限り排除した方がよい.急ぐ修正ではないので,以下のことをチェックできるようにしてしばらく運用してみることにする.①ループを空回しするのに要する時間を計測する,②つねにループを2周でブレークすることを確認する.反例があれば停止する.

さて,昨日の続きに戻ることにしよう.PENDINGタグに残っているのはオン/オフを含めてあと5件だけになった.うち4件はすべて「描画矩形領域オーバーフロー」に関わるものなので,実質2件ということになるが,最初のSTRONGSHIFTREGULATIONは「すでに使われていない属性値」に関係してすでにパージを予定されている.まず,「すでに使われていない属性値@20201113」と「すでに使われていない変数@20201113」をフィックスしてしまおう.前者が10箇所,後者が11箇所だ.⇒完了した.

▲上記修正後,アプリ終了時のメモリリークが増えている.これまでは64バイトというのしかなかったが,20バイト,648バイトというのが現れた.もし,これがアプリがnewで生成したものなら,サイズ648バイトで検出できるはずだ.⇒20, 64, 648バイトに相当するメモリ取得は発生していない.メモリの取得関数は一つしかないので間違いないと思う.これには,MFCのオブジェクトは関知していないが…newを使っているところはDLLだけで149箇所ある.メモリリークはDLLだけに関係するものではないが… ⇒ソリューション全体では156箇所だ.

もし状態変化がいまの修正によるものなら,DLLに関係するものということになるが,修正をフィックスしてしまったので戻すことができない.⇒今朝の始業時バックアップを走らせてみた.同じ事象が起きる.つまり,気付かなかったが,もっと前から起きていた事象だ.Detected memory leaks! は2段階で起きている.後段のダンプは前から確認されていたが,前段のダンプは比較的最近のものだ.newで取り出しているMFC(など)NODULEクラス外のオブジェクトには以下がある.

  1. MyRichEdit 128
  2. tm 36
  3. CRichEditCtrl 128
  4. CBitmap 8
  5. CMetaFileDC 16
  6. CBrush 8

しかし,どれもサイズ的には一致しない.この他にnewで取得している以下のような配列がある.

  1. long*[segmentcnt]
  2. long[max- min + 1]

いずれも関数の出口で解放されている上,これを使っている関数MAXIMALGRAPH::SegmentBlockInclusionはすでに使われていない.

MAXIMALGRAPH::SegmentBlockInclusionは使われていないので「使われていない関数」で廃棄する.これを含めてMAXIMALGRAPHで使われなくなった関数が14個ある.CheckMergeSegment1~6は使用可能だが,SWO(SearchWrongObject)があれば代替可能と思われるので廃止する.⇒FIX完了した.

描画矩形領域オーバー時のオプションには以下がある.

  1. 描画矩形領域オーバーフローを無視する
  2. 描画矩形領域オーバーフローのとき自動的にズームアウトする
  3. 描画矩形領域オーバーフローのとき食み出した領域をクリッピングする
  4. 描画矩形領域オーバーフローのときメッセージパネルを表示する

現行では特に何もしていないのだから,(1)に該当するようにも思われるが相違点があるのだろうか?DISREGARDLIMITSIZEOVERがオフのときにはTREEVIEW::Refreshでダンプが出るようになっている.また,CtrlZoomInとCtrlZoomOutでは (TreeView->OVERSIZE) であればエラー復帰になるが,オンのときはつねに通常復帰している.VB側では戻り値によって何か動作が変わるのだろうか?いや,特に何もしていないようだ.従って,このオプションの効果はデバッグ時のダンプだけのように思われる.最後の(4)はすでに廃止されている.

(2)ではTREEVIEW::GetScrollValueからLimitSizeOverという関数が呼ばれてズーム倍率を調整しているようだ.(3)も廃止になっているので,効果があるのは(1)と(2)だけということになる.(1)はDEBUGのタグに移動し,(2)はOPTIONSということでよいのではないか?(3),(4)は当然廃止だ.「自動的にズームアウトする」という動作の効果は実際に試してみなくては分からないが,それは後日ということにしてとりあえず,デフォルトオフでOPTIONSに入れておく.これでPENDINGタグは空になった.

いくつかの項目をOPTIONSからSPECIFICATIONに移した.たとえば,「描画オブジェクトのリサイクルシステムを用いる」や「極大グラフ検定を実施する」などで,これらは裏面も実行可能ではあるが,実際にはオフに設定する予定はないという意味では「仕様」と見るのが自然であるからだ.というか,OPTIONSでデフォルトONであるような項目はすべてSPECIFICATIONとみなすべきだろう.

つまり,OPTIONSに残るのはデフォルトOFFの項目だけということになる.この中には「完全被参照リスト管理を実行する」や「SIMPLEGRAPHのattributeを廃止」がある.「結婚枠を折り畳む機能をサポートする」のように実装されていないオプションも便宜的にOPTIONSに収納しておこう.デフォルトOFFで現在OPTIONSに入っているものとしては,これらの他に以下がある.

  1. 印刷出力では連結線分・罫線を太線で描画する
  2. 単位家系図を廃止して拡張Z木図をサポートする
  3. 描画矩形領域オーバーフローのとき自動的にズームアウトする

(1)と(2)に関しては出荷時に再検討するというのでよいと思うが,(2)の「拡張Z木図」というのがよくわからない.Z木図というのはもともと多系統図だったはずだ.「拡張Z木図の場合は上流系の兄弟ノードを含む」という記述がある.また,2016-05-24 確定として「拡張Z木図を廃止して姻戚関係図をサポートする」とある.現行ではどうなっているのだろう?現行では親族範囲は7種で

  1. 標準家系図
  2. Z木家系図
  3. 直系血族図
  4. 傍系血族図
  5. 直系親族図
  6. 傍系親族図
  7. 法定親族図

となっている.拡張Z木図も姻戚関係図も出てこない.2018-03-26には「姻戚関係図を直系親族図に仕様変更」とあるので,現在の直系親族図がそれに該当するもののようだ.

ビルドしようとすると以下のパネルが出てくるようになった

image

書き加えた部分を削除しても変化しない.mmm… 分かった.✗(バツ)という文字を使ったためだ.X(乗算記号)なら通る.

姻戚関係図というのは多分,直系血族+配偶者の直系血族だったと思う.親族にはこれに血族の配偶者が加わるので,直系親族図というのは

直系親族図=直系血族+直系血族の配偶者+配偶者の直系血族+配偶者の直系血族の配偶者

となっているのではないかと思う.※

※親族図メニュー→親族範囲→直系親族図のヒントを見ると「本人の直系血族(先祖系と子孫系)とその配偶者,および配偶者の直系血族=直系親族」とあるのでこちらの方が正しい.つまり,直系親族図には「配偶者の直系血族の配偶者」は含まれない.

Z木家系図=直系血族+直系血族の配偶者

だと思うが,拡張Z木図はこれに本人の兄弟姉妹とその配偶者を追加したものではないだろうか?標準家系図は,

標準家系図=本人の(もっとも遠い)先祖の直系血族+その配偶者

なので,

拡張Z木図=本人の(すべての)先祖の直系血族+その配偶者

となっていたのかもしれない.直系親族図はわたしがイメージする「親族図」にもっとも近いので,現行の仕様通りでよいのではないかと思う.EXPANDGENEALOGYで「単位家系図を廃止して拡張Z木図をサポートする」ことができるのか?見てみよう.⇒このオプションはすでに廃止されている.まぁ,上の記録から見て当然かもしれない…これでOPTIONSのデフォルトOFFの項目は5件となったが,この他にまだデフォルトでONという項目が3つある.

  1. メタファイルに出力する@20180312
  2. 同性婚をアブノーマルな婚姻とする
  3. 系列枠内水平スプリット検定をシンメトリ婚を持つ系列に限定する

OPTIONSにはまだ実現されていない機能を含めてもよいということになっているので,「メタファイルに出力する」をOPTIONSに含めることは妥当だが,デフォルトでONになっているという点が気になるところだ.何をやっているのだろう?なるほど,「ファイルへ出力をサポートしない@20180314」というオプションに依存しているため,現在は実行できるようにはなっていない.⇒ようやくストーリィが読めてきた.それは,こういう意味だ.つまり,

印刷ダイアローグで「ファイルへ出力」を選択した場合には「メタファイル」に出力される

ということだろう.「ファイルへ出力」を選択した場合には,通常のアプリでは*.pngファイルに出力するようになっていると思う.描画イメージはCImageで保持しているので,PNG出力することは難しくない.実際,「ファイルへ出力をサポートしない」→OFF→「メタファイルに出力する」→OFFのときは,*.bmpファイルに出力するようなコードになっている.多分BMPよりPNGの方がファイルサイズは小さくなるはずだと思うので,通常通り,PNG出力でもよいのではないだろうか?

メタファイルにこだわったのは,ビットマップではなくベクトル形式で画像データを出力したかったためだが,メタファイルを編集できる(無料)ソフトが見つからなかったためペンディングになっていたのではないだろうか?「ファイルへ出力」できないというのも不都合なので,「ファイルへ出力をサポートしない」をSPECIFICATIONから一旦OPTIONSに格下げした後,改めてファイル出力形式を再検討すべきではないだろうか?⇒「ファイルへ出力」をサポートし,とりあえず,BMPに出力できるようにしておこう.この場合は「メタファイルに出力する」はOFFになるから,OPTIONSに留まる資格が維持される.

「同性婚をアブノーマルな婚姻とする」はONになっているが,その内容はかなり疑わしい.ゼルコバの木の初期バージョンというか,かなり最近までは異性婚以外はすべてエラーとして処理され,誤入力した場合には自動的に性別が整合するように補正するような仕組みまで組み込まれていたのだが,さすがに時代の潮流には逆らえずかなりルーズな扱いにはなってきている.従って,「同性婚をアブノーマルな婚姻とする」ONというのはかなり疑わしい.何をやっているのか見てみよう.

結婚リンクにはabnormalsexという属性があり,「配偶者性別に矛盾がある」場合つまり,同性婚の場合にはONになるという仕掛けだ.このabnormalsexという値が真の場合には「結婚点」の形状が変化するようになっている.具体的には,とここまで書いたところでサンプルを作ろうとしてエラーになった.⇒下記▲参照

結婚点は形状が違うというより,塗りつぶしの色が異なるだけのようだ.つまり,白と黒の中間色のグレーで表示されている.これはかなりわかり易いような気がする.

image

結婚点の白・黒は必ずしも女性・男性を意味するものではない.結婚連結線はあるカードとその配偶者を連結する水平線で,配偶者が複数ある場合には本人(重婚者)側が黒,配偶者側に白が与えられる.この色別はその連結線の所有者が誰かを示すためのもので,そのカードが本人ポジション(親の子ども枠)にあるか,配偶者ポジション(配偶者の親の子ども枠)にあるかには関わりがない.

「同性婚をアブノーマルな婚姻とする」という表現はややどぎつい印象を与えるかもしれないが,結婚点をグレー表示すること自体は穏便な表現になっていると思われるので,このオプションはSPECIFICATIONとしてよいと思われる.ただし,今回のセッション中に再現手順はまだ確立していないが,親子関係を設定しただけで女性が父親になってしまう例が複数発生している.これは明らかに予定されていない動作であり,誤動作と見るしかないので,バグとして追いかける必要がある.

▲下図のような家系図を入力中,GIRLの父母MAN+OTHER MANをOTHER MAN+MANに書き換えて登録で停止した.

image

このエラーが同性婚に関わりがあるのかないのかは不明.BUG20-11-14 22-23-24.ZELで再現できる.エラーパネルをOKで閉じるとGIRLのカードには父:OTHER MAN, 母:WOMANが残っているが,親子連結線は描画されていない.母の欄にMANと記入したのにWOMANとなっているのは,入力氏名の探索にあいまい検索を使っているため,MAN→WOMANを第一候補として登録したものと思われる.

image

GIRLで並び替えると下図のようになる.

image

できの悪い図面だ.MANが多重になってしまっている.こんな簡単な作図ができないようでは,まだまだ先が遠いという感じがしてしまう…この障害は再現できるので,先に残ったOPTIONSのデフォルトONの項目を片付けてしまおう.「系列枠内水平スプリット検定をシンメトリ婚を持つ系列に限定する」というやつだ.どうなっているのか見てみよう.TRIBEBOX::CheckInnerSplitの入口で(!SymmetryCount && !TooYoungCount)の場合は単にゼロ復帰しているというだけだ.つまり,その系列内にフロート婚と呼ばれる特殊婚が存在しない場合は処理をパスするというフローになっている.これで特に問題は生じていないように思われるので,現状でFIXでよいのではないだろうか?

これでPENDINGタグはデフォルトでON/OFFに関わらず完全に空になった.つまり,「PENDINGゼロという目標」を達成することができた.その代わり,OPTIONSには「未完成のオプション」が含まれることになったが,これらはONにしない限り作用することはないので放置されたとしても安全だ.しかし,INCOMPLETEにポジションを移した項目がかなりある.これらはINCOMPLETEというより,説明にやや疑義があるというもので,動作確認を含めた精査が必要だ.全部で8項目あり,うち「直属系列落ちを無視する@20181001」を除く7つの項目はすべてデフォルトONになっている.

  1. SELECTFOREFATHER IsFurtherのオプション
  2. 配偶者の性別不問とした  動作に疑問がある
  3. WriteNonStopSampleListを一時停止 WriteNonStopSampleList
  4. 衝突検定で計算誤差を許容する CheckIncidentalCollision
  5. ここでは直属系列落ちを許容する SolveTribeCollision
  6. 直属系列落ちを無視する SolveBondedTribeCollision
  7. MakeYokogakiBoxをパス TITLEBOX::SetDispParm
  8. NAMEBOX:Drawへ移動 意味不明なところがある

デフォルトOFFの「直属系列落ちを無視する」から見てみよう.この分岐はTRIBELIST::SolveBondedTribeCollisionに置かれ,OFFの場合にはDEBUG_NEVER (tribe->IsDirectSubTribe())を実行するようになっている.つまり,IsDirectSubTribeで停止するようになっている.ということはIsDirectSubTribe=直属系列落ちを意味すると考えて間違いないだろう.つまり,現状は「直属系列落ちなら停止する」という動作になっている.SolveBondedTribeCollisionという関数の目的は「すべての束縛系列の衝突を検査し修復する」ということだ.束縛系列に対して,自由系列というのがあり,それぞれがブロックを構成している…

束縛系列は(BONDEDTRIBE|MAGARIHOSEI|SPOUSESENZO)のいずれかの属性を持った系列と定義される.

  • BONDEDTRIBE すべての下位系列と交叉しかつ上位系列と交叉している系列
  • MAGARIHOSEI 先祖曲がり補正を受けた系列(スプリット検定をパスする)
  • SPOUSESENZO BTW左手本人が系列先祖でかつ系列優先ノードの特殊BTWに関わる系列と左手本人およびその結婚枠

かなり難解だが,自由系列というのはおそらく主系列とそのブロックに直接接触していない系列のことだろう.始系列を除くすべての下位系列は主系列に従属するという関係にあるから,この関係を枝とする木を構成する.しかし,系列は大雑把に言って右から左に水平配置されるので,いわば切り倒した木を薪にして並べたような状態になっていると考えられる.これはそれ自体一種のトポロジーソーティングのようなものになっていると考えられるが,それをどうやって解決しているのだろう?IsDirectSubTribeという関数が返しているのは,

(MAGARIHOSEI|BOTTOMEMPTY|SPOUSESENZO|HASNOMINOR)

という値だ.新たに2つの属性が出てきた.

  • BOTTOMEMPTY 最下段世代枠が空で最下段世代に消去された系列優先仮ノードが存在する系列
  • HASNOMINOR 始系列を除き下位系列を持たない系列

直属系列に関しては次のような記述がある.

以下のような系列をその主系列の直属系列とする
①下位系列を持たない系列(付帯系列)
②最下段世代枠が空で最下段世代に消去された系列優先仮ノードが存在しかつ,付帯系列以外の下位系列を持たないような系列

上の定義に従えば①はHASNOMINOR,②はBOTTOMEMPTYでかつ,HASNOMINOR以外の下位系列を持たないような系列となる.IsDirectSubTribeにはBOTTOMEMPTYやHASNOMINORが含まれているところから見ると,直属系列落ちというのは,直属系列でなくなるというより,「直属系列の状態になる」のように汲み取れる.束縛系列にはMAGARIHOSEIとSPOUSESENZOが含まれているから,「束縛系列の状態から直属系列の状態に変化する」のようなニュアンスが感じられるがあまり正確ではない.

この操作に関係する属性は5つあるが,そのうちHASNOMINOR(付帯系列)だけは動的に変化する可能性はないと考えられる.(系列参照関係は動的に変化し得るので絶対にないとは言い切れない…)おそらく,この図法では直属系列と呼ばれるものをできる限り主系列の近傍に配置するというストラテジーを考えているのだろう.もう少し読まないと何をやっているのか理解できないが…

PENDINGゼロという目標は容易に達成可能

ようやく乗ってきたようだ.200箇所も分岐がある中でPENDINGをゼロにするなどという目標はとてつもないものに聞こえるかもしれないが,PENDINGから,①SPECIFICATION,②OPTIONS,③DEFECTED に移すという道が開けたのでPENDINGが空になるというのももはや時間の問題だ.ここで,①SPECIFICATIONというタグは「仕様」であり,このようなものを作るという「決定」だが,特に「それなしではシステムが動作しないようなオプション」が含まれる.言い換えれば裏面(#defineをコメントアウト)では動作しないことを認める.

一方,②OPTIONSは「任意選択可能なオプション」であり,基本的にはオンとオフの両側で動作するものでなくてはならない.ただし,「裏面の動作は必ずしも保証されない」としてよい.③DEFECTEDは本来ならOPTIONSの候補であるところだが,明らかに不具合が見られるようなもので,「当面の間使用を禁止する」類のものだ.

昨日の修正では誤って「仮修正」を廃止してしまったための手戻りが発生した.一応STOP文で停止してくれたのでことが発覚し,比較的短時間でシューティングできたが,やはり,「現状」というのは(そこまでの動作確認の積み重ねがあるので)重く見なくてはならない.『その場の思い付き命取り』という格言をもう一度噛みしめるべきだ.

PairBoxGeneChangeの中でノード対が削除される場合がある PairBoxGeneChangeの呼び出し箇所を総点検する必要がある

PairBoxGeneChangeは8箇所から呼び出しがあり,その中で戻り値を取っているのは4箇所だけだ.PAIRBIX::retrieveShiftedPairBoxでは自ノード対を対象にPairBoxGeneChangeを実行している.⇒この関数の動作は安全だ.出口で「このノード対は死んだ~」という表示が出るくらいだから…その他3箇所で対処した.

さて,昨日の続きに戻ろう.DEBUGタグとVERIFYタグをすべてオフにして,PENDINGの動作を検証しているところだ.定義文のコメントから「仕様」と考えられるものはとりあえずすべてOPTIONSに移動することにする.この中には「純正血統図をサポートする」,「同性婚をアブノーマルな婚姻とする」などがあるが,ここでは動作確認は行わない.

たとえば,「純正血統図をサポートする」ならオフの場合はどういう動作になるのかを調べなくてはならないが,「純正血統図をサポートする」をオフにしても,系図画面設定パネルから「純血統図」が消える訳ではないし,そのときの動作がどのようなものになるのかも分からない.しかし,そこまでやると手が回らないので,ここでは「OPTIONSの動作確認は出荷時検査で行う」とだけマニフェストしておく.

これでPENDINGタグのデフォルトでオンに残るのは7件になった.これらにはコメントからよく趣旨が汲み取れないもの,ないし疑義のあるものなどが含まれるが,一度すべてINCOMPLETEに差し戻して別途動作確認を行うことにする.これらはおそらくOPTIONSに昇格するのではなく,フィックス→確定になるはずだ.PENDINGカテゴリでデフォルトでオフのものに関してはすべて現状でフィックスでよいのではないかと思われるが,一通り調べてみる必要がある.この中には

  1. HeapUpperの循環検査を行なう→廃止
  2. 計算時間制限の代わりに計算ループ回数に上限を設定する
  3. MakeSectionだけでノード対区間計算を完結させる
  4. カード世代シフトの規制を強める
  5. 描画矩形領域オーバーフロー(3件)
  6. 直属系列落ちを許容する/しない

などがある.HeapUpperの循環検査はYリストの誤接続などで生じる循環を検出するもので,すでにそのようなバグは完全に消滅したとみられるので廃止でよいと思う.計算時間制限というのはメインループが止まらない場合に時間で処理を打ち切るという安全装置だがすでに廃止され,現行のループ回数制限を越えるような事象も起きていないと判断できるのでフィックスでよい.これにはTESTタグで「当面使用禁止」としているオプションも絡んでいるが,整理可能な論理のクリアランスが必要だ.ノード対区間計算もなんどか仕様変更を繰り返しているが,現状でフィックスでよいのではないだろうか?カード世代シフトの規制を強めるというのはよく分からないが,「カードシフト」という機構自体が廃止されているという話もあるので,調査する必要がある.

「描画矩形領域オーバーフロー」という事象もすでに根絶されているようなので周辺論理を含めて一掃でよいのかもしれない.「直属系列落ち」が何を示しているのかよくわからないが,「ここでは直属系列落ちを許容する」というのと,「直属系列落ちを無視する」というのがあり,混乱している.というか,「許容する」と「無視する」というのが同義であるとすれば,では「直属系列落ちを無視しない」ところはあるのか?ということになる.そのような場所がないというのなら,「直属系列」という概念自体消滅する.そういう場所があるとしたら,むしろ積極的に「この場所では直属系列落ちを…のようにする」ということが明示されなくてはならない.

開いていたサンプル(ZTシステム構成図7.ZEL)を閉じようとしてFAMILYTREE::GetPersonalBaseで停止 topologyが空になっている

VB側からの呼び出しによるものでVBではすでにFormClosingを実行しているところだ.PHASEはゼロでINITIALSTATEに戻っている.FAMILYTREE自体はまだ活きているが,スロットはすべて空になっている.再現できるだろうか?系図画面設定パネルを操作したあとしばらく放置してからアプリ終了という手順だったと思うが…VBからのコマンドから発生する例外はGCでキャッチするようになっているので異常終了のようなことにはならないとは思われるが…

開いて閉じる,あるいは開いて設定パネルを出して終了では起きないが,部分図への切り替え,カラー設定など操作してから終了すると再現する.VBとDLLは並列実行されていて,特に待ち合わせということをしていないので,VB側の終了処理が遅れればこのようなことはあり得るのではないかと思われる.どうすればよいか?通常それぞれのコマンドは単体で完結しているので,特に待ち合わせの必要はないが,終了時は全体の処理時間が相当なものになるためまだ存続していると思って問い合わせするなどのことはあり得るのではないか?

GetPersonalBaseではASSERT_NEVERで停止しているが,これを内部で判断して動作を変えることはできる.しかし,このようなことは他の関数でも起き得ると考えなくてはならないから,かなり広範な修正が必要になってくる.以下のような外部インタフェース関数では:

_EXPORT long __stdcall GetPersonal(long num, PERSONAL* person);

入口で_ASSERT_NEVER(!FamilyTree)を実行してFAMILYTREEが活きていることを確認しているが,ここに専用マクロを設置してPHASEを確認するようにすればよいのではないか?たとえば,_ASSERT_ACTIVEとし,PHASEがINITIALSTATEではゼロ復帰するというのでよいのではないだろうか?⇒INITIALSTATEではなく,BEGINNINGSまで戻っていた.⇒こういう状態で呼び出しが掛かってくるのはこの件一回だけだが,念のためすべての外部関数にこのマクロを仕込んでおくことにする.(!FamilyTree)の検査もこのマクロの中で実施して,マクロを一本化しておこう.⇒52箇所あった.ただし,うち2箇所はvoid関数(シグネーチャが異なる)なので直接コードを書き込んだ.

いや,今度は3つ呼び出しが掛かってきた.CallGetUndoStat,GetRecordCount,CallSetKeizuParm.タイミングによっていろいろ変わるようだ.すべての外部関数に仕込んでおいたのが正解だった.

CHECKHEAPCIRCULATION(HeapUpperの循環検査を行なう)はすでにどこからも参照されていないので単純に廃止.⇒間違えた.大ミスをするところだった…検索条件が「このドキュメント」になっていた.このオプションは2017年1月に廃止され,2018年3月に停止が確認されているので廃止でよいと思われるが,Yリストの上流方向での循環検査ルーチンというのが見当たらないので,どこかにコードとして残しておいた方がよい.この検査ではBobject::heaptestという専用変数を使っているが,他の検査ルーチンではcheckmarkというのを使っているので,これを使うように改めればよい.まず,Bobject::HeapUpperの中ある検査コードを切り出してみよう.

いや,この部分だけを切り出してもあまり意味がないかもしれない…HeapUpper自体動的にYリストを上昇しながら実行される計算過程なので,その関数内では動作が完結しない.Yリスト(描画リスト)というのはBobjectに備わった機構で3つのスロットと1つの整変数を使って構成される有向グラフだ.ルートを除くすべての描画オブジェクトは1入力枝と2出力枝を持ち全体として大きな2分木を構成する.たとえば,抽象グラフ検証系を使ってこのような有向グラフが有向木となることを検査することは難しくないが,それなりのコストが掛かる.

単発的にあるノードが上流経路で循環していないことを確認するには単純に木を上昇して自ノードに遭遇しないことを確認するだけでよい.コードにしても2, 3行で終わってしまう.有向グラフが木であるか否かを検査するツールを整備することは意味がある.親参照パスも同種の有向木だ.ただちに必要というものでもないので,これはまた後日ということにしておこう.ということで,ためらわず廃止でよいのではないか?

SORTINGTIMEOUTLIMITの説明には「計算時間制限の代わりに計算ループ回数に上限を設定する」とあるが,実際のコードではSORTINGTIMEOUTLIMITが未定義のとき(つまり現状では)TIMEOUTLIMITが立っていないときにのみ「制限時間」をレジストリに登録するという動作になっている.この値はVBで読み出されて表示される.分かり難い説明だが,どうも書き込まれているのは実際の計算に要した実時間で,「制限時間」という場所を借りているだけのようだ.

TIMEOUTLIMITというブール値はレジストリの「計算時間制限」に格納されている.計算時間制限と制限時間は下図のようにVBの隠しパーツで設定できるようになっている.

image

時間制限するがオンで計算時間3秒になっているが,実際の動作はどうなっているのだろう?⇒TIMEOUTLOOPがゼロでないときは時間制限は作動しない.ようやく少し読めてきた.

このシステムではループが停止しないときに備えて元々タイムアウトでブレークするような作りだったのだが,デバッグ上の都合で回数でブレークというのを導入したのだろう.なぜ回数か?というと,時間制限では実行時にムラが出て現象を再現出来ない可能性があるためだ.いくらマイクロセコンドのクロックで制御された機械でも実時間にはどうしてもさまざまな理由でムラが生じる.バックグラウンドタスクの影響を受けることもあり,室温にさえ左右されるかもしれない.事象が発生したときのループカウントを使えば現象を確実に再現できる.

現行論理ではデバッグ時にはTIMEOUTLOOPカウントを10に設定し,リリース版ではゼロに設定している.TIMEOUTLOOP値がゼロの場合には時間制限が掛かるはずだがそのような動作にも見えないのはなぜか?リリース版では時間計測していないから,時間はつねにゼロだ.

現行ではおそらくすべてのサンプルでメインループを2回しか回っていない.2回回るのは,システムが静定するのを待っているからだ.つまり,データが更新されている間はループするようになっているため,最小限でも2回回らなくてはならない.もし,2回目がデータが更新されないことを確認するだけのものであるのなら,ループではなく単純な線形処理に書き換えてしまってもよいはずだ.すでにそういう状態になっているのではないか?この点に関してはもう少し調べなくてはならない.

ともかく,SORTINGTIMEOUTLIMITというオプションは説明にあるほどのことをやっている訳ではないので,現状で単純にフィックスでよい.その他,loopcountとroundがまったく同じ値を持っていてどちらかは不用であるなど,気になる点は多々あるが,また後でということにしよう.SORTINGTIMEOUTLIMITの説明文は「計算時間制限なしの場合,計算実時間をレジストリ登録する」のように書き換えた.

MAKESECTIONFULL(MakeSectionだけでノード対区間計算を完結させる)はすでにフィックスしている.どこからも参照されていない.STRONGSHIFTREGULATIONも説明文が悪い.このオプションではTRIBEBOX::ResetCriticalの中で禁止ノード対リストを空にした後,系列内のすべての人名ノードのSHIFTRECURRENT属性をリセットしている.これが「カード世代シフトの規制を強める」ことだろうか?

いや,おかしい.そもそも禁止ノード対リストはとっくの昔に廃止したのではなかったろうか?何かまたとんでもない失敗をやらかしてしまったような予感がする…prohibitlistとshiftlistを廃止したのは11月9日だ.この日付には何か不吉感が漂っている.「系列枠のシフト管理用ノード対リストを廃止@20201109」という名義で始末しているはずなのだが…ソースファイルのどこにも痕跡がない.

確かに2020年11月10日には9日の二回目バックアップまで戻っているので,その後に実施された修正は抜けている.9日の記事は2つのノード対リストのパージ以外では一括変換のことしか触れられていないので,この部分だけ修正を入れれば多分同期が取れると思う.まず,これをやっておこう.キーワードには「系列枠のシフト管理用ノード対リストを廃止@20201113」とする.以下の関数・変数もすべて不用となる.

NAMEBOX::shiftnum,NAMEBOX::targetnum,TRIBEBOX::RemoveFromShiftList,UpdateShiftList,ShiftMultiCards,DumpShiftList,DumpProhibitList,PAIRBOX::shiftnode,gettarget,shiftcard,shifttarget

これからフィックスするところだが,かなり修正が入ったので一旦バックアップを取っておこう.mmm… また例外が発生してしまった.

image

障害発生日時:2020年11月13日20時56分51秒 bugflag=0 C:\Users\babalabo\Desktop\添付サンプル\源氏物語全系譜6.ZEL
Failed at  with error 1008: 存在しないトークンを参照しようとしました。 障害発生日時:2020年11月13日20時56分51秒 bugflag=1 C:\Users\babalabo\Desktop\添付サンプル\源氏物語全系譜6.ZEL
0x781DFFD0 (ZelkovaDLL3.dll) で例外がスローされました (ZelkovaTree2021.exe 内): 0xC0000005: 場所 0x000000A8 の読み取り中にアクセス違反が発生しました

エラーは画面右上の終了ボックスをチェックしたタイミングで発生する.⇒リリース版をビルドしていた.デバッグ版ではnodule::Disposeで停止する.nodule::Disposeに入ってくるときには,すべてのスロットが空になっていなくてはならないのだが,残留リンクがあるようだ.PAIRBOXの2つのスロットを潰しているのにインデックス名が残っていた.⇒対処した.問題なく閉じられるようになった.

さて,バックアップも取ったのでいよいよ「系列枠のシフト管理用ノード対リストを廃止@20201113」をフィックスすることにしよう.もちろん,最初から最終的な記述に進んでもよいのだが,やはり手順を踏んで最初に#defineを定義し,次に変更箇所を#ifndef で書き換えてゆくというステップの方が安全だ.どっちみち最終的には(修正タイムスタンプを除いて)すべて削除してしまうのだが…

修正箇所は35箇所あるが,すべて「系列枠のシフト管理用ノード対リストを廃止@20201113」のキーワードが付いているので検索で容易に位置を特定できる.あとはグレーの「アクティブでないブロック」を一つづつ潰してゆくだけだ.⇒終わった.

STRONGSHIFTREGULATIONをフィックスしようとしているところだった.STRONGSHIFTREGULATIONが作動しているのはTRIBEBOX:ResetCriticalでここでは「カードシフトの循環防止用パラメータを初期化する」を実行している.この関数はTRIBELIST:TribeRelocationから呼び出されているが,「垂直セグメント検定用パラメータを初期化する」という説明が付いている.垂直セグメント検定というものはすでに廃止されているので,この関数自体不用となっている可能性がある.

確かに,SHIFTRECURRENTという属性自体がすでに使われていない.「系列枠のシフト管理用ノード対リストを廃止@20201113」の一部として,SHIFTRECURRENTを廃止してみよう.もう一度このキーワードを#defineして始めよう.⇒修正した.これ以外の属性でもすでに使われていないものが発生している可能性があるので,点検しておこう.

ENDPOINTFAILUREはすでに使われていない.「すでに使われていない属性」が11種もあった.この修正はかなり大きなものになりそうなので,一度バックアップを取ってから初めた方がよい.「すでに使われていない変数」というのもある…

目標:PENDINGゼロでVERIFICATION,TEMPORARYの完全ニュートラルを達成

コンパイルオプションを6つのカテゴリに区分し,それらにタグを付けて管理するようにした.

  1. VERSION comdebug.h COMDEBUG:
  2. INCOMPLETE nodule.h INCOMPLETE:
  3. PENDING nodule.h PENDING: 
  4. TEMPORARY bobject.h DEBUG: , LOCAL:
  5. VERIFICATION bobject.h VERIFY:
  6. TEST coupling.h TEST:

comdebug.hに入っているCOMDEBUG:はバージョンやライセンス管理などに用いるものでデバッグの対象とはならない.nodule.hにはINCOMPLETEとPENDINGが入っているが,INCOMPLETEは現在ゼロ個,PENDINGが36個ある.PENDINGの中には将来的な拡張に備えるものもあるので必ずしもすべてという訳ではないが,一応PENDINGの個数をゼロにするというのがこの作業の目標と言える.TEMPORARYはデバッグ時のダンプなど一時的に使われるコード,VERIFICATIONは検証のために常設されているプローブだが,いずれも無害コード(フローを変化させず,データの書き換えを行わないもの)でなくてはならない.TEMPORARYとVERIFICATIONがこのような意味でのニュートラルなコードになっていることを確認することもこの作業の一部に含まれる.

coupling.hに入っているTESTは包括テストなどを実施するためのオプションだが,すでにかなり古くなっているように思われる.実際,現行では包括テストはすべてヘルプメニューから実施できるようになっており,コードをいじる必要はない.システムに組み込まれた作り付けの装置のようなものになっている.TESTコードの大半は廃棄の対象になるかもしれない.オプションをオンないしオフにしてエラーが発生するとすれば,基本的には動作する側でフィックスすることになる.

不用となったコードを参考資料として温存するか否かはまた別の判断になるだろう.INCOMPLETEということはデバッグ未完了ということを意味するから,これを一応対象外とすれば,このセッションの目的は「PENDINGゼロでTEMPORARYとVERIFICATIONがデータフローニュートラルであることを確認する」ことにあると言える.

counpling.h はシステム構成図の頂点をなすCOUPLINGクラスを定義するものなので,すべてのコードから参照可能になっていると思っていたが,一部にアクセスできないものがあった.これまでヘッダファイルのヘッダファイルとして使われていたkakeizu.h はcoupling.h でもインクルードしているので,include “kakeizu.h” となっていたところをすべて include “coupling.h” に書き換えた.nodule.h や comdebug.h は元々グローバルにアクセスできるようになっている.coupling.h はkakeizu.h に入っているKAKEIZUを参照しているので, kakeizu.h から coupling.h をインクルードすることはできない.

PENDINGはデフォルト(既定でオンのオプションをオンとして),DEBUGはすべてオフ,VERIFYはデフォルトを試して動作した.ただし,Bobject::vsegmentが廃止されているため,NAMEBOX::DrawとMARGBOX::Drawで修正が発生した.DEBUGはすべてオフのまま,VERIFYのデフォルトオンをすべてオフに切り替えて試したところ,広域スプリットと描画不良が発生した.ただし,TRIBELIST:TribeBaseList→baselist,GENEBOX::getGene→getGenerationの書き換えた.⇒以下のオプションをオンにすることによって描画できた!

#define ABSOLUTEYLIST            // VERIFY: ★絶対座標変換を描画リストに沿って実施する

これはかなり驚異だ!止めてあるオプションの中には,「極大グラフ検定を実施する」,「三極検定を①結婚点一致→②衝突検定→③セグメント検定の順で実行する」,「★BRect::InterSectRect関数で接触を交差に含める」など,システムの基本仕様のようなものまで含まれている.

ともかくABSOLUTEYLISTを実施しないと描画できないことが分かったので,この定義はオプションから仕様(SPECIFICATION)に格上げするしかない.いや,そう結論するのはあまりにも単純だ.ABSOLUTEYLISTがオフのときには何もしていない,つまり相対座標系のまま放置されているのだから,描画できるはずがない.仮に描画関数がすべて相対座標に対応していればその可能性もゼロではないが,現行ではほとんどの関数はそうなっていないはずだ.

将来的にすべての描画関数が相対座標系に対応するということも考えられないことではないが,ここでは一旦このオプションを仕様として確定するしかないのではないか?⇒#if defined(ABSOLUTEYLIST) を #if 1 に変えて確定したが,元の論理は残した.「Yリストに沿った変換」ではなく「座標系変換パスに沿った変換」というのも考える余地があるからだ.むしろそれを試して両者が完全一致することを確認した方がよい.ただし,それはまた後日ということにしておこう.

ここでは一旦DEBUGとVERIFYのデフォルトでオンの項目をすべて戻した上で,PENDINGの分を試してみることにしよう.⇒2つのオプションをオンに戻すことでエラーを回避することができた.一つは「GetOSDisplayStringの使用停止@20190109」でもう一つは「CHUNKFILEクラスを実装する」だ.前者がないと「’GetVersionExA’: が非推奨」の警告が出てオブジェクトファイルが作れない.また,後者がないと,カード写真イメージのロードに失敗する.従って,これらも必須オプションということになるが,ここで新たに2つのサブカテゴリないしタグを追加することを提案したい.

①SPECIFICATIONと②OPTIONSだ.前者はそれがないとシステムが動作しないもの,後者はオン・オフを切り替えても動作するが,オフのときの動作は保証しないというものだ.両者の線引きには多少微妙なところがあるが,いまの場合,上記のABSOLUTEYLISTや,「GetOSDisplayStringの使用停止@20190109」はSPECIFICATIONで「CHUNKFILEクラスを実装する」はOPTIONSでよいのではないかと思う.一旦PENDINGからSPESIFICATIONないしOPTIONSに格上げされたオプションは,基本的には確定したものと考えてよい.

このサブカテゴリに該当するものはかなりあると思われるので,整理してみよう.というか,まだデフォルトでオフのオプションをオンに切り替えるテストをやっていないので,そちらを先に片付けてしまおう.デフォルトでオンのオプションはすべてオンに戻しておくことにする.多少の修正が入っているので一度バックアップを取っておこう.いや,それどころではない.例外が発生している.

上記の環境で起動→終了で,以下のようなメモリアクセス違反の例外が発生した.

mblock->**mmptr** が 0x93203436

再現しない.障害はSaveFamilyBaseで起きているのでバックアップタイマが作動するまで待つ必要があるのかもしれない.例外はfreeblock:delmptrで発生している.バックアップファイルは作られたが正常に終了できた.バックアップが更新されるまで待ってみよう.以下の行が数回出ていたのでかなり時間が経っていたかもしれない.

書き込み:zoom_normal=0.290677 preview=0.305460

終了ボタンが効かなかったような気がするので,放置していただけで起きていた可能性もある.これまでにこのような例外の発生は見たことがないので何か微妙なタイミングがからむ超低頻度の事象がたまたま起きたという可能性もある.このプリント文はSerializeHeaderで出しているものだ.⇒再現した.上の「書き込み」を4回出したあと,例外が発生している.ただし,今度は少し状況が違う.

**this** が 0x92D0907B

というパターンだ.この障害は直ちにはシューティングできない可能性もあるので(いじっているうち消えてしまう可能性もある)現状をサンプルも入れて別にバックアップしておこう.何か止めているオプションで影響しそうなものはあるだろうか?「ファイル保存時バッファを予約する」などというのは可能性がありそうだ.バックアップタイマーのインターバルを縮めて再現できるかどうか見てみよう.現行では1分タイマーになっているので,10秒に短縮してみる.

どうも効きが悪い.この値を短くしても効果はない.これはタイマーが作動する「インターバル」でタイムアップするまでの時間ではない.この値はユーザの設定値でレジストリに保持されている.しかもこの値は1分単位だ.とりあえず,1分に変えてみよう.少しは速くなったようだ.RESCUEMINUTEという変数に格納されているので,これを直接変えることもできる.あるいは残り時間の計算をいじった方が早いかもしれない.⇒確かに4回目で起きている.比較対象のタイムスタンプが分単位なので分より速くはできない.今度は3回目で起きた.

OPENQUICKDB(ファイル保存時バッファを予約する)をオンにしてみよう.⇒確かにこれだ.すでに9回「書き込み」が発生しているが,例外は起きていない.つまり,このオプションも必須ということになる.バッファを使わないという方法もあるいは可能なのかも知れないが少なくとも現状ではそうなっていない.これでSPECIFICATIONに入るものが3つになった.「CHUNKFILEクラスを実装する」を含めそれ以外のPENDINGのかなりのものはOPTIONSに移すことができる.

もう一つサブカテゴリ(タグ)としてDEFECTIVEというのを作っておこう.これは「欠陥がある」という意味ではINCOMPLETEと同じだが,INCOMPLETEは完全なものに仕上げる必要があるのに対し,DEFECTIVEは「当面使用禁止」というもので放置しておくことができるものだ.TESTに入っているオプションはほとんどそこに入ると思う.INCOMPLETEはnodule.hが一元管理しているが,DEFECTIVEはそのオプションが配置された特定ヘッダファイルの中で個別管理することにする.⇒DEFECTIVEはタグではなく,#if defined(DEFECTIVE)のブロックで隔離するだけでよい.多分これで相当整理されると思う.RESCUEINTERVALは1分では粗すぎるので,20秒にしておこう.

PENDINGの中からOPTIONSに移せるものを拾い出してみよう.テストを公平なものにするために,DEBUGとVERIFYはすべて止めておくことにする.その上でオンで動くものはOPTIONS候補だ.IMPLEMENTCHUNKFILE(CHUNKFILEクラスを実装する)もSPECIFICATIONとしておこう.カード写真イメージが読めないというのは致命的ではないが,欠陥であることは明らかだ.PENDINGのうちデフォルトでオンの項目すべて(17件)をオンにしてテストしてみよう.サンプルはZTシステム構成図7.ZELとする.

上記設定で起動→部分図→全体図でエラーが発生した.PAIRBOX::getLocationで(Deleted())が起きている.「一時的にSTOPを抑制する@20201026」で止めてあったところだ.シューティングを試みることにしよう.PAIRBOX::getLocationで失敗している.⇒getLocationを呼び出す前にすでに「ノード対世代不一致」が検出されている.このノード対はすでに削除されてゴミ箱の中に入っている.StillAliveという検査をすり抜けている.なぜだろう?StillAliveでは#2434が返っている.USERECYCLESYSTEM (描画オブジェクトのリサイクルシステムを用いる)はオンになっている.「DEBUGとVERIFYはすべて止めておく」という設定にはなっていない.

確かにここではまだ死んでいない.PairBoxGeneChangeを実行した時点で削除されている.PairBoxGeneChangeでは「世代差ゼロ以下でノード対を破棄」が実行される.この関数はnewboxを返しているので戻り値を見れば成功したか否かは判定できる.それをやっていないのが致命的だ.⇒対処した.

USERECYCLESYSTEM(描画オブジェクトのリサイクルシステムを用いる)をオフにしているのに,削除されたオブジェクトがゴミ箱に入っている.USERECYCLESYSTEMというのはリサイクルを実施する/しないではなく,リサイクルシステムを使っているときにはCARDLINKとMARGLINKを初期化する必要があるというだけの話だ.これをオフにしても動作しているのは,特に必要な処理ではないということを意味するように思われる.ゴミ箱はシステム木のルートであるCOUPLINGを生成する以前に生成されている.⇒ゴミ箱を生成しないようにしてみたが問題なく動作している.オブジェクトはフロート状態になっているが,アクセスは可能だ.つまり,ゴミ箱はなくても動く.deleteしても直ちには削除されず,Nringが管理しているのだろう.

リサイクルシステムはCANが空のときは実質無動作になるので,USERECYCLESYSTEMのときはCANを生成しないようにすれば,名実ともに「リサイクルシステムを用いる」の言葉が活きてくる.リサイクルシステムはなければ動作しないというものではないから,SPECIFICATIONというよりはOPTIONSとすべきだろう.

上記サンプルでDEBUG, VERIFYはすべてオフ,PENDINGのデフォルトオンはすべてオン,OPTIONSもすべてオンの環境で,源氏物語全系譜6.ZELを開いて停止した.(primarynode->getmarglink() != getOyalink())が起きている.障害はTRIBEBOX::decidePrimaryNodeで起きている.インストールされているアプリでは以下のような別の障害が起きる.この版は2.2.0.015 R 2020-11-05だ.

image

この後も操作の度にエラーが出る.源氏でこんなにエラーが出るなんて信じられない.どこかで間違ってしまったのではないだろうか?再開発スタート版を見てみよう.その前に開発機でバックアップを取っておこう.⇒確かにスタート版ですでに起きている.この版を採用するとき十分なチェックをしたつもりだったのだが…ZELKOVA_2020-10-12★では,「非連結グラフ」というのは出るが,ここまでひどくはない.ZELKOVA_2020-10-24では問題なく開ける.再開発スタートは10月14日だから,それより後の版だ…無傷で開けるのは10月24日が最後でその後は重婚同類検定で以下のようなエラーが出るようになる.

image

リリース版は滅多に作らないので日付と内容が一致しているとは限らない…デフォルトでオンのオプションをすべてオンに戻してみたが,動作は変わらない.ともかく,少し追いかけてみよう.どうもかなりおかしい.TRIBEBOX::Oyalinkには値を設定しているところがない.だからつねに空だ.10月24日版で見ると,ASSERT_NEVERの行は「仮修正@20190129」としてコメントアウトしてある.どうもそれを不用意に外してしまったようだ.TRIBEBOX::Oyalinkには値が設定されていないのだから,このパラメータはすでに廃止になっているのと同じだ.その方針で修正してみよう.

勘違いしていた.Oyalinkは活きている.これがなくては動かない.Oyalinkというのは,「従系列に属する優先仮ノードの親の結婚リンクへの参照」だ.いまの場合は軒端の荻が優先ノード,主系列は光源氏のいる一院系列,従系列は配偶者の蔵人の少将が先祖ノードになっている.軒端の荻の親の伊予介は先祖ノードで別の系列を立てている.軒端の荻の出現箇所は以上の3箇所だが,蔵人の少将の系列では配偶者ポジションなので親はいない.つまり,Oyalinkは空だ.従って,優先ノードのgetmarglinkと系列のgetOyalinkが一致することはあり得ない.しかし,それ以外の論理には抜けはないと思われるので,ASSERTION行だけの誤りということになる.以下のような記録がある.

@20190129 → 廃止@20201107 decidePrimaryNode,SetMinorTribe

仮修正@20190129を廃止したという意味だろう.decidePrimaryNodeというのはいまの問題箇所,SetMinorTribeにも同様の(おそらく)誤りがあるが,この2箇所にはSTOP文が入っているため,間違っていても停止するだろうという見込み(安全を見込んで)で廃止で確定したのだろう.@20190129の修正は3箇所あって,もう一つのNAMEBOX::IfBTWPossibleの修正は活きている.

もう一度DEBUGとVERIFYのオンをオフに戻して次に進もう.SEPARATEVHZONEはすでにどこからも参照されていないので廃止.HORIZONTALQUICKARANGE,BESTEFFORTCHANNELTROUBLEも同様.ファイルへ出力をサポートしない@20180314はいまのところは「仕様」と考えられるのでSPECIFICATIONに移しておこう.PUREBLOODLINE 純正血統図をサポートするも完全に仕様だが,オフでどういう動作になるのか見ておく必要がある.

多重カードのカウントがおかしい.ZTシステム構成図7.ZELで多重が10件以上出ているのに,表示では多重ゼロ,不可避ゼロとなっている.

image

いや,何か勘違いしていたようだ.上の図では多重14で合っている.ただし,不可避が1で多重が14も出るというのはよく分からない.重婚同類循環は3となっている.

▲書き込み:zoom_normal=0.322251 preview=0.034721の表示にタイムスタンプを追加する⇒タイマーのインターバルを確認したい

こうなったらまた出戻る以外の選択肢はない

とんでもないことになってしまった.大きな過ちを冒していた.こうなったらまた出戻る以外の選択肢はない.リスタートポジションは確保したのでもう一度やり直すことにしよう.大部分の修正はヘッダファイルの書き換えなので,修正がそのまま再利用できるから必要な作業は最小限のものに留まる見込みだ.おそらく今日中にもクリアできるだろう.

この作業は二つのポイントから見て重要だ.条件コンパイルは基本的にプログラム開発の分岐点に相当するから,分岐点がp個あれば,観念的には2^pの異なるバージョンが存在することになる.仮に条件コンパイル文が100個存在するとすれば,

2^100=1,267,650,600,228,229,401,496,703,205,376

もの異種バージョンが存在し得ることになる.修正をフィックスするという作業の目的はこの分岐点を最終的にはゼロ個まで削減し,2^0=1,つまり,単一バージョンまで統合することにある.この状態になって始めてプログラムは「正式版」と呼ばれることになるだろう.

条件コンパイルのもう一つの解釈は,それがプログラム開発の履歴を表現するものとなっているというポイントだ.つまり,「分岐点」というのは一つの新しい「進化」の方向性であり,プログラムの成長を促す「新芽」であると考えられる.修正をフィックスした時点でその分岐に付けられたコメントは「修正履歴」として記録されるべきだ.この「履歴」はプログラムの開発途上で「経験」したさまざまな事象の蓄積であるとも考えられる.この「経験された事象」は具体的には「障害」ないし「障壁」であると考えられるから,それに対処するために実装された「対策」を体系化したものが「免疫系」と呼ばれるシステムであり,「自己の恒真性を維持するための仕組み」であるとも言える.

さて,ぼちぼち始めることにしよう.現状では#defineで定義された値を持たないキーワード(コンパイルオプション)は226箇所ある.昨日確認された186件に比べると大分多いが,これはヘッダファイルが最新でC++ソースファイルが古いバージョンに戻っているため重複が発生していることによる.まず,この重複を解消するところから始めよう.昨日Meryを再インストールして「並び替え」マクロを使うことができるようになったので,この作業は簡単だ.検索結果をMeryで並び替えして重複行をチェックするだけでよい.⇒対処した.

#define\s+\S*\s*(/|$) (C)

で検索すると,まだ200個残っている.14件のダブリが残っているが,これはリネームしているものがあるためと思われる.DLLだけバックアップを取って進めることにしよう.現状では#ifdef が372個,#ifndef が59個,#if defined が429個,#if !defined が44個ある.#if の方はすでに原則としてすでに始末されていると考えられるので,#ifdef, #ifndef の431個を個別にチェックしてゆくことにする.#ifdef, #ifndef を #if の形に書き換えるのは正規表現を使って一括処理できるはずだから,まず,以下のような修正を実施することにする.

  1. #ifdef, #ifndef の条件式(キーワード)が#define によって定義済みのときは何もしない
  2. キーワードが未定義のときは,現状のままフィックスする つまり,グレー表示のアクティブでないブロックを削除し,平文に戻す アクティブでないブロックが存在しないときも同じ
  3. キーワードが未定義でアクティブなブロックが存在しないときは,x行削除のコメントを残してアクティブでないブロックを抹消する
  4. ただし,内容に疑義があるとき,ないしそのオプションの存続が必要と認められるときには,新たなキーワードを定義し,負論理を正論理に改めた上で条件分岐文を温存する
  5. 特定ヘッダファイル(nodule.h, comdebug.h, coupling.h, Bobject.h)以外のファイルにある#define文はLOCAL: に属するものを除いてすべて抹消する
  6. 「衝突検定で計算誤差を許容する」に関わる論理はすべて温存して後日再検証することにする 未定義のものに関しては新たにキーワードを定義し,「衝突検定で計算誤差を許容する」という表記とMAXKEISANGOSAの適用が「同期」するように真偽値を定める 
  7. ただし,「計算誤差を見る」の類は現状(AboutEqualを使う論理)でフィックスする 「計算誤差を無視する@20170509」も確定する 「MAXKEISANGOSAを適用する@20171229」は現状でフィックスした
  8. 「この関数は最高速で実行されるべきだ」の類は「速度最優先オフ」に統合する
  9. キーワードが定義済か否かの検査を「定義をここに表示」メニューで判定してはならない 定義はあってもコメントアウトされている場合があるので,つねに「実検索」の結果を見て判断する必要がある
  10. 何かの理由で温存したい論理がある場合には,if 0/1 のような表記を認める
  11. 「配偶者の性別不問とした@20180201」は未定義だが,新設してPENDINGとする 「メタファイルに出力する@20180312」も未定義だが,PENDINGとした

コンパイルエラーが発生した ⇒最優先オフをオンにしたためだ.checkブロックの内容が古くなっていた.⇒対処した.

非アクティブブロックがグレー表示されなくなってしまった.これでは修正を誤る可能性が高い.⇒ビルドし直したら正常に戻った.⇒ダメだ.また出てきた.今度はビルドして戻らない.一旦VSを落として再起動でようやく正常に戻った.

古いコードでDEBUGPRINTというマクロが見当たらない.⇒空のマクロを作っておく.DEBUGDUMPというキーワードはマクロとオプションの両方に使われている.整変数に使っているところもある.整変数はiDEBUGDUMPとしてみよう.マクロはmDEBUGDUMPとする.

NAMEBOX:: PaintNameFrameは2つの関数から呼び出されている.デバッグ時にはNAMEBOX::Drawから,リリース版ではNAMEBOX::DrawNameからという変則的な呼び出しになっている.おかしいので,つねにNAMEBOX::Drawで実行するように書き換えた.

#ifdef と#ifndef の点検は完了した.あとは整理するだけとなった.その前に#ifdef を#if defined の形式に変換してしまおう.いや,その前に#defineされて使われていない定義というのがあるはずなので,それを先に片付けてしまおう.⇒#defineの個数は200個になった.増減があるので,多分これで正しいのだと思う.バックアップを取っておこう.#ifdef から #if defined への一括変換は昨日やっている.

検索:#ifdef (.+)\b
置換:#if defined($1)

ただし,この方式ではコメントが入っている行ではコンパイルエラーが発生する.コメント付きの場合にも適用できるようにしたい…

検索:#ifdef\s+(\S*)(/|\b)*?   (S)
置換:#if defined($1)       (R)

これでなんとか行けそうだ.#ifdef は140個ある.140個置換した.⇒うまくいった!ビルドも通った.#ifndef も同じ要領でやっておこう.#ifndef は61個ある.⇒問題なく変換できた.さて,この200個の#define文が1個になるまで削減するというのだが,可能だろうか?とりあえず,やり易いところからやってゆくことにしよう.

まず,ローカルに定義されているLOCAL: は整変数に変えてしまってもよいだろう.実際この方LO法は現在すでに使われている.#define DISPALWAYS を int DISPALWAYS に変えるというだけだ.⇒これで定義文は7個減って193になった.残りはすべて特定ヘッダファイルだけということになる.

これはダンプルーチンなどをシステムの装置に組み込むということを意味する.計測装置をシステムに組み込むというのは,たとえば車にスピードメーターを付けるようなものであり,もしロジックが混み合うようなことがあれば,その部分だけ切り出してルーチン化してやればよい.Bobject.hにはDEBUG:という区分でデバッグ用の定義がどっさり入っているが,装置化できるものは装置化してしまうのが一番よいと思う.少なくとも1つのファイル内で完結している定義に関してはそれをやるべきだろう.いままでやってきたことと真逆のことをやることになるが,それも仕方ない.⇒この修正は一旦撤回することにする.

▲comdebug.hの古いプリント文などの定義は廃棄してもよいのではないか?

▲_DEBUG_,XDEBUGなどの動作をチェックする必要がある 古いバージョンは廃棄した方がよい

すべての定義文をデフォルト(=現状)でオンとデフォルトでオフに区分けして管理する その上ですべての定義文をオンにして動作を確認する また,すべての定義文をオフにして動作を確認する ただし,comdebug.hとcoupling.hの管理する分は除く(ということは対象はnodule.hとBobject.h内の定義文に限定される

まず,すべてのスイッチオンを試してみる.コンパイルエラーが出ている.MERGETESTCARDIMAGEでbool NOCARDIMAGEを初期化しているが,参照されていない.⇒#if 0 で仮止めした.COLCOLORSEGMENTやCOLVERTICALSEGMENTで使っているHというパラメータが未定義.⇒対処した.COLVERTICALSEGMENTで使っているvsegmentという変数が未定義.⇒垂直セグメント検定という機構は完全に廃止されている.SIZEOVERAUTOZOOMOUTでLimitSizeOverの引数が足りない.⇒補充した.⇒とりあえず動いた.

▲TESTタグのPRESETPARAMETERを実行するとNAMEBOX:EraseGhostNode→setCriticalでエラーが発生する.チャンネル不足が発生しているものと思われる.チャンネル数自動ならエラーは発生しない.このエラーを仮修正でパスすると,MakePairListClean中にnoduleのデストラクタで非参照カウントの残留が発生する.サンプルはゼルコバの木モジュール構成図 TEST.ZEL.エラーを無視して描画は可能.PAIRLIST→PAIRBOXの参照が残っている.TESTPERFECTでも同じエラーになる.⇒いや,これはプリセット値が入っていたためと思われる.しかし,自動テストの動作にはなっていない…

▲PENDINGをすべてオン,COMDEBUGは無指定,それ以外のすべてのオプションをオフにしてエラーが発生する.スプリットが発生しているが,それ以上に画面が壊れている.