displayX
@displayX (あーる)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

C言語で、free()できません

解決したいこと

今、「与えた文字列をセパレータで分割して、配列を返す関数」を作っています。
JSの split() みたいなやつです。

しかし、上手く free() できずに困っています。

発生している問題・エラー

// 文字列 str を sep で分割して配列にして返します
char** split(char str[], const char sep[], int len) {
     // 元の配列を傷つけたくないので、新しい文字列にコピーして処理する
     char* str2 = (char*)malloc(len);
     strcpy_s(str2, len, str);

     // 処理の結果を入れる
     char** res = (char**)malloc(len);

    char* ctx;
    char* token = strtok_s(str2, (char*)sep, &ctx);
    int i = 0;
    while (token != NULL) {
        res[i] = token;
        token = strtok_s(NULL, sep, &ctx);
        i++;
    }
    // free() したらバグる・・・
    //free(str2);
    return res;
}
int main() {
    char s[] = "aa,bbbbb,ccccc";
    int len = sizeof(s) / sizeof(s[0]);
    char** r = split(s, ",", len); // 上で定義した関数です

    // きちんと r に値が入っているか確認
    for (int i = 0; i < 3; ++i) {
        printf("%s\n", r[i]);
    }
    printf("元の文字列%s", s); // 元の文字列が傷ついてないか
    return 0;
}

新しい配列の宣言時に、サイズが固定されているとマズイので malloc() してます。

return で出来上がった配列を戻しているのは、これ以上引数を増やしたくないからです(あとJSのsplit()に似せるため)

これって、関数が終わったら str2 と res は free() されてるんですか?
それともポインタ変数のみが破棄されてるんですか?
関数内で確保した動的メモリも free() しないとマズイでしょうか?

0

2Answer

実際に動かしていないので指摘があっているかわかりませんが、resにstrtokで切り出したstr2のポインタが格納されているので、freeしたあとにアクセスしようとしておかしくなっているのでないでしょうか?。

char str[] = "a,b,c", sep[] = ",";だったとして

str2をstrtokしたあとはa\0b\0c\0になってresにはそれぞれ

res[0] = str2[0];
res[1] = str2[2];
res[2] = str2[4];
//res[3]~res[5]は不定値

が入っていると思います。

あと、この処理の場合いくつに分割されているか受け取り側では把握できないので、引数をポインタのポインタで取ってアドレスを返すようにして戻り値に個数を返すか、値の入っていないところはNULLを入れるか等の工夫が必要なように思えます。

0Like

Comments

  1. @displayX

    Questioner

    希望の振る舞いとは違いますが、それでやってみます。
    (引数でポインタのポインタを受け取って、戻り値で個数を返す)
    現状でfree()出来ないし問題あると思うので。
    ありがとうございます。

resに対して動的に文字列を格納する方式でしたら

  while (token != NULL) {
        size_t len = strlen(token);
        res[i] = (char*) malloc(len+1);
        strcpy(res, token);
        token = strtok_s(NULL, sep, &ctx);
        i++;
    }

のようにすればstr2は不要になると思います。
呼び出し側で各文字列に対して解放が必要になってきますが
個人的には元の方式含め関数内でメモリ確保して返すのはメモリ管理が難しくなりリークの原因になるので避けたいところです。
どうしても必要なら関数名を明示するような命名にします。

C言語ですと

  • 配列はポインタ計算の糖衣構文
  • 文字列は末尾NULLのchar配列
  • 関数はオブジェクトに所属しない
    ので他言語のような使い勝手は簡単にはできないです。

他言語のListに当たるデータ構造(配列、長さ、前後の要素のポインタをもつ構造体等)とそれを操作する関数を実装するのも一つの手ですが、デストラクタがないのでメモリ管理から逃げられないのは変わりません。

0Like

Comments

  1. @displayX

    Questioner

    仰られる通りにしたらfree()関係は上手く行きましたが、str が "aa" になってしまいました・・・。

    str2を作ったのは、元のstrを破壊したくなかったからです。
    strtok()にstrを与えたら、それが破壊されました・・・。

    難しい要求で済みません。

    > 関数内でメモリ確保して返すのはメモリ管理が難しくなりリークの原因になるので避けたいところです。

    覚えておきます。
    ありがとうございます。

    スクリプト言語みたいに配列を楽に扱えるようにしようと思いましたが、
    それ以前に制約が多くて大変ですね。
  2. あ、すみません、str2を関数内でfreeしても良いという風に書こうとして間違えました。
    おっしゃるとおりstrtokで破壊するのでコピーは必要ですね。
    あとstr2に関しては最近のCでは次のようにmallocなしで確保できないでしょうか?

    ```
    char str2[len];
    strcpy_s(str2, len, str);
    ```

Your answer might help someone💌