7章 メモリーの割り付け



7.1:
以下のプログラムはなぜ動かないのか。

char *answer;
printf("なにか入力してください:\n");
gets(answer);
printf("あなたは \"%s\" と入力しました\n", answer);

A:
ポインター変数answerは応答を貯える場所として関数gets()に渡される が、その時点ではanswerは有効な領域を指していない。すなわちポイ ンターanswerは、どこを指しているのかわからない(ローカルな変数 は初期化されることはないし、たいていゴミが入っている。「answer」 がヌルポインターとして始まることさえ保証されていない。質問1.305.1を参照のこと)。

上の質問のプログラムを修正する一番やさしい方法は、ポインターの 代りにローカルの配列を使って、コンパイラに領域の割当てをまかせ ることである。

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

char answer[100], *p;
printf("なにか入力してください。:\n");
fgets(answer, sizeof answer, stdin);
if((p = strchr(answer, '\n')) != NULL)
*p = '\0';
printf("あなたは \"%s\"と入力しました\n", answer);

この例はgets()のかわりにfgets()を使って、配列に続く部分が上書 きされないような工夫もしている。(質問12.23参照。残念ながらこの 例ではfgets()はgets()とは違って、後ろの改行を自動的には削除し ない)。malloc()を使って返答用のバッファを割り当てることもでき る。


7.2:
strcat()がうまく動かない。以下のプログラムを書いたら、

char *s1 = "Hello, ";
char *s2 = "world!";
char *s3 = strcat(s1, s2);

変な答えが返ってきた。

A:
上の質問7.1と同じように、ここでも一番の問題は連結した結果を貯 える領域がうまく確保されていないことである。C言語には、自動的 に管理される文字列型はない。ソースコードで明示的に表されたオブ ジェクトに領域を割り当てるだけである("文字列"についてはcharの 配列と"でくくられた文字列を含む)。プログラマーは、文字列の連結 のような実行時の操作の結果に対して十分な領域を、配列を定義した り、malloc()を起動することで明示的に確保しなければならない。

strcat()は、領域の割り当てを行わない。二番目の文字列は最初の文字 列に、その場で連結される。一つの解決方法は、最初の文字列を十分 な大きさを持つ配列として宣言することである。

char s1[20] = "Hello, ";

strcatは第一引数を返すので(この場合はs1)、s3は余計である。

質問のコードの元のstrcat()の呼び出しは、実際には二つの難点があ る。s1によって指される文字列リテラルが、連結されてできるどんな テキストも保存できるほど大きくないかもしれないだけではなく、そ もそも書き込み不可かもしれない。質問1.32参照。

References:
CT&P Sec. 3.2 p. 32.


7.3:
けれどstrcat()のmanページによると、strcat()は引数として二つの charへのポインターを取ることなっている。領域の割り当てが必要で あるとどうやって知ることができるのか。

A:
一般にポインターを使うときは、いつも領域の割り当てのことを考え ておかなければならない。少なくともコンパイラが代わりにやってく れることを確認しておかなければならない。ライブラリーのドキュメン トに記憶領域の割り当てについてはっきり記述していなければ、割り 当ては普通は使う人の責任である。

Unix形式のmanの先頭やANSI C規格の要約の章は、誤解を招くかもし れない。そこに載っているコードの一部は、使い方というよりは、関 数の実装で使われる関数定義に近い。特に(構造体や文字列への)ポイ ンターを扱う関数の多くはなんかしらのオブジェクト(構造体か配列 への … 質問6.36.4を参照)へのアドレスを引数に起動される。ほ かによくある例はtime() (質問13.12参照)やstat()である。


7.5:
文字列を返す関数がある。けれど呼んだ側の関数に返ってくると、返っ てきた文字列にはゴミが入っている。

A:
関数が返す文字列を格納する領域が正しく割り当てられていることを 確認すること。返されたポインターは静的に割り当てられたバッファ か、呼んだ側の関数から渡されたバッファを指すべきで、呼ばれた関 数のローカルな(自動変数の)配列を指していてはいけない。つまり以 下のようなことは絶対にやってはいけない。

char *itoa(int n)
{
    char retbuf[20];                /* 間違い */
    sprintf(retbuf, "%d", n);
    return retbuf;                  /* 間違い */
}
修正する方法のひとつは、バッファを以下のように宣言することであ る(これも、f()が再帰的に起動される場合や、戻り値が同時に複数の 箇所で必要な場合にうまく行かないことを考えると不十分である)。

static char retbuf[20];

質問12.2120.1も参照のこと。

References:
ANSI Sec. 3.1.2.4; ISO Sec. 6.1.2.4.


7.6:
どうしてmalloc()を呼ぶと「警告: 整数をポインターに代入の際には キャストが必要」というのが出るのか。

A:
<stdlib.h>を#includeしたか、してないとしてmalloc()が正しく宣言 されるようにしたか。

References:
H&S Sec. 4.7 p. 101.


7.7:
mallocが返した値を確保したデータ型のポインターに注意深くキャス トしているコードをたまに見るのはなぜか。

A:
ANSI/ISO規格のC言語がvoid *という汎用のポインター型を導入する までは、互換性のないポインター型の間で代入をするときに警告を黙 らせるのにこうしたキャストが必要となることがよくあった。 (ANSI/ISO規格のCでは、こうしたキャストはもう必要ない。)

References:
H&S Sec. 16.1 pp. 386-7.


7.8:
以下のようなコードを見た。

char *p = malloc(strlen(s) + 1);
strcpy(p, s);

malloc((strlen(s) + 1) * sizeof(char))では?

A:
sizeof(char)をかける必要がある場合は絶対にない。なぜなら sizeof(char)は定義によりぴったり1であるから。 (一方、 sizeof(char)をかけても何も害はない。こう書くことで式にsize_tが 現れて理解しやすくなるかもしれない。)質問8.9も参照のこと。

References:
ANSI Sec. 3.3.3.4; ISO Sec. 6.3.3.4; H&S Sec. 7.5.2 p. 195.


7.14:
オペレーティングシステムによってはmallocしたメモリーを実際に確 保するのをプログラムがそのメモリーを使おうとするまで先延ばしす ると聞いたことがある。これは文法上許されるのか。

A:
これは難しい。規格は、システムはこういう風に振る舞ってもよいと 書いていないけれど、こういう風に振る舞ってはいけないとはっきり 書いてあるわけでもない。

References:
ANSI Sec. 4.10.3; ISO Sec. 7.10.3.


7.16:
数値演算をするのに、大きな配列を確保しようと考えている。下のコー ドを書いたところ、

double *array = malloc(256 * 256 * sizeof(double));

malloc()はヌルを返すわけではないけれど、プログラムの動きが変だ。 メモリーを上書きしたり、こっちが望んだだけmalloc()が確保してな かったりとかするようだ。

A:
256 x 256は65,536で、これはsizeof(double)をかける前から、16ビッ トの整数にはおさまらないことに注意。こんなに大きなメモリーを確 保する必要があるときは、注意する必要がある。使っているマシンの size_t(malloc()が認めたデータ型)が32ビットであれば、256 * (256 * sizeof(double))と書くことでやっていけるかもしれない(質問3.14 参照)。これで駄目ならデータの構造をもっと小さな単位に分解する か、32ビットのマシンを使うか、標準ではないメモリー確保ルーチン を使うかしなければならない。質問19.23も参照のこと。


7.17:
私のPCには8メガもメモリーが載っている。どうして640Kかそこらし かmalloc()できなさそうなのか。

A:
PC互換機のセグメント付きのアーキテクチャーでは、640Kより多くの メモリーを使うことは非常に難しい。質問19.23も参照のこと。


7.19:
プログラムがコケる。malloc()の内部のどこかのようだ。でもどこが 悪いか分からない。

A:
mallocした領域の内部のデータ構造は残念ながら非常に簡単に壊れて しまう。しかも引き起こされる障害は追跡しにくいものとなることが ある。いちばんよくある障害の元は、mallocした領域に確保したサイ ズよりも多く書き込んでしまうことである。特によくある例は、大き さstrlen(s)+1ではなく、大きさstrlen(s)だけmalloc()で確保するこ とである。他にはfreeした領域を指すポインターを使うことや、2回 freeしたり、malloc()で確保した先を指していないポインターを free()を使って開放しようとしたり、ヌルポインターを使って realloc()を呼び出すことも障害を引き起こす(質問7.30を参照)。

質問7.26, 16.8, 18.2も参照のこと。


7.20:
動的に割り当てた記憶領域は解放した後には使えないね。

A:
使えない。malloc()の昔の解説には解放された領域は「壊されずに残っ ている」と記述してあるものもあった。このうかつな保証は一般的に なることはなく、C規格では、このようなことを保証することは要求 されていない。

意識して解放した領域の中身を使うプログラマーは少ない。けれど偶 然使ってしまうことはよくある。一重リンクのリストを解放する以下 の(正しい)コードを考えてみよう。

struct list *listp, *nextp;
for(listp = base; listp != NULL; listp = nextp) {
nextp = listp->next;
free((void *)listp);
}

一時変数のnextpを使うことなくlistp = listp->nextを使ったとすれ ばどうなったか考えてみること。

References:
K&R2 Sec. 7.8.5 p. 167; ANSI Sec. 4.10.3; ISO Sec. 7.10.3; Rationale Sec. 4.10.3.2; H&S Sec. 16.2 p. 387; CT&P Sec. 7.10 p. 95.


7.21:
どうしてポインターがfree()を呼んだ後でヌルポインターにならない のか。freeした後のポインターの値を使う(代入、比較する)ことは、 どれくらい危険なのか。

A:
free()を呼ぶと、free()に渡したポインターの指す先のメモリーは解 放されるが、呼び出した側のポインターの値は変わらない。それはC の値渡しとは、呼ばれた側の関数が自分の引数を永久に変えたままに することはないということだからである。(質問4.8も参照)

解放されたポインターの値は厳密にいえば無効で、それをどう使って も、たとえ間接参照以外のことに使っても理屈の上ではトラブルの元 である。ただ、これは実装の質の話だけれど、無効なポインターの害 のない使いかたにわざわざ例外を発生させる実装はたぶんない。

References:
ANSI Sec. 4.10.3; ISO Sec. 7.10.3; Rationale Sec. 3.2.2.3.


7.22:
ローカルなポインター用にメモリーをmalloc()で確保する。明示的に free()を呼ばなければならないか。

A:
もちろん。ポインターとポインターが指す先は別物であることを忘れ てはいけない。ローカル変数は関数から戻るときに解放される。ただ しポインター変数に関しては、ポインターが解放されるのであって、 ポインターが指す先が解放されるわけではない。malloc()によって確 保されたメモリーは明示的に解放するまで必ず残る。一般に、すべて のmalloc()の呼び出しに、対応するfree()がなければならない。


7.23:
動的に割り当てたオブジェクトへのポインターを含む構造体を割り当 てている。構造体を解放する前に、構造体が含むポインターの先のオ ブジェクトを全部解放しなければならないのか。

A:
そのとおり。一般にmalloc()が返してきたポインターを(解放すると したら)それぞれ一度だけfree()に渡すしくみを用意しなければいけ ない。


7.24:
プログラムが終了する前に、確保したメモリを解放しなければならな いか。

A:
その必要はない。まともなオペレーティングシステムならきっとプロ グラムが終了した時点ですべてのメモリを取り返すだろう。にもかか わらず、個人向けコンピュータ(PC)の中にはメモリを取り戻すことが 確実にはできないものもあるようである。ANSI/ISO C規格から結論つ けられることは、解放してくれるかどうかは「実装の品質がどれくら い高いかによる」ということだけである。

References:
ANSI Sec. 4.10.3.2; ISO Sec. 7.10.3.2.


7.25:
大量の記憶領域をmallocしてfreeしてまわるプログラムがある。けれ ど、メモリの使用状況をみると(psで見える値では)領域が戻ってきた ようには見えない。

A:
たいていのmalloc/freeの実装は、解放されたメモリをO/Sに(O/Sがあっ たとして)返さない。後からmalloc()が呼び出されたときのために取っ ておく。


7.26:
free()は、何バイト解放するかをどうやって知るのか。

A:
malloc/freeの実装は、メモリのブロックを割り付けて、そのブロッ クの先頭アドレスを返す。その時に、ブロックのサイズを記憶する。 よって解放するときにfreeに思い出させる必要はない。


7.27:
だったら割り当てた領域の大きさをmallocパッケージに聞くことがで きるのか。

A:
移植性の高い方法では不可能である。


7.30:
realloc()の最初の引数にヌルポインターを使うことは許されている のか。どうしてそんなことをするのか。

A:
ANSI Cはこの使用方法を許している(これに関係するrealloc(...,0) も許している。これが領域を解放する)。けれども昔のコンパイラに は対応していないものもあるので、この方法は移植性が高いとはいえ ない。第一引数をヌルポインターにすることで、確保する領域をだん だん増やしていくアルゴリズムを実現するときに、起動する部分を書 くことが容易になる。

References:
ANSI Sec. 4.10.3.4; ISO Sec. 7.10.3.4; H&S Sec. 16.3 p. 388.


7.31:
calloc()とmalloc()の違いは。calloc()の0を埋めるという機能を、 ポインターや浮動小数点数に使っても問題ないか。free()はcalloc() が割り当てた領域にも働くか、それともcfree()を使わなくてはなら ないか。 A:
calloc(m, n)は以下のコードと同じ動きをする。

p = malloc(m * n);
memset(p, 0, m * n);

0を埋めるというのは全ビット0にするということで、ポインターや浮 動小数点数について意味のある0を埋めることを保証していない(1章 を参照のこと)。calloc()が割り当てた領域の解放にfree()を使うこ とは可能である(そうすべきである)。

References:
ANSI Sec. 4.10.3 to 4.10.3.2; ISO Sec. 7.10.3 to 7.10.3.2; H&S Sec. 16.1 p. 386, Sec. 16.2 p. 386; PCS Sec. 11 pp. 141,142.


7.32:
alloca()とは何者で、なぜ使わないほうがよいと人は言うのか。

A:
alloca()は領域を割り当て、alloca()を起動した関数を抜けた時点で その領域は自動的に解放される。すなわちalloca()によって割り当て られた領域は、特定の関数の「スタックフレーム」やその関数の前後 の状況に局所的となる。

alloca()を移植性が高いように書くことはできないし、スタックのな いマシン上に実装することは難しい。これを使うことは、戻り値を直 接別の関数に渡す場合に(すぐに思いつくような実装では、スタック に基づくマシンでは必ず失敗する)、たとえば fgets(alloca(100),100, stdin)のような式で問題を招く。

これらの理由により、alloca()は便利にみえるが、広く移植性が高く なければならないプログラムでは使うことはできない。

質問7.22も参照のこと。

References:
Rationale Sec. 4.10.3.

目次へ戻る