C++のゼロクリアとメモリリーク

C++のゼロクリア処理

古いC++のソースコードでは、配列や構造体の格納領域全てを0で埋めることで、初期化処理としていることがあります。memset関数やZeroMemoryマクロを使います。

struct Struct1{
        char charArray[256];
        int val;
};

int main()
{
        Struct1 test1;

        // 構造体のゼロクリア
        ZeroMemory(&test1, sizeof test1);
}

メモリリークチェック

_CrtDumpMemoryLeaks()というデバッグ実行中に未開放のメモリ領域があるかどうかを調べる関数が存在します。以下の処理ではリークが起こらないので、特に何も出力されません。

struct Struct1{
        char charArray[256];
        int val;
};

int main()
{
        {
                Struct1 test1{}; // {}は初期化のおまじない

                strcat(test1.charArray, "aaaaaaaaaaaaaaaaaaaaaaaaaa");

                // 構造体のゼロクリア
                ZeroMemory(&test1, sizeof test1);

                strcat(test1.charArray, "bbbbbbbbbbbbbbbbbbbbbbbbbb");
        }

        // リークチェック
        _CrtDumpMemoryLeaks();
}

文字列要素の持ち方を変える

ここで、文字列をchar配列で持つのは今後のメンテナンスにも不便なので、std::stringというコンテナクラスに置き換えます。

struct Struct2 {
        std::string str;
        int val;
};

int main()
{
        {
                Struct2 test2{}; // {}は初期化のおまじない

                test2.str = "cccccccccccccccccccccccccc";

                // 構造体のゼロクリア
                ZeroMemory(&test2, sizeof test2);

                test2.str = "dddddddddddddddddddddddddd";
        }

        // リークチェック
        _CrtDumpMemoryLeaks();
}

これを実行すると、今度は以下のようにメモリリークしているという情報が出力されます。

Detected memory leaks!
Dumping objects ->
{149} normal block at 0x001DC508, 32 bytes long.
 Data: <cccccccccccccccc> 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 63 
{148} normal block at 0x001DD988, 8 bytes long.
 Data: <X       > 58 FB CF 00 00 00 00 00 
Object dump complete.

ゼロクリアする前に書き込んだ、"cccccccccccccccc…"というデータがスコープを抜けても解放することができなかったということがわかります。

std::stringコンテナクラスには、キャパシティーや実際の文字列が存在する領域のアドレスなどが入っています。それをゼロクリアするということは、数値要素は単純に0になるだけですが、アドレスが急に0になると、その先で確保していた領域がどこか忘れてしまうということです。

そのため、その次に"dddddddddddddd…"という文字列の代入をした時点で本来なら解放されるはずの"cccccccccccccccc…"が解放できなくなっているということです。

というわけで、ちょっとでもクラスを扱う可能性のあるプログラムでは、基本的にゼロクリアは使用しないようにしましょう。