久々にC++でプログラムを書きたくなったので始めたCalc。

 

プログラムイメージは直ぐにできたのですが、直ぐに古希を迎えた「脳体力」の限界を感じ始めます。

 

一応は書いてみたのですが、プログラムイメージにしっくりこない。「あーでもない、こーでもない」と試行錯誤を繰り返して、そうこうするうちに一応の形を見ますが、同時に

 

(1)演算式の文字列にU+0001Hという制御コードが混入し、

(2)文字列の最終にヌル終端(L'\0'-U+0000H)を置いていたのに、それが知らないうちに空白(U+0020H)に変わるという、

 

奇怪な現象

 

を経験します。自分でも解析し、プログラムコードに致命的欠陥がないので、Chat-GPT様にも相談しますが、原因が分からないまま、一応の対処療法を施しました。

 

あー、一安心

 

と思ったのもつかの間、(これは疑いもなく私のミスですが)「整数化できない入力ミス」によってスタックオーバーフローが生じ、プログラムが落ちるというバグを発見したことから、「ユーザー入力ミス対応が脆弱」であることが判明しました。しかし、これが

 

怪我の功名

 

でバグフィックス中に「newでメモリーに確保した文字列(wchar_t配列)のデータが知らない間に書き換えられる瞬間」を確認することが出来ました。

 

 

↑の出力画像の上の赤枠は括弧内の演算式文字列「'-' (45),' ' (32), '9' (57),'' (0)」(即ち"... - 9")で、"38035334"のメモリーアドレスにあります。この後括弧内の演算式(紫枠)の評価処理があり、戻ってきて'-'を読んだ後が"38035336"(wchar_tは1文字2バイト)のメモリーアドレスにある下の赤枠の文字列になりますが、「' ' (32), '9' (57), '' (1),'' (0)」とあるのがお判りでしょうか?

 

これはキャラクターコンタミだと思っていたU+0001Hコードであり、ここでハタと「キャラクターコンタミ」ではなく、

 

L'\0'がL' '(空白)に変わったように、L'\0'がL'\1'(と続くL'\0')に書き換えられた

 

のだ、ということが判りました。要すれば全ての奇怪現象の原因は知らない間のメモリー書き換えという

 

真実はひとつ

 

だった、と考えられ、且つ考えること合理性があるということです。

 

では、それはどこで発生したのか?と考えた時にそれは「クラス内クラスで定義したCPOSクラスの仕業」に違いない!

 

    ////////////////////////////////
    //クラス内クラス Part Of String
    //st - ed 間の文字列を保有し返す
    ////////////////////////////////

    class CPOS
    {
    private:
        wchar_t* m_partstr = nullptr;    //解説:切り取り文字列用ポインターです。(初期値はnullptr-ゼロ)
    public:
        CPOS() {    //解説:コンストラクターです。
            m_partstr = new wchar_t[1];    //解説:1文字(2バイト)の配列を作成(メモリーを確保)
            *m_partstr = L'\0';    //解説:そこにヌル終端を置く。
        }
        CPOS(wchar_t* st, wchar_t* ed) {    //解説:引数付きコンストラクターの場合はSet関数を呼ぶ。
            Set(st, ed);
        }
        ~CPOS() {    //解説:デストラクターです。
            delete [] m_partstr;    //解説:newで確保したメモリーはdeleteで確実に開放する。
        }
        void Set(wchar_t* st, wchar_t* ed) {    //解説:文字列の切り取り処理です。
            if(!m_partstr)    //解説:nullptrであれば、

                delete [] m_partstr;    //解説:既成の文字列を開放します。
            if(ed >= st) {    //引数順序st (<=) ed解説:使用はstが開始文字、edが終了文字です)
                int len = ed - st + 1;    //解説:文字列の長さです。
                m_partstr = new wchar_t[len + 1];    //解説:+1しているのはヌル終端分が必要だからです。
                wcsncpy(m_partstr, st, len);    //解説:開始文字から終了文字までコピーし、
                m_partstr[len] = L'\0';    //解説:最後にヌル終端を入れます。
            }
            else {            //引数順序間違い対応

                //解説:エラー対応としてstとedを間違えても大丈夫なように入れ替えています。)
                int len = st - ed + 1;
                m_partstr = new wchar_t[len + 1];
                wcsncpy(m_partstr, ed, len);
                m_partstr[len] = L'\0';
            }
        }
        wchar_t* Get() {    //解説:切り取った文字列を取得します。
            return m_partstr;
        }
    };

 

見た限りにおいて全く問題が無いように見えます

 

が、

 

試しに、このプログラムのnewする長さを「+1(2バイト)」増やし、ヌルを追加するようにした所、総ての

 

奇怪現象

 

が消えました。矢張り元々はm_formulaという配列をnewで確保し、そこから都度CPOSインスタンスで文字列を一部newでコピー文字列を作成して解釈処理を行っていましたが、このローカル変数用メモリー達が最後の所でオーバーラップ(上書き)していたということです。

 

この「ヌル二重化対応」で行こうと思いましたが、Chat-GPT様が「手動でポインタを操作してメモリを管理する方法から、標準ライブラリ(例: std::wstringstd::string)への移行を検討してください。これにより、ポインタの解放忘れやオーバーランといったメモリ関連のバグを大幅に削減できます。」というので完全に書き換えました。

 

    ////////////////////////////////
    //クラス内クラス Part Of String
    //st - ed 間の文字列を保有し返す
    ////////////////////////////////

    class CPOS
    {
    private:
        wstring m_partstr;    //解説:可変長のUnicocde(ワイド文字)用文字列クラス
    public:
        CPOS() : m_partstr(L"") {}
        CPOS(const wchar_t* st, const wchar_t* ed) {
            Set(st, ed);
        }
        void Set(const wchar_t* st, const wchar_t* ed) {
            if(ed >= st)    //引数順序st (<=) ed
                m_partstr = wstring(st, ed + 1);
            else            //引数順序間違い対応
                m_partstr = wstring(ed, st + 1);
        }
        const wchar_t* Get() const {
            return m_partstr.c_str();
        }
    };
 

矢張りChat-GPT様の言われる通り、wstringクラスを使うようになってメモリーオーバーラップ現象は消失しました。

 

イヤー、疲れた(><)

 

というのが本音ですが、当初の目的は達成したといえます。

 

ps. 一応「お詫び」で紹介したバグは全てフィクスしました。序にWindows版のテストプログラムも作りました。

こいつのよい所は、(医療費控除などで行う)卓上計算機が出来ない一部のキーミスを修正して長い加減算を再実行できることです。

何れ紹介しましょう。