C言語で文字列の領域をmallocで確保する際に気を付けること

はじめに

こんにちは、ばりです。最近C言語の復習をしてます(といってもVisual Studio2022で.cppに書き込んでる)。C言語といえば文字列はchar型の配列で扱うわけですが、やっぱり動的に配列サイズを確保したいですよね。char[100] str;みたいにデカ配列用いすればええやろ!みたいなのあんまりスマートじゃないですし…。と思ってmallocを使って文字列確保をしようとしたら、ドツボにハマって結局6時間くらい溶かしました。原因がかなり初歩的な内容だったので戒めを込めて残しておきます。

結論

文字を確保する際は終端文字の領域をちゃんと確保しよう!!

// ヘッダファイルは省略

int main(void)
{
    char str[] = "hogehoge";
    char strSize = (strlen(str) + 1); // ここで終端文字'\0'の分までサイズを設定する

    // 文字列のサイズ分のメモリを確保
    char* hoge = (char*)malloc(sizeof(char)* strSize);
    // 文字列の内容をコピー
    strcpy_s(hoge, sizeof(char) * strSize, str);

    // ****************
 // いろいろな処理
    // ****************

    // メモリを解放
    if (hoge) {
        free(hoge);
        hoge = NULL;
    }
    return 0;
}

(文字列コピーして終わりって何がしたいんだって話ですけど、もともとは引数の文字列を取得するみたいなコードでした)

C言語では文字列の最後に\0を用意するなんて当たり前なんですけど、これに気づかず頭を抱えていたわけですね…。

ついでに、終端文字の存在に途中で気づいてコードをいじったことが余計にドツボにハマるきっかけになりました。 6時間も溶かした詳細を知りたい方は以下もどうぞ。

なにをしていたか

int main(void)
{
    char str[] = "hogehoge";
    char strSize = strlen(str); 

    char* hoge = (char*)malloc(sizeof(char)* strSize); // 終端文字を考慮せずにメモリを確保
    strcpy_s(hoge, sizeof(char) * (strSize + 1), str); // なぜかここでは終端文字を考慮してますよって噓をついたコード(?????)

    // ****************
 // いろいろな処理
    // ****************

    if (hoge) {
        free(hoge);    // エラー
        hoge = NULL;
    }
    return 0;
}

最初はstrSizeをそのまま文字列サイズでおいてたわけです。 これをすると実行時にエラーを吐きます。

HEAP CORRUPTION DETECTED CRT detected that the application wrote to memory after end of heap buffer

「アプリケーションが、ヒープバッファが終わった後の部分にも書き込もうとしてるぜ!」

char* hoge は終端文字の情報を持っていないので、まあ当然っちゃ当然ですね。

ただ、確保したメモリ以上の内容を書き込もうとしてるはずのstrcpy_sの箇所ではエラーは出てないんですよね。

エラーが出力されるのはfree(hoge)の部分。

僕がstrcpy_sの引数で嘘をついたので、「ほなええか!」とエラー吐かず通過してしまったぽい。 strcpy_sは書き込み先のBufferが明らかに足りてなかったらエラーはいてくれたっていいじゃないか

なのでメモリ解放の際に終端文字を読み込むまで処理を進めていこうとしたら、終端文字よりも先に確保部分が終わってしまったため、「解放したい部分のメモリ、自分の土地の外にあるんだけど!」のエラーですね(多分)。

しかし、strcpy_sの直後にprintf("%s", hoge)するとちゃんとhogehogeが出力されます。

なので「あれ?文字列はちゃんと格納できてるよな…」となって原因特定に時間がかかってしまったみたいですね(他人事)。


仮に、以下のように

int main(void)
{
    char str[] = "hogehoge";
    char strSize = strlen(str); 

    char* hoge = (char*)malloc(sizeof(char)* strSize); 
    strcpy_s(hoge, sizeof(char) * strSize, str); // 嘘をついていない正しいサイズを伝えているコード // エラー

    // ****************
 // いろいろな処理
    // ****************

    if (hoge) {
        free(hoge);  
        hoge = NULL;
    }
    return 0;
}

とすると

L ”Buffer is too small

「バッファが小さすぎるぜ!」

と、ちゃんとstrcpy_sの部分がエラーを吐いてくれます。

なので今回のポイントは

終端文字の知識が浅かった上に中途半端に変数を変えて試行錯誤したせいでさらに複雑化した

の部分かなと思います。

反省

下手に原因がよくわからないままいろいろな値を試してみる、のような試行錯誤はよくないなぁと実感。 まず何が原因でエラーが起こっているかを吟味して、そのうえで解決のためのコードを書くことを意識しようと思いました(小並感)。