C/C++
勉強会
2007年6月18日目次
1 C標準ライブラリとSTL(1) 2 1.1 前置き . . . 2 1.2 stdio . . . 3 1.3 ちょっといい話 . . . 10 2 ポインタ(中) 11 2.1 sizeof(データサイズ) . . . 11 2.2 動的メモリ . . . 12 2.3 newとdelete . . . 12 2.4 動的配列 . . . 14 2.5 メモリリーク. . . 15 2.6 voidポインタ . . . 151
C
標準ライブラリと
STL(1)
1.1 前置き 1.1.1 ライブラリの利用と作成 ライブラリは以下のように作成する。 ほげほげ 一度ライブラリが作られてしまえば、利用の際に必要なのはヘッダファイルのみであり、 ライブラリのソースは必要ないということに注意しよう。これには幾つかの利点がある。 まずソースをユーザ(=開発者)へ提供しないで済むことにより、ライブラリ(=プログラ ムの部品)だけを商品とした商売が成立する。数万行のコードを含んだライブラリを、コン パイルすることなくただリンクするだけでその資産を使うことができる。ユーザとライブ ラリ製作者、という形で責任範囲を明確にできる。C言語用のヘッダファイルさえあれば、 ライブラリのコードはC言語で書かれていなくともよいということ。 1.1.2 標準ライブラリとは Cの言語仕様には標準ライブラリ(libc)が含まれており、それによってCの文法だけで はカバーできない機能(標準入出力やファイルシステムの利用、時刻の取得など)を扱うこ とができる。また、C標準ライブラリはプログラミングの際頻繁に利用される操作(文字列 や乱数、数学関数など)をある程度まとまった形で提供してもいる。C++では標準ライブラリにあたるものとして、STL(Standard Template Library:
標準テンプレートライブラリ)が用意されている。これはlibc以上に強力で柔軟な機能を
持っているが、その分libcと比べて複雑な部分がある。
C++ は基本的に C 言語と互換性を持つため、STL だけでなく libc をも含んでいる。
STLのiostreamやAlgorithmなどは現段階では説明と理解に困難があるため、とりあえず
は主にlibcの使い方について幾つか紹介する。また、STLの中でもstringとvectorは比較
的簡単に扱うことができる便利のよい機能であるため、ここで紹介する。 libcにせよSTLにせよ、標準ライブラリはほとんどの処理系に用意されているため(で ないとそのC/C++処理系が標準に準拠しているとは言えない)*1、ライブラリに標準ライ ブラリだけを使ったプログラムはOS/マシンなどの環境を問わず共通のソースで動かすこ とができ、移植性が高いと言える*2。 *1標準ライブラリを処理系が独自に拡張している場合もあるため、移植性の高いコードを書くためにはそういった処理系独自の機能 を使わないよう注意しなければならない *2実際にはこれに加えてビットワイズやエンディアン、アラインメントなど、マシンの特性に依存したコードを注意深く避けなけれ ば、真に移植性の高いプログラムにはならない。またプログラムの移植性と実用性の両立を真剣に考えるならば、標準ライブラリ 以外を一切使わないという手段ではなく、環境に依存するコードを局所化した上で差異を吸収するライブラリを作り、それ以外の 共通部分を標準ライブラリを使って作るという選択をするべきだ
1.2 stdio
stdioはSTanDard Input/Outputの略であり*3、入出力に関する標準的な機能及び
標準入出力を提供する。ヘッダファイルcstdio、もしくは stdio.hをインクルードするこ とで利用することができる。 以下はこの節で紹介するstdioの関数の一覧。 関数名 説明 fopen ファイルを開く fclose ファイルを閉じる fread ファイルからデータを読み込む fwrite ファイルへデータを書き込む fseek ファイルの読み書き位置の移動 ftell ファイルの読み書き位置の取得 feof ファイルの終わりに達したかどうか fgets ファイルから1行読み込む fgetc ファイルから1文字読み込む fputs ファイルへ1行書き込む fputc ファイルへ1文字書き込む printf フォーマット出力 fprintf ファイルへのフォーマット出力 sprintf 文字列へのフォーマット出力 scanf フォーマット入力 fscanf ファイルからのフォーマット入力 sscanf 文字列からのフォーマット入力 1.2.1 ファイル入出力 以下にstdioを利用したファイル入力プログラムのサンプルを示す。 stdio read.cpp 1 #i n c l u d e <c s t d i o > /∗ s t d i o . hで も い い ∗/ 2 #i n c l u d e <i o s t r e a m > 3 4 u s i n g namespace s t d ; *3多分
6 #d e f i n e READ FILE ” s t d i o r e a d . cpp ” 7 #d e f i n e SIZE BUFFER 256 8 9 10 i n t main (v o i d) 11 { 12 FILE ∗ fp ; // フ ァ イ ル の ハ ン ド ル ( フ ァ イ ル ポ イ ン タ ) 13 c h a r b u f [ SIZE BUFFER ] ; // 読 み 出 し 用 バ ッ フ ァ 14 15 f p = f o p e n (READ FILE , ” rb ”) ; // フ ァ イ ル をR e a dモ ー ド で 開 く 16 17 i f ( f p==NULL) { // f o p e n ( )は 失 敗 す る とNULLを返す
18 c o u t << ” can ’ t open t h e f i l e ” READ FILE << e n d l ; 19 r e t u r n 1 ; 20 } 21 22 w h i l e ( ! f e o f ( f p ) ) { // EOF( End Of F i l e )へ 達 し た か を チ ェ ッ ク 23 i n t s i z e ; // 読 み 込 み に 成 功 し た サ イ ズ 24 25 // フ ァ イ ル か らb u fへ 読 み 込 み
26 s i z e = f r e a d ( buf , 1 , SIZE BUFFER − 1 , fp ) ;
27 b u f [ s i z e ] = ’\0 ’; // ヌ ル 文 字 で 終 端 28 c o u t << b u f ; // 読 み 込 ん だ 内 容 を 標 準 出 力 へ 29 } 30 31 f c l o s e ( f p ) ; // フ ァ イ ル を 閉 じ る 32 r e t u r n 0 ; 33 } fopen()は指定した名前のファイルを開き、ハンドルとしてFILE型の値へのポインタを 返す。以降のファイルの操作はこのポインタを経由して行うこととなる。第2引数はファ イルを開く際のモードを表す文字列で、以下の値をとることができる。 なおテキストモードとバイナリモードを区別する環境を考慮し、通常はこれらのモード文 字列へ「b」を加えたものを指定し(”rb”など)、ファイルが常にバイナリモードで開かれる
値 説明 ”r” 読み込みモードで開く ”w” 書き込みモードで開き、ファイルが存在していれば長さ0に切り詰め、なければ作成 ”a” 追加書き込みモードで開き、ファイルが存在していなければ作成 ”r+” 読み書きモードで開く ”w+” 読み書きモードで開き、ファイルが存在していれば長さ0に切り詰め、なければ作成 ”a+” 追加読み書きモードで開き、ファイルが存在していなければ作成 ようにするといい*4。
feof() はFILE型のポインタをとり、読み込み位置がファイルの終わり(EOF : End Of File)へ達していれば0以外の値を返し、達していなければ0を返す。fopenによって 得られたファイルストリームは内部に「現在の読み書き位置」(ファイル位置指定子)を記 憶しており、これは通常読み込み/書き出しを行うたびにその分だけ移動する。ファイル位 置指定子がファイルの末尾に達した時、ファイルストリーム内部のフラグ終端指示子が立 つ。feof()は正確にはこの終端指示子をテストしている。終端指示子は clearerr()によっ てのみクリアすることができる。 fread()は第1引数のポインタが示す領域へ指定サイズ(第2引数)のデータを指定個数 (第3引数)だけファイル(第4引数)から読み込み、読み込みに成功したデータの個数を 返す。要するにファイルからデータを読み出す。ファイル位置指定子は読み込んだ分だけ 進み、途中でEOFへ達した場合は終了する。 fclose()はファイルを閉じる。 ファイル入出力のサンプルをもう1つ示す。 stdio write.cpp 1 #i n c l u d e <c s t d i o > /∗ s t d i o . hで も い い ∗/ 2 #i n c l u d e <i o s t r e a m > 3 4 u s i n g namespace s t d ; 5
6 #d e f i n e WRITE FILE ” hoge . t x t ”
7 #d e f i n e SIZE BUFFER 256 8
9
10 i n t main (v o i d)
11 {
12 FILE ∗ fp ; // フ ァ イ ル の ハ ン ド ル ( フ ァ イ ル ポ イ ン タ )
13 c h a r b u f [ SIZE BUFFER ] ; // 読 み 出 し 用 バ ッ フ ァ
14 c h a r c ; 15
16 f p = f o p e n (WRITE FILE , ”w+b”) ; // Read/ W r i t eモ ー ド で 開 く
17
18 i f ( f p==NULL) { // f o p e n ( )は 失 敗 す る とNULLを返す
19 c o u t << ” can ’ t open t h e f i l e ” WRITE FILE << e n d l ; 20 r e t u r n 1 ; 21 } 22 23 // フ ァ イ ル へ の 書 き 込 み 24 f w r i t e (” hogehoge\ r \n” , 1 , 1 0 , f p ) ; // \ r \ nは 改 行 文 字(CRLF) 25 26 f s e e k ( fp , 0 , SEEK SET ) ; // フ ァ イ ル 先 頭 か ら
27 f g e t s ( buf , SIZE BUFFER , f p ) ; // フ ァ イ ル か ら1行 読 み 込 み
28 29 // フ ァ イ ル へ 文 字 列 の 書 き 込 み 30 f p u t s (”ほげほげ\n”, f p ) ; // \ nは 改 行 文 字 (LF) 31 32 // 前 に 読 み 込 ん だ1行 を1文 字 ず つ 書 き 込 む 33 f o r (i n t i =0; b u f [ i ] != ’\0 ’; i ++) 34 f p u t c ( b u f [ i ] , f p ) ; // フ ァ イ ル へ1文 字 書 き 込 み 35 36 f s e e k ( fp , 0 , SEEK SET ) ; // フ ァ イ ル 先 頭 か ら 37 // フ ァ イ ル 全 体 の 内 容 を1文 字 ず つ 読 み 込 む 38 f o r (i n t i =0; ( c = f g e t c ( f p ) ) != EOF; i ++) 39 b u f [ i ] = c ; 40 41 // f t e l l ( )で 現 在 の フ ァ イ ル 位 置 ( = 末 尾 の オ フ セ ッ ト ) を 取 得 42 b u f [ f t e l l ( f p ) ] = ’\0 ’; // ヌ ル 文 字 で 終 端 43 44 c o u t << b u f ; 45
46 f c l o s e ( f p ) ; 47 } fwrite()はfread()と逆に第1引数のポインタが示す領域から指定サイズ(第2引数)の データを指定個数(第3引数)だけファイル(第4引数)へ書き込み、書き込みに成功した データの個数を返す。要するにファイルへデータを書き込む。ファイル位置指定子は読み 込んだ分だけ進む。 fseek() は フ ァ イ ル 位 置 指 定 子 を 指 定 し た フ ァ イ ル 位 置 へ 動 か す 。第 3 引 数 は
SEEK SET、SEEK CUR、SEEK ENDのどれかの値をとり、それぞれファイルの先
頭、現在位置、ファイルの末尾を表す。そして第2引数はそこからのオフセット位置*5を示
す。fseek(fp,0,SEEK SET) ならファイルの先頭、fseek(fp,10,SEEK END) ならファイル
の末尾から10バイト手前ということ。 fgets()はバッファへのポインタ(第1引数)とバッファサイズ(第2引数)、ファイルポ インタ(第3引数)をとり、ファイルから最大でバッファサイズ-1まで読み込み、EOFも しくは改行文字を読み込んだ時に終了する。読み込まれた改行文字はバッファへ格納され、 末尾にヌル文字が加えられる。fgets()は1文字も読み込まずにファイル末尾へ達した場合 にNULLを返し、読み込みに成功した場合はバッファへのポインタをそのまま返す。 fputs()は文字列をファイルへ書き込み、fputc()は文字をファイルへ書き込む。
fgetc()はファイルから1文字をunsigned charとして読み込み、int型へ変換して返す。
ファイルの末尾へ達した場合はEOFを返す。
ftell()は現在のファイル位置指定子のファイル先頭からのオフセットを返す。
1.2.2 フォーマット入出力
C標準ライブラリにはprintf系とscanf系のフォーマット入出力があり、cin/cout がや
るような文字列と数値などの間での変換を行うことができる。 formatio.cpp 1 #i n c l u d e <c s t d i o > /∗ s t d i o . hで も い い ∗/ 2 3 u s i n g namespace s t d ; 4 5 #d e f i n e RW FILE ” hoge . t x t ” 6 #d e f i n e SIZE BUFFER 256 *5offset は英語で「差し引き」を表し、コンピュータ用語としては「基準点からの距離」を表す
8 i n t main (v o i d) 9 { 10 FILE ∗ fp ; 11 c h a r b u f [ SIZE BUFFER ] ; 12 i n t i ; 13 u n s i g n e d u ; 14 15 p r i n t f (” h e l l o , w o r l d\n”) ; // 単 純 に 文 字 列 を 出 力 16 17 p r i n t f (”なんか入力くれくれ\n”) ; 18 s c a n f (”%s ”, b u f ) ; // % sで 標 準 入 力 か ら 文 字 列 を 取 得 19 20 // % sで 文 字 列 出 力 21 p r i n t f (”「% s」 が 入 力 さ れ た と か な ん と か\n”, b u f ) ; 22 23 // %dで1 0進 符 号 付 整 数 入 出 力 (d i g i t) 24 p r i n t f (”整数くれくれ\n”) ; 25 s c a n f (”%d”,& i ) ; 26 p r i n t f (”%dが 入 力 さ れ た\n”, i ) ; 27 28 u = i ; 29 p r i n t f (” 8進 数 だ と%o\n”, u ) ; // %oで 符 号 な し8進 30 p r i n t f (” 16進 数 で%04x\n”, u ) ; // %xで 符 号 な し1 6進 31 // 0を 詰 め な が ら4桁 表 示 32 33 f p = f o p e n (RW FILE , ” a+b”) ; 34 i f ( f p==NULL) { // f o p e n ( )は 失 敗 す る とNULLを返す
35 p r i n t f (” can ’ t open t h e f i l e ” RW FILE ”\n”) ; 36 r e t u r n 1 ; 37 } 38 f s c a n f ( fp , ”%s\n”, b u f ) ; 39 f p r i n t f ( fp , ”%s\n%i \n%x\n”, buf , i , u ) ; 40 f c l o s e ( f p ) ; 41 }
フォーマット入出力の変換指定子はサンプルに挙げた以外にも幾つか種類がある。以下 はその一例。 • %c(1文字) • %g(浮動小数点数(double)) • %p(ポインタ) 1.2.3 バッファリング バッファとはある場所から別の場所へデータを送り出す際、一時的にそのデータを記憶し ておく領域のことである。バッファリングが行われるのには状況により幾つかの異なる理 由が考えられるが、ここではファイルストリームのバッファリングについて説明する。 デバイスの管理はOSの役割であるため、当然stdioはディスク入出力を行うために内部 でOSのシステムコールを呼ぶこととなる。システムコールとディスクへのアクセスはどち らも共に重い処理であるため、プログラムが「ファイルから1文字読み出して大文字小文字 を変換して別のファイルへ書き込んで」というような処理を愚直にやっていては効率が悪 い。はじめに1度のシステムコールで多めのデータをメモリ上のバッファへ読み込んでお き、その範囲外のデータをアクセスしようとしたときはじめて2度目のシステムコールを呼 ぶ、というような手順を繰り返していけば、重い処理を頻繁に行う必要がなくなり、効率が 上がる。書き出しの際も同様に、メモリ上である程度まとまった量のデータをバッファリン グしてから、一気にディスクへ書き込む。 stdioのファイルストリームはデフォルトで完全にバッファリングされており、バッファ の内容はバッファが一杯になったとき、fclose()でファイルが閉じられた時、fflush()が呼 ばれた時に実際に書き出される(フラッシュされる)。 1.2.4 標準入出力 C標準ライブラリをリンクするプログラムでは、起動時に3つのファイルストリームがあ
らかじめ開かれている。それは標準入力stdin、標準出力stdout、標準エラー出力stderr
の3つである。これらは明示的にfopen()を呼んで開かなくとも、stdioのFILE*をとる関
数へ渡すことができる。printf(””)はfprintf(stdout,””)と同じ意味である。
STL のcinとcoutはそれぞれstdinとstdoutと結びついており、stderrと結びついた
cerrというものもある。 注意せねばならないのは、cin/cout とstdin/stdout は混在させてはならないというこ とだ。実は絶対に駄目だということはないが、できる限り避けた方がいい。cin/cout と stdin/stdoutは同じものを指していながら、それぞれが個別にバッファリングしているた め、cout/stdoutの混用では適宜にバッファをフラッシュするのでない限り出力の順序が保 障されなくなり、cin/stdinの混用では入力データを見失う可能性がある。プログラムの先
頭で を呼び出すことでバッファを共有するようになり、 とstdin/stdoutの混用が可能になるが、これは処理系によってはオーバーヘッドがあり動 作が遅くなる。 なお以上は標準入出力を使用する際の注意としてのみあてはまるものであり、C標準ラ イブラリのstdioとSTLのiostreamが混在させられないという意味ではない。それぞれで 別々のファイルストリームを扱う分にはまったく問題ない(cin/coutとfopen()など)。 1.3 ちょっといい話 1.3.1 stdio.hとcstdio 前述のようにC言語の標準ライブラリはC++にも取り込まれたが、その際ヘッダの名
前がstdio.hからcstdio、stdlib.hからcstdlibといったように、先頭へcを加え末尾の.hを
取り除いたものへ変更されている。C++では互換性のため古い形式のヘッダも標準へ含ま れているため、まともな処理系ならどちらをincludeしてもlibcへアクセスできるが、大 きな違いとして新形式版(cNAME)の方ではすべての宣言がstd名前空間にあり、旧形式 版(NAME.h)では大域で宣言されているということがある *6。既存のC言語のコードを C++へ移植する場合は旧形式の名前でincludeし、新規のC++コードでは新形式の名前 でincludeするなどといった区別をするとよい。 1.3.2 標準ライブラリの調べ方 標準ライブラリは言語の一部ではあるものの、全ての関数名などを覚える必要はない。と いうかほとんど一切何も覚えなくていい。標準ライブラリにどんな機能があるかだけは把 握しておいて、実際使うときにいちいちGoogle様で検索したりして調べる。繰り返してる うちに何度も使う関数なんかは勝手に覚える。魚を得ようとするより魚の釣り方を覚える こと。3桁の掛け算を暗記しなくても筆算のやり方を覚えればいい。進歩とか流動の激しい 世界なんで、技術を覚えるより技術の調べ方を覚えた方が潰しがきく*7。 まず、欲しい機能が含まれてそうなヘッダをじっと見つめる。関数名とかであたりをつけ て「strcpy+C言語」とか、「C言語+時刻+取得」とか「C言語+ファイル+書き込み」 みたいな感じでGoogle様で検索。
Unix 系の OS や Cygwin なら Manpage が用意されてる。コマンドラインで「man
fopen」とかやればfopen()のマニュアルが読める。「man stdio」とかやればヘッダの説明
が読める。ネットで公開されてるLinux用のManpageとかもあって、標準関数なんかを 検索すると割とひっかかる。OS毎の独自拡張もあったりするけど、C標準準拠の部分は LinuxだろうがWindowsだろうが内容変わらないので参考になる。 検索とかで引っかかったら、後は簡単なコードを書いて使い方をテスト。必要なだけ使い 方を把握したら、実際のプログラムへ取り込む。 *6正確には、cstdio などで std 名前空間に宣言される名前を大域の using 宣言で引っ張ってきている *7ちなみに文法も覚えるというより沢山書いて沢山読んで慣れるようにした方が楽
2
ポインタ(中)
2.1 sizeof(データサイズ) sizeof演算子を使うことで、型や変数のサイズを取得することができる。これは配列の 要素数を取得する場合など、幾つかの局面で利用することができる。 sizeof.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 6 i n t main (v o i d) 7 { 8 i n t i n t e g e r ; 9 i n t a r r a y [ ] = {9 , 8 , 7 , 6 , 5 , 4 , 3}; 10 s t r u c t S { 11 c h a r c ; 12 i n t i ; 13 d o u b l e d ; 14 } ; 15 16 /∗ 型 の サ イ ズ は 括 弧 が 必 要 、 変 数 の サ イ ズ な ら )∗/ 17 c o u t << ” c h a r / i n t / s t r u c t S” << e n d l ; 18 c o u t << s i z e o f (c h a r) << ” / ” << s i z e o f (i n t) << ” / ” 19 << s i z e o f (s t r u c t S ) << e n d l ; 20 21 c o u t << ” c h a r∗/ i n t ∗/ s t r u c t S∗” << e n d l ; 22 c o u t << s i z e o f (c h a r∗) << ” / ” << s i z e o f (i n t∗) << ” / ” 23 << s i z e o f (s t r u c t S∗) << endl ; 24 25 c o u t << ” i n t e g e r : ” << s i z e o f i n t e g e r << e n d l ; 26 c o u t << ” a r r a y : ” << s i z e o f a r r a y << e n d l ; 2729 f o r (i n t i =0; i < s i z e o f a r r a y / s i z e o f a r r a y [ 0 ] ; i ++) 30 c o u t << a r r a y [ i ] << e n d l ; 31 32 r e t u r n 0 ; 33 } 2.2 動的メモリ C/C++ においてプログラムの関数内で定義されたローカル変数は通常自動変数(auto 変数)であり、関数の実行終了と同時にそのデータ領域の有効性は破棄される。また、グ ローバル変数や関数内でstaticとして定義された静的変数のデータ領域はプログラムの実 行中ずっと確保されてある。いずれにせよ、確保されるメモリ領域のサイズはコンパイル時 に確定されているため、大きなデータと小さなデータのどちらも扱う可能性がある場合にメ モリ領域を固定的に(大きめに)確保しておく必要があり、結局小さなデータを扱うことに なった場合無駄が大きくなる*8。C/C++にはこれらとは別に動的メモリ領域(ヒープ)と いう種類のメモリ領域がある。動的に確保されたメモリの生存期間(有効期間)はプログラ マが自由に決めることができ、またそのサイズはプログラムの実行時に変更することができ る。これにより大きなデータには大きなメモリを、小さなデータには小さなメモリを、と必 要なだけのメモリを確保することができるため、無駄がなくなり、更にそのデータ領域を関 数をまたいで使い回すことができる。 2.3 newとdelete newを使うことで指定サイズのメモリ領域を動的に確保し、deleteによって解放するこ とができる。確保したメモリ領域がどこにあるかをポインタで捕捉し、各種のデータの操作 はこのポインタを経由して行う。 new.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 *8C99 では配列のサイズを変数で指定して確保することができる(可変長配列)。これは強力な機能だが、C++ では不可能なため、 C との間の非互換の 1 つとなってしまっている。
5 6 i n t main (v o i d) 7 { 8 s t r u c t S { 9 i n t i ; 10 c h a r c [ 1 6 ] ; 11 } ; 12 13 // n e wに よ る 領 域 の 確 保 14 c h a r ∗ pchar = new c h a r; 15 i n t ∗ pint = new i n t ; 16 s t r u c t S ∗ps = new s t r u c t S ; 17 18 //ポインタ経由でのデータの操作 19 ∗ pint = 20000; 20 ∗ pchar = ’P ’; 21 ps−>i = 10; 22 ps−>c [ 0 ] = ’ a ’; ps−>c [ 1 ] = ’ b ’; ps−>c [ 2 ] = ’ c ’; 23 ps−>c [ 3 ] = ’ d ’; ps−>c [ 4 ] = ’ e ’; ps−>c [ 5 ] = ’\0 ’; 24 25 c o u t << ∗ pint << endl ; 26 c o u t << ∗ pchar << endl ; 27 c o u t << ps−>i << endl ; 28 c o u t << ps−>c << endl ; 29 30 // d e l e t eに よ る 領 域 の 解 放 31 d e l e t e p c h a r ; 32 d e l e t e p i n t ; 33 d e l e t e ps ; 34 35 r e t u r n 0 ; 36 }
動的配列 new を使って配列を作ることもできる。静的な配列はサイズを定数でしか指定できない が、この動的配列はサイズを実行時に決めることができる。動的配列を解放するには通常の deleteではなく、delete[]を使う必要がある。 new array.cpp 1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 6 i n t ∗ c r e a t e a r r a y (i n t num) 7 {
8 i n t ∗p = new i n t[ num ] ; // 配 列 のnew
9 10 f o r (i n t i =0; i < num ; i ++) 11 p [ i ] = num − i ; 12 13 r e t u r n p ; 14 } 15 16 v o i d d e s t r o y a r r a y (i n t a [ ] ) 17 { 18 d e l e t e [ ] a ; // 配 列 の 解 体 はd e l e t e [ ] 19 } 20 21 i n t main (v o i d) 22 { 23 i n t ∗p ; 24 i n t num ; 25 26 c o u t << ”整数くれくれ” << e n d l ; 27 c i n >> num ;
28 29 p = c r e a t e a r r a y (num ) ; 30 31 f o r (i n t i =0; i < num ; i ++) 32 c o u t << p [ i ] << e n d l ; 33 34 d e s t r o y a r r a y ( p ) ; 35 36 r e t u r n 0 ; 37 } なお動的配列はポインタを経由しなければ参照できないので、sizeofは動的配列のサイズ を知ることはできない(ポインタのサイズしか得られない)。 2.5 メモリリーク
newで得たメモリ領域をdeleteし忘れることをメモリリークという。newにより確保し
たメモリ領域は、deleteされるまで解放されない。そのため、サーバーのような長時間起動 し続けるプログラムでdeleteを忘れてしまった場合、そのプログラムがそのメモリ領域を ずっと占有し続けることになる。また、そのメモリをnewするコードが起動中に何度も実 行されるようになっている場合、プログラムのメモリ消費量はどんどん増大してシステムを 圧迫することになる。メモリの解放を忘れてはならない。 こうしたメモリ管理はC/C++プログラマが必ず正確にやらなければならないことだが、 何についてでもミスを完全になくすことは難しい。メモリ管理をプログラムに自動で行わ せるため、GC(Garbage Collection)やスマートポインタといった仕組みの力を借り るプログラマも多い*9。C/C++用の有名な GCライブラリとしてはBoehmGC *10などが ある。 2.6 voidポインタ voidポインタは特別なポインタであり、あらゆる型のポインタを保持することができる。 voidは無なのでvoidポインタの中身を直接参照することはできないが、キャストにより型 変換を行い元の型のポインタへ戻すことで中身を参照できるようになる*11。 *9一部の言語には GC が標準で備わっている。Java や C#、D 言語といった C++ の後継にあたる言語が全て GC を採用してい るというのは注目すべき点だ *10http://www.hpl.hp.com/personal/Hans Boehm/gc/ *11C 言語だとキャストなしで void ポインタを代入できるが、C++ では型のチェックが厳密なので不可能
1 #i n c l u d e <i o s t r e a m > 2 3 u s i n g namespace s t d ; 4 5 6 i n t main (v o i d) 7 { 8 v o i d ∗p ; // v o i dポ イ ン タ 9 i n t ∗ pint ; 10 i n t i = 2 3 4 ; 11 12 p = &i ; // i n tポ イ ン タ をv o i dポ イ ン タ へ 13 14 p i n t = (i n t∗)p ; // i n tポ イ ン タ へ の キ ャ ス ト 15 16 c o u t << ∗ pint << endl ; 17 18 r e t u r n 0 ; 19 } 関数内で何らかのデータをnewを使って作成し、voidポインタでそのデータを呼び出し 元へ返し、そのポインタをとってデータへの操作を行うような一連の関数群を作成すること で、データの内部実装を隠蔽し、プログラムの他の部分との結合度(依存関係)が強くなる のを抑制して部品としての強度を高めることができる。