質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.04%

C言語にて一度、mallocで確保したメモリサイズを縮小した際、縮小されない?

解決済

回答 4

投稿

  • 評価
  • クリップ 1
  • VIEW 2,802

jacky

score 45

C言語のmallocとreallocの用途の練習をしています。

以下のようなコードで作業しています。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>

struct php_struct {
    char* str;
    int size;
};

int main () {
    struct php_struct php;
    int re_size;
    char* temp_str = NULL;
    FILE* fp;

    // (1)事前に確保するメモリ量
    php.size = 1000000;

    // 1000000Byte,つまり1MB分のメモリを確保
    temp_str = malloc(php.size * sizeof(char) + 1);
    if (temp_str != NULL) {
        php.str = temp_str;
    } else {
        printf("You could not get the memory which you specified.");
        exit(2);
    }
    // php.strにファイルから1MB分文字列を読み込む
    fp = fopen("test.public.pem", "rb");
    fread(php.str, php.size, 1, fp);
    fclose(fp);
    printf("%s", php.str);


    printf("\r\n");
    printf("気が変わったので確保したメモリを縮小させる");
    printf("\r\n");

    // (2)気が変わって確保したメモリを縮小
    re_size = 30;
    temp_str = realloc(php.str, re_size * sizeof(char) + 1);
    if (temp_str != NULL) {
     // メモリを1MBから30Bへと縮小したはずのメモリ
        php.str = temp_str;
    } else {
        printf("You failed to shrink the memory.");
        exit(2);
    }
    // php.strにファイルから文字列を読み込む
    fp = fopen("test.public.pem", "rb");
    fread(php.str, re_size, 1, fp);
    fclose(fp);
    printf("%s", php.str);
}

上記のようなコードで
事前に、php_structという構造体の中にchar*のポインタ変数を用意して起き
(1)の箇所で1000000を宣言して
php.strという変数に1MB分のメモリを確保したアドレスを保持しました。
その後、適当なテキストファイル等から1MB分を読み込みphp.strに保持しました。

ただその後
(2)の箇所で気が変わりphp.strの値を30Bまでメモリを下げたくなりました。

temp_str = realloc(php.str, re_size * sizeof(char) + 1);
    if (temp_str != NULL) {
     // メモリを1MBから30Bへと縮小したはずのメモリ
        php.str = temp_str;
    } else {
        printf("You failed to shrink the memory.");
        exit(2);
    }


上記の箇所です。
その後、再度適当なファイルからふたたび30B分をとりだしたところ、
なぜか、(1)で確保した量と同じ分ファイルから取り出しているようなのです。。。

そして、(1)でのメモリ確保量を100KBとかに一桁下げると正しく、reallocでメモリを縮小できているようです。

どうにも私の知識量では腑に落ちないのでお聞きしました。
ご教授のほどよろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+5

reallocでメモリ領域を縮小して成功した場合やfreeでメモリを解放した場合、その解放した領域について0になるなどのメモリクリアのような動作が行われるわけではありません。メモリ領域の開放というのは「もうその領域は使いません」と宣言するだけです。その後のmalloc等でその領域が再利用される場合もあれば、ずっと使われない場合もあり得ます。また、アプリ上の仮想メモリとして物理メモリ上に割り当てられていたその領域をOS側が実際に回収するタイミング(物理メモリ上のデータが消えるタイミング)もいつになるかは予測不可能です。物理メモリに余裕があればずっと回収されない可能性もありますし、他アプリに物理メモリが必要なってすぐに回収されるかも知れません。

ということで、temp_str = realloc(php.str, re_size * sizeof(char) + 1);で31バイト(+1しているため30バイトではありません)にしていますが、このとき、temp_strphp.strが同じままになっている場合があります。その場合でも、temp_strへの32バイト目以降については「もうその領域は使いません」と宣言している状態です。データはそのまま残っている場合もあれば、既にOSが回収していたり、その後のmalloc等で再利用されている場合もあります。そして、その領域にアクセスした場合にどのような動作が行われるかは未定義です。元のデータがそのまま取得できる場合もあれば、0になっている場合や、全く別の値になっている場合や、メモリアクセス違反でアプリが落ちる場合もあります。これを制御する方法はありません。

そして最後に、printf("%s", php.str);ですが、これは、php.strについて\0になるまで文字を出力することになります。そして、mallocで確保された領域がは0になっている(メモリクリアされている)かは保証されなません。つまり、コードには\0php.strでアクセスできる文字列に書き込んでいる所がありません。ですので、メモリ状態によってはどんな出力になるのかが全く予測できない物になります。

とにかく、malloc等について、その動作をよく理解してください。かいつまんで、勘違いしそうな所を下記に抜き出します。

malloc

  • 指定されたサイズのメモリ領域を確保する。成功すればポインタ、失敗すればNULLを返す。
  • 確保されたメモリ領域がどのような状態になっているかは不明。0で埋められている場合もあれば、何かの値が入っている場合もある。

calloc

  • 指定されたサイズのメモリ領域を確保する。成功すればポインタ、失敗すればNULLを返す。
  • 確保されたメモリ領域は必ず0埋めされている。(この動作がmallocとは大きく異なる)

realloc

  • 指定されたポインタ(malloc、calloc、reallocで確保された領域で無ければならない)のメモリ領域を指定のサイズに変更する。 成功すればポインタ、失敗すればNULLを返す。ただし、NULLを指定した場合はmallocと同じ動作になる。
  • ポインタが変更される場合は、古いメモリ領域のデータは新しいメモリ領域にコピーされる。古い領域は解放される(freeされたのと同じ)。ポインタが変更されない場合は、そのまあ拡張または縮小される。
  • サイズが大きくなる場合、拡張された領域がどのような状態になっているかは不明。0で埋められている場合もあれば、何かの値が入っている場合もある(mallocと同じ動作)。
  • サイズが小さくなる場合、元々あった領域は解放される(freeと同じ動作)。

free

  • 指定されたポインタ(malloc、calloc、reallocで確保された領域で無ければならない)のメモリ領域を解放する。 ただし、NULLを指定した場合は何もしない(エラーにもならない)。
  • 解放された領域をアクセスした場合の動作は未定義。元データの場合もあれば、0になっている場合もあるし、別のデータがある場合もあるし、エラーになる場合もある。
  • 解放された領域のデータはそのまま残っている場合もあれば、0になっている場合もあるし、別のデータがある場合もある。OS側が回収するタイミングなどを操作することはできない。
  • 解放済みのポインタを再度解放した場合の動作は未定義。
  • reallocで解放されたメモリ領域も同様の動作になる。
  • malloc、calloc、realloc等でのメモリ確保で、解放済みのメモリ領域を再利用する場合がある。その場合、データが残っている場合がある(残っていない場合もある)。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/08/16 17:38

    昔々、HP-UX の C でお仕事してたとき、誤って malloc(0) をやったら(実際には計算の結果0になった)、NULL じゃない値が返ってきました。NULL チェックはしてたのでそこはすり抜けて、アクセスしたとたんに segfault でこけたことが(w
    確かに 0バイトの領域を確保してるのだから、間違ってはいないんですけれどもね。

    キャンセル

  • 2017/08/16 18:19

    http://en.cppreference.com/w/c/memory/malloc
    によると、malloc(0)の動作は実装依存のようです。null以外が返って来る場合もあるし、アクセスしちゃ駄目だけど、freeは通るとか…。

    キャンセル

checkベストアンサー

+2

こんにちは。

なぜか、(1)で確保した量と同じ分ファイルから取り出しているようなのです。。。

これはどのようにして確認されたのでしょうか?
php.strに1,000,000Bytesのメモリを確保してそこへ読み出していたデータが残っているだけということはありませんか?
reallocで「必ず」メモリを確保するとは限りません。特にサイズを小さくするだけであれば、事実上何もしない処理系もあると思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/30 17:35

    こんにちわ、ありがとうございます
    >これはどのようにして確認されたのでしょうか?
    単純にコマンドプロンプト上で printf("%s",)
    を使って表示された内容がそのような現象だったんですよね。。。

    キャンセル

  • 2017/07/30 21:32

    >php.strに1,000,000Bytesのメモリを確保してそこへ読み出していたデータが残っているだけということはありませんか?
    上記の指摘について,
    reallocでメモリを縮小した場合,仮に100000 => 100まで縮小したとした場合
    101バイト以降はrealloc直後に必ず開放されることが保証されていないのでしょうか?


    temp_str = realloc(php.str, re_size * sizeof(char) + 1);
    if (temp_str != NULL) {
    // メモリを1MBから30Bへと縮小したはずのメモリ
    php.str = temp_str;
    } else {
    printf("You failed to shrink the memory.");
    exit(2);
    }
    上記の点ですが,char* ポインタ変数のphp.strが保持するアドレスから仮に連続したアドレスにメモリの開放待ちのものが残っていたら
    printf("%s", php.str);
    でも101バイト以降が出力されるでしょうか?

    キャンセル

  • 2017/07/30 22:29

    > 101バイト以降はrealloc直後に必ず開放されることが保証されていないのでしょうか?

    保証されてないですよ。指定したバイト数のメモリを使って良いことは保証されますが、それ以上が「使えないこと」は保証されないです。間違って使ったらそれは「未定義動作」を引き起こします。その場合、処理系は何をやっても規格違反になりません。

    > 上記の点ですが,char* ポインタ変数のphp.strが保持するアドレスから仮に連続したアドレスにメモリの開放待ちのものが残っていたら

    メモリは普通連続したアドレスに存在します。realloc()やfree()などで解放したとしても、解放されたメモリがパッと消えるわけではなく、普通はそのまま存在しています。その解放された領域のデータが書き換わらない限りそのままです。解放された領域のデータがいつ書き換わるのかは処理系とプログラムの動作次第です。

    なお、プロセスがOSへメモリを返却した時にその返却したメモリがプロセス空間から消えてなくなります。それがいつ起こるのかは、標準ランタイムのメモリ管理プログラム次第です。返却処理はかなり重いので頻繁には発生しない筈です。少なくともrealloc()やfree()する度に発生するということはないでしょう。

    キャンセル

+1

C言語における realloc の挙動としては、たいていの場合

  • サイズを増加させる場合は改めて別の領域を確保して渡す(後ろに拡張できる余地があればその限りではない)
  • サイズを減少させる場合は(よほどのことが無い限り)同じ領域を返している

ではないかと思われます。(man malloc とかすると書かれてますが)
サイズを減少させる場合というのは単なる領域の端点をずらすだけで済みますから、わざわざ新しい領域を確保する理由がありません。ですから領域の先頭アドレスは変わらないでしょう。
この状態で領域にアクセスしたとして、サイズが小さくなっただけならデータ自体は消えてませんから(言うまでも無くメモリをクリアするのもコストがかかります)、そのまま「前のデータが見える」状態は普通にあり得ます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

他の方々が指摘されている通り、この場合realloc()は同じアドレスを返しているのです。よって、realloc()する前に読み込んだデータがそのまま残っているのは、むしろ当然の事です。他の方々が仰るように、そうした振る舞いは不当ではありません。

同じアドレスが返ることは簡単に確認できます。ファイルから読み込む必要は、特にありません。貴方も試してみてください。

#include <stdio.h>
#include <malloc.h>
#include <assert.h>

#define LARGE  1000000
#define SMALL  30

int main(void)
{
    char *str1, *str2;

    str1 = malloc(LARGE);
    assert(str1 != NULL);    // just in case
    printf("%p for %d bytes\n", str1, LARGE);
    printf("気が変わったので確保したメモリを縮小させる\n");
    str2 = realloc(str1, SMALL);
    printf("%p for %d bytes\n", str2, SMALL);
    return 0;
}


手元のgcc 5.4.0, Ubuntu 16.04 64bitで試すと、こう表示しました。

0x7f4f5fb1c010: for 1000000 bytes
気が変わったので確保したメモリを縮小させる
0x7f4f5fb1c010: for 30 bytes

(1)でのメモリ確保量を100KBとかに一桁下げると正しく、reallocでメモリを縮小できているようです

実は貴方のプログラムで、100KBなら正しく縮小できた、と貴方が思われたであろう事象を私も見たので、LARGEの値を変えて試しました。

0x1ec4010 for 100000 bytes
気が変わったので確保したメモリを縮小させる
0x1ec4010 for 30 bytes


と、100KBでも同一のメモリアドレスを返しました。貴方の手元でも十中八九同じ結果になると思います。もうひとつ、1MBと100KBでは、取得できるメモリの領域が大きく異なることも見てとれる。
こうした振る舞いは、malloc(),realloc()等の、内部の実装に理由があります(ある程度は私にも見当がつく、というだけですがw)。恐らく貴方は、実装上の問題を解明したいのではなく、「メモリを縮小する」事の誤解を修正できれば十だと思います。それは他の方々の回答で十分理解できるはずですから、ここまでにしますが、Cライブラリのソースを調べたり、コンピュータやOSの仕組みを学ぶことが役に立つことも言っておきます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.04%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る