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

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

新規登録して質問してみよう
ただいま回答率
85.50%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

4回答

6864閲覧

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

退会済みユーザー

退会済みユーザー

総合スコア0

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

1クリップ

投稿2017/07/30 06:17

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

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

C

1 2#include <stdio.h> 3#include <stdlib.h> 4#include <string.h> 5#include <malloc.h> 6 7struct php_struct { 8 char* str; 9 int size; 10}; 11 12int main () { 13 struct php_struct php; 14 int re_size; 15 char* temp_str = NULL; 16 FILE* fp; 17 18 // (1)事前に確保するメモリ量 19 php.size = 1000000; 20 21 // 1000000Byte,つまり1MB分のメモリを確保 22 temp_str = malloc(php.size * sizeof(char) + 1); 23 if (temp_str != NULL) { 24 php.str = temp_str; 25 } else { 26 printf("You could not get the memory which you specified."); 27 exit(2); 28 } 29 // php.strにファイルから1MB分文字列を読み込む 30 fp = fopen("test.public.pem", "rb"); 31 fread(php.str, php.size, 1, fp); 32 fclose(fp); 33 printf("%s", php.str); 34 35 36 printf("\r\n"); 37 printf("気が変わったので確保したメモリを縮小させる"); 38 printf("\r\n"); 39 40 // (2)気が変わって確保したメモリを縮小 41 re_size = 30; 42 temp_str = realloc(php.str, re_size * sizeof(char) + 1); 43 if (temp_str != NULL) { 44 // メモリを1MBから30Bへと縮小したはずのメモリ 45 php.str = temp_str; 46 } else { 47 printf("You failed to shrink the memory."); 48 exit(2); 49 } 50 // php.strにファイルから文字列を読み込む 51 fp = fopen("test.public.pem", "rb"); 52 fread(php.str, re_size, 1, fp); 53 fclose(fp); 54 printf("%s", php.str); 55}

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

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

C

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

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

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

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

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/07/30 13:59

raccy

総合スコア21733

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tacsheaven

2017/08/16 08:38

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

2017/08/16 09:19

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

0

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

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

C

1#include <stdio.h> 2#include <malloc.h> 3#include <assert.h> 4 5#define LARGE 1000000 6#define SMALL 30 7 8int main(void) 9{ 10 char *str1, *str2; 11 12 str1 = malloc(LARGE); 13 assert(str1 != NULL); // just in case 14 printf("%p for %d bytes\n", str1, LARGE); 15 printf("気が変わったので確保したメモリを縮小させる\n"); 16 str2 = realloc(str1, SMALL); 17 printf("%p for %d bytes\n", str2, SMALL); 18 return 0; 19}

手元の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の仕組みを学ぶことが役に立つことも言っておきます。

投稿2017/07/31 02:10

rubato6809

総合スコア1380

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

こんにちは。

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

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

投稿2017/07/30 06:47

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/07/30 08:35

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

退会済みユーザー

2017/07/30 12: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バイト以降が出力されるでしょうか?
Chironian

2017/07/30 13:29

> 101バイト以降はrealloc直後に必ず開放されることが保証されていないのでしょうか? 保証されてないですよ。指定したバイト数のメモリを使って良いことは保証されますが、それ以上が「使えないこと」は保証されないです。間違って使ったらそれは「未定義動作」を引き起こします。その場合、処理系は何をやっても規格違反になりません。 > 上記の点ですが,char* ポインタ変数のphp.strが保持するアドレスから仮に連続したアドレスにメモリの開放待ちのものが残っていたら メモリは普通連続したアドレスに存在します。realloc()やfree()などで解放したとしても、解放されたメモリがパッと消えるわけではなく、普通はそのまま存在しています。その解放された領域のデータが書き換わらない限りそのままです。解放された領域のデータがいつ書き換わるのかは処理系とプログラムの動作次第です。 なお、プロセスがOSへメモリを返却した時にその返却したメモリがプロセス空間から消えてなくなります。それがいつ起こるのかは、標準ランタイムのメモリ管理プログラム次第です。返却処理はかなり重いので頻繁には発生しない筈です。少なくともrealloc()やfree()する度に発生するということはないでしょう。
guest

0

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

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

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

投稿2017/07/30 13:11

tacsheaven

総合スコア13703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問