potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

C++&Visual Studio 便利なデバッグ変数情報の視覚化(natvis)と関数使用時の注意

Visual Studio 2022はC++でユーザー定義のデバッグ変数ウィンドウ視覚化に対応しています(natvis)。この機能でクラスのメンバー関数を使おうとしたらいきなり適用されなくなり戸惑ったので現象と解決策を共有します。

natvisの紹介

次の2つのファイルmain.cpptest.natvisC++プロジェクトにあるとtest1だけ変数情報がカスタマイズされます。

main.cpp

class test1
{
public:
    int x;
};

class test2
{
public:
    int x;
};

int main()
{
    test1 t1;
    test2 t2;

    return 0;
}

test.natvis

<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> 
    <Type Name="test1">
        <DisplayString>test1 {x}</DisplayString>
        <Expand>
            <Item Name="value">x</Item>
        </Expand>
    </Type>
</AutoVisualizer>

natvisは拡張子.natvisのファイルで、Visual Studio 2022であれば新しい項目の追加ダイアログの「Visual C++➡ユーティリティ➡デバッガー視覚化ファイル (.natvis)」から骨組みを作成できます。Microsoft Learnには分かりやすい公式情報「Natvis フレームワークを使用してデバッガーで C++ オブジェクトのカスタム ビューを作成する」があり、SDKをインストールしていれば参考資料としてSTLのnatvisも確認できます。

STLのnatvis:C:\Program Files\Microsoft Visual Studio\2022\Preview\Common7\Packages\Debugger\Visualizers(プレビュー版、Cドライブインストール時)

natvisはメンバー関数の情報も表示できます。次のようにnatvisで関数f1()を書けば結果が表示できます。

class test1
{
public:
    auto f1() { return 123; }
};

int main()
{
    test1 t1;

    return t1.f1();
}
<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> 
    <Type Name="test1">
        <DisplayString>test1 {f1()}</DisplayString>
        <Expand>
            <Item Name="[value]">f1()</Item>
        </Expand>
    </Type>
</AutoVisualizer>

うまく表示されない場合はローカル変数ウィンドウなどを右クリックして関数の評価を有効にします。

natvisで関数が消える場合と対処

便利なnatvisなのですが、急に適用されなくなり困りました。結論はコンパイラによる最適化で使わない関数が削除されたことです。

次のコードを実行するとこれまで視覚化されたtest1の表示がnatvis適用前になります。

class test1
{
public:
    auto f1() { return 123; }
    auto f2() { return 456; }
};

int main()
{
    test1 t1;

    return t1.f1();
}
<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> 
    <Type Name="test1">
        <DisplayString>test1 {f1()},{f2()}</DisplayString>
        <Expand>
            <Item Name="[value1]">f1()</Item>
            <Item Name="[value2]">f2()</Item>
        </Expand>
    </Type>
</AutoVisualizer>

調べた結果、natvisで参照している関数f2()コンパイラ最適化による削除が原因でした。解決策はf2を意図的に呼び出すか、natvisで省略可能に設定するか、プロジェクト設定の変更です。ただし、どの方法もクラスを使う側の対応が必要です。

class test1
{
public:
    auto f1() { return 123; }
    auto f2() { return 456; }
};

int main()
{
    test1 t1;

    auto x = t1.f1() + t1.f2();

    return x;
}

または

<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> 
    <Type Name="test1">
        <DisplayString>test1 {f1()},{f2()}</DisplayString>
        <Expand>
            <Item Name="[value1]">f1()</Item>
            <Item Name="[value2]" Optional="true">f2()</Item>
        </Expand>
    </Type>
</AutoVisualizer>

または

解決策

STLのnatvis(stl.natvis)では表示情報として内部変数が積極的に使われています。これは内部変数のクラス・構造体(使われた変数のクラス・構造体)は最適化で消えないからだと思います。メモリ使用量の考慮は必要ですが、手っ取り早い対応は以下のどれかかなと思います。

  1. 関数を使うnatvis定義にはOptional="true"を設定する。
  2. クラス側で表示情報を内部変数に持つ。
  3. デバッグ時は不使用関数を削除しない(STLの多用時はたぶん重くなります)。