5.4 記憶領域の動的割り当て

 記憶領域の動的割り当て
配列の動的確保
これまで配列の宣言では、必要な要素数の上限値を前もって決定していた。たとえば、多項式の
実数解を求めるプログラムでは、そのプログラムで扱う多項式の最大次数をあらかじめ決定し
)*
"
ABC * !
*?ABC:@3
- 最大 次までの多項式を扱うものとする -
- " 次多項式の係数を格納 -
のように、多項式の係数を覚える配列を宣言していた 。この場合
たとえば 次式の解を求める場合、:; : ; のための記憶領域が無駄になる。
もし 次式の解を求める必要が生じた場合、記号定数 6?( の定義を 以上に変更してプ
ログラムを再コンパイルする必要がある。
などの問題が生じる。このような問題は、変数や配列の値を記憶するための領域を 関数を
用いてプログラム実行時に動的に割り当てることで解決できる。
記憶領域の動的割り当てを行う場合、ポインタ変数を用いて、割り当てられた記憶領域にアクセ
スする。
次のプログラム例は、正整数 $ を入力し、$ 個の 2 型の値を記憶するための領域を確保
し、$ 個の 2 型の値を入力してその領域に格納し、それらの値を出力するプログラムである。
プログラム例 に必要なヘッダーファイル ) に必要なヘッダーファイル 変数 は 型へのポインタ )&#'
*
変数 は 型へのポインタ &3 + 3'
&343 6'
の入力 + & ')&
/&'' 型 の デ ー タ を 個 格 納 す る た め に 必要な領域の確保 & ++ HIGG'*
が HIGG なら領域確保に失敗 &3@53'
エラーメッセージを出力 &'
エラー終了 ,
+ 確保した領域へのポインタを に代入 &+ !!'*
確保した領域に値を連続して入力 &34 3'
プロンプト出力 &343!!'
ポインタ が指している場所に 型のデータを入力し、 ポインタ が次のデータ位置を指すように更新 ,
+ ポインタ の値を、) で確保した領域へのポインタに戻す &+ !!'
領域に格納された 個のデータの値を順番に出力 &34 45453!! "'
ここでは、ポインタ と を用いて、 同じ場所に格納されているデータを . 回出力している。 「!!」は、現在 が指している場所に格納されているデータ値を 章では配列名 を用いたが、この節では配列名 で説明している
,
&'
表し、 は次のデータ位置を指すように更新される。 「 "」は、ポインタ を配列名とみなして、 その配列の 番目の要素の値を表す。 正常終了 dp
malloc䛻䜘 䜚
tp
doubleᆺ㼚ಶศ䛾
グ᠈㡿ᇦ䜢 ☜ಖ
図 ) 記憶領域の動的確保
このプログラムでは 図 参照、!! で確保する 2 型の値を格納するための記憶領域
へのポインタのために、変数 * を用いている。関数 !! は、確保すべき記憶領域の大きさ バイ
ト単位 を引数に取るが、ここでは、$ 個の 2 型の値を格納するために、H
.* !
/-"
バイトの領域を確保している
。!! の戻り値は、システムで 型へのポインタとして定義
されているので、これを 2 型へのポインタに変換するために .* !
-/ を !! の前に
付加して、型変換 キャスティング している。メモリ不足等の理由により指定された大きさの領
域を確保できない場合は、!! は JKK を返す。
次に、* の値を に代入し、その後、
「 が指している領域へ 2 型の値を " で読み
込んでは の値を次の 2 型の値を格納する領域を指すように更新する」と言う操作を $ 回
繰り返している。
このプログラム例のように、$ 個の連続した領域を確保した場合、その先頭の要素は -* や
*?@ でアクセスすることができる。次の要素は *?@ でアクセスできる。このように、ポインタ
を記憶する変数名は、配列名として扱うことができる。また、* の値を に代入してから-::
を繰り返すことにより、先頭から順番にアクセスすることができる。
演習
次方程式の根を 分法により求めるプログラム 演習 において、方程式の係数を記
憶する配列を動的に確保するように変更し、その動作を確認せよ。
文字列を格納する領域の動的確保
「$* で読み込んだ文字列へのポインタを文字型へのポインタ変数に格納する」ことを行うよ
うなプログラムを考える。プログラム例 では、まず文字型配列 2< に $* で文字列を読み込
みその文字列へのポインタを に代入している。次に再び文字型配列 2< に $* で文字列を
読み込みその文字列へのポインタを に代入している。この時点で と が指している文
「」は 個の '34 型の値を記憶するのに必要なバイト数を与える
「
」は、現在の が指している場所に格納されている値を返し、その後 をポインタとして 増やす 次の
格納場所を指す。なお、 の値を保存しておく必要がなければ、
を使わずにでアクセスしていくことも出来る。
演習 のプログラムが完成していない場合は、多項式の値を求めるプログラム 演習 に対して、方程式の係数を記
憶する配列を動的に確保するように変更したものでも良い。
字列を出力すると、実行例より、両者とも後から入力した文字列になっていることが分かる。これ
は、 ともに配列 2< の先頭要素 2<:; へのポインタになっており、配列 2< の内容が
回目の $* で上書きされるためである 図 参照。
プログラム例 G@H .;
G@H!"
)&#'
*
.
&34
3 '
+ &3
+ 4
53 '
&34
3 '
. + &3
+ 4
53 '
&3
. + 4
53 .'
,
や に必要なヘッダファイル で読み込む文字列の最大長 で読み込む文字列を格納する文字型配列 文字列の最後には HIGG 文字 &252' が付加されるため、
配列の要素数は、読み込む文字列の最大長+1必要である。 . は文字型へのポインタ 文字型配列 に文字列を読み込む へ読み込んだ文字列へのポインタを代入 が指している文字列を出力 文字型配列 に文字列を読み込む . へ読み込んだ文字列へのポインタを代入 が指している文字列を出力 . が指している文字列を出力 【実行例】
4 9
+ + . + 4
「 と入力」
「プログラムの出力。
が指している場所の文字列は33。」
「 と入力」
「プログラムの出力。
が指している場所の文字列が33に変わっている。」
「プログラムの出力。
. が指している場所の文字列は33である。」
buff
buff
str1
str1
str2
str2
図 ) 最初に読み込んだ文字列が上書きされる様子
このような挙動を行うプログラムを作りたかったのであればこれで問題無いが、もし、 回目の
$* で読み込んだ文字列が最初に $* で読み込んだ文字列を上書きしてしまうのを避けたいの
であれば、$* で読み込んだ文字列を格納するための領域を新たに確保して、その領域へ文字列
をコピーするなどの操作が必要となる。
プログラム例 では、$* で配列 2< に読み込んだ文字列に対して、その文字列を格納するの
に必要な領域を新たに確保して、2< に入っている文字列を確保された領域へコピーしている 図
参照。領域の確保は、
「. -/!!.H
. /-.!
". /://3」で行われる。
ここで、!
" は引数として与えられた文字型へのポインタが指す場所に格納されている文字列
の長さ .文字列の最後の JKK 文字はカウントされない/ を返す関数である。1 言語では、文字列は
最後に JKK 文字を付けて格納することになっているので、!
". /: 個の文字を格納する
領域が必要となる。必要な領域が確保出来たら により配列 に格納されている文字列
を、!! で確保した領域にコピーしている。 は、二つの文字型ポインタを引数としてと
り、第 $ 引数のポインタが指す場所に格納されている文字列を第 引数が指す場所にコピーする関
数である。なお、!
" や 等の文字列を操作する関数を用いる場合、ヘッダファイルと
して " を読み込んでおく必要がある。
buff
buff
strcpy
strcpy
str2
str2
malloc
str1
str1
malloc
図 ) 文字列を格納する領域を確保してコピー
プログラム例 L
G@H .;
G@H!"
や に必要なヘッダファイル に必要なヘッダファイル や $ に必要なヘッダファイル で読み込む文字列の最大長 で読み込む文字列を格納する文字型配列 )&#'
*
.
. は文字型へのポインタ &34
3 '
文字型配列 に文字列を読み込む + & ')&
/&'&
&'!''
読み込んだ文字列を格納するための領域を確保しそのアドレスを に代入 &
++ HIGG'*
領域が確保できない場合はその旨出力してエラー終了 &3
文字列を格納する領域が確保できない53'
&'
,
$&
'
配列 に格納されている文字列を が指す領域にコピー &3
+ 4
53 '
が指している文字列を出力 &34
3 '
文字型配列 に文字列を読み込む . + & ')&
/&'&
&'!''
読み込んだ文字列を格納するための領域確保しそのアドレスを . に代入 &
. ++ HIGG'*
領域が確保できない場合はその旨出力してエラー終了 &3
. 文字列を格納する領域が確保できない53'
&.'
,
$&
. '
配列 に格納されている文字列を . が指す領域にコピー &3
+ 4
53 '
が指している文字列を出力 &3
. + 4
53 .'
. が指している文字列を出力 &'
正常終了 ,
【実行例】
4 ;
「 と入力」
+ + . + 4
「プログラムの出力。
が指している場所の文字列は33。」
「 と入力」
「プログラムの出力。
が指している場所の文字列は上書きされていない。」
「プログラムの出力。
. が指している場所の文字列は33である。」
構造体を記憶する領域の動的確保
次のプログラムは、構造体を 個記憶する領域を動的に確保するプログラム例である。
プログラム例 L
G@H .00
や に必要なヘッダファイル ) や に必要なヘッダーファイル や $ に必要なヘッダーファイル で入力する文字列の最大長 # & '
引数として与えられたメッセージを出力後エラー終了する 関数のプロトタイプ宣言 *
)
,
G@H!"
構造体 の定義 文字型へのポインタを格納するメンバ ) 整数値を格納するメンバ 文字列入力のためのバッファ )&'
*
は構造体 へのポインタ + &
')&
/&
'' 構造体のための領域確保 &
++ HIGG'
が HIGG ならば &3> ))$ 3'
領域確保失敗 &3H) + 3'
プロンプト出力 &34
3'
配列 へ文字列を入力 -) + & ')&
/&'&
&'!''
入力された文字列を格納するための領域を確保し、 その領域へのポインタを構造体のメンバ ) に代入 &
-) ++ HIGG'
-) が HIGG の場合は、領域確保失敗 &3> ))$ )3'
$&
-)'
配列 に格納されている文字列を、
-) が指す場所にコピー &3
+ 3'
プロンプト出力 &3436
-
'
メンバ に値を入力 &34
2
453
-)
-
' 両メンバの出力 &'
,
# & '
*
&3@ 4
53
'
引数として与えられた文字列を出力し &'
エラー終了 ,
このプログラムでは、 を構造体 * へのポインタとし、構造体のデータを格納するための
記憶領域を !! 関数で確保し、確保した領域へのポインタを へ代入している .図 $' 参照/。
!! 関数は、引数として与えられた大きさ .バイト単位/ の記憶領域を確保し、その記憶領域
へのポインタを返す。メモリ不足等の理由で確保に失敗したときは JKK を返す。H
.
*/ は構造体 * のデータを記憶するのに必要なメモリ容量をバイト単位で与える。
!! の戻り値は、0* 型へのポインタと定義されているので、これを構造体 * へのポ
インタに変換するために、. * -/ を !! の前につけてキャスティング .型変換/
している。
その後、前節と同様に、配列 に文字列を読み込み、その文字列を格納するのに必要な領域
を動的に確保し、その領域へのポインタを ,"
にセットし、 で配列 に格納され
ている文字列をその領域へコピーしている。
sp
name
score
scanf
buff
sp
name
score
sp
name
score
scanf
sp
name
score
95
図 ) 構造体 を格納する領域の動的確保とデータの入力
演習 次のようなプログラムを作成せよ。なお、プログラムの作成においては、入力された $ に
対して、$ 人分の構造体と各々の名前を格納する記憶領域を動的に割り当てること。
入力: $ 整数、$ 人分の名前 文字列 と点数 整数
出力: 入力された名前と点数の一覧表、平均点、最高点とその点を取った人の名前、最低点とそ
の点を取った人の名前。
ヒント 構造体 の配列 配列の大きさ 要素数 は を動的に確保する。