情報処理 Ⅱ 2012 年 1 月
1~2 月のスケジュール 1 月 16 日 ( 月 ): 第 12 回授業 1 月 19 日 ( 木 ): レポート課題提出期限 17:00 までに, 学科事務室前に提出 1 月 23 日 ( 月 ): 第 13 回授業 1 月 30 日 ( 月 ): 第 14 回授業,A601で実施 1 月 31 日 ( 火 ): 第 15 回授業, おさらい問題 (2 月 6 日 : 予備日のため授業なし ) 2 月? 日 : 試験 2
前処理とコンパイル (1) ソースファイル ( 前処理前 ) 前処理 ソースファイル ( 前処理後 ) コンパイルアセンブル オブジェクトファイル リンク 実行ファイル 前処理 コンパイル アセンブル リンクの各処理は通常, コンパイラ (cc など ) が一手に引き受ける. 3
前処理とコンパイル (2) 前処理は, 狭義には, コンパイルに先立って行われる処理 であり, したがってコンパイルとは別広義には,ccでコンパイルすれば自動的に処理してくれる, という意味でコンパイル作業の一部 前処理のコマンド ( プリプロセッサ, 前処理系 ) は,cpp C の前処理以外にも使用可能 4
オブジェクト形式マクロの注意点 前提 : #define WORD_SIZE 6+1 単純に置き換える. int a[word_size * 2]; は,int a[6+1 * 2]; に置き換えられる ( 意図した動作ではない ). #define WORD_SIZE (6+1) とすればよい. 語のみを置き換える. print_word_size() のような 語の一部 や, printf("word_size"); のような 文字列中の語 は, 置き換えない. 5
オブジェクト形式マクロの注意点 予約語も置換可能. #define char signed char は文法上問題ないが, よい書き方ではない. typedef signed char schar; とすべきである. 末尾にセミコロンをつけない. #define WORD_SIZE 6; は ( たいていの場合 ) 間違い. 6
関数形式マクロの注意点 単純に置き換える. #define mul(x, y) x*y に対して, z=mul(6+1,2); としたとき,z=14 ではなく z=8 となる. 9/mul(3,3) は,1 ではなく 9 と評価される. #define mul(x, y) ((x)*(y)) のように, 置換要素には, 全体と各引数にカッコをつける. 置換要素の中に, 引数を 2 箇所以上書くことができる. その回数だけ置換される. #define triple(x) ((x)+(x)+(x)) に対して b=triple(++a); と書くと,b=((++a)+(++a)+(++a)); となる. 一つの式で, 同一オブジェクトに複数回の代入を行ったときの結果は未定義 7
関数か関数形式マクロか 関数 機能 を正確に表現したいとき 例 :int square_int(int x) { return x * x; } 引数や戻り値の型に制約される. 関数呼び出しのオーバーヘッドがある. ローカル変数や制御文を活用できる. 実引数が ++a などのときも, その評価は一度だけ. マクロ 機能 を簡便に表現したいとき 例 :#define square_int(x) ((x) * (x)) 引数や評価式に型はない. ( 狭義の ) コンパイル前に展開され, オーバーヘッドは少ない. ローカル変数や制御文は使用しにくい. ( マクロ利用側の ) 引数は, 置換要素内の回数だけ評価される. connection2.c 8
条件付きコンパイルの注意点 if 文は実行時に判定 分岐するが, 条件付きコンパイル (#if ほか ) は, 前処理時にコンパイル対象の取捨選択をする. 条件付きコンパイル #if 条件式 1 #elif 条件式 2 #else #endif if 文 if ( 条件式 1) { } else if ( 条件式 2) { } else { } 条件付きコンパイルは入れ子にできる. #if 0 ~ #endif はコメントと同じで無視される. 9
前処理指令と空白 コメント... 不可... 必須 # define pint( x ) printf ( #x " = %d n", x ) 一つの前処理指令は,1 行で書かなければならない. ただし, 行末に を置くことで, 複数行で書ける. 関数形式マクロの場合, 括弧の途中で改行できる. コメント (/* */) は, 前処理時に空白文字に置き換えられる.... 任意 10
標準入力 標準出力とコンソール 実行環境 (OS など ) 'a''b''c' ' n' 標準入力 コンソール 実行プログラム '1'':'' ''a''b''c' ' n' $./linenumber abc 1: abc $ 標準出力 実行コマンド入力 ( エコーバック ) 出力 linenumber.c 11
コンソール入力の注意点 Enter を押すことで,1 行分の入力が標準入力に送られる. Enter を押すまでは, 標準入力として送られない. 低水準入出力 (raw モード ) を利用すれば, キー入力ごとに値を取得できる. 入力終了は, 行頭で Ctrl+D Ctrl+C はプログラムの終了 12
EOF(1) ファイルの終わり (End Of File) を表す定数. stdio.h で,#define EOF ( 1) などと定義されている. unsigned char signed char ASCII (7 ビット ) 128 1 0 127 255 EOF ナル文字 13
EOF(2), ファイルアクセス 文字 ( バイト ) 単位で読み書きするとき int 型を使用する. signed char 型だと,255をEOFと誤認識する. unsigned char 型だと,EOFを255と誤認識する. EOFと0から255までの 257 種類の値 は,char 型 (signed char 型,unsigned char 型 ) で区別できない! 読み出して EOF を受け取ったら, そこでおしまいにする. ライブラリ関数は getchar, putchar など 文字列単位で ( 何バイトか一括して ) 読み書きするとき char 配列または char* 型を使用する. ライブラリ関数は fgets, fprintf など 14
入力方法あれこれ プログラム内に書き込む ( ハードコード ). int a = 44, b = 16; コマンドライン引数から獲得する. int main(int argc, char *argv[]) 標準入力 ( キーボード入力 ) から獲得する. scanf,getchar,gets など ファイルをアクセスする. fopen,fscanf,fgets など 入力 出力 実行プログラム 15
入力方法の得失 (1) ハードコード プログラム内に値を書き込む ( 埋め込む ) こと. 手軽 ( 原始的 ) であり, 他の環境でも実行しやすい. 入力の値の型は, プログラム内で指定できる. 入力の値が変わるたびにコンパイルが必要. コマンドライン引数 入力の値が変わってもコンパイル不要. 実行時に毎回引数指定が必要. シェルのヒストリ機能を使えば省力化できる. 入力サイズには ( 現実的な ) 制限がある. 入力の値の型は必ず文字列. メリットとデメリット のこと. ある目的を達成するための手段が複数あるとき, どれを選ぶかの判断材料になる. 16
入力方法の得失 (2) 標準入力 入力の値が変わってもコンパイル不要. 入力サイズに制限がない. 実行時に毎回入力が必要. シェルのリダイレクション機能を使えば省力化できる. 入力の値の型は原則として文字または文字列. ファイルアクセス 最も洗練された手法. 標準入力の特長を受け継ぐ. ファイルの内容を変えなければ, 同じ入力が得られる. プログラムは複雑になる. 17
入力方法の比較 洗練柔軟 ファイルアクセス コマンドライン引数 標準入力 静的 (main 関数実行前に入力値が決まる ) 動的 ( 実行中に入力値を与える ) ハードコード 原始的固定 18
fscanf を用いた読み出し (1) if (fscanf(fp, "%d %d", &v0, &v1) == 2) fscanf 呼び出し前 プログラムの内部状態 ファイル twonum.txt '8' ' ' '1' '3' ' n' ストリーム fp v0=? FILE オブジェクト v1=? ' 0' がない ( 文字列ではない ) twonumber.c 19
fscanf を用いた読み出し (2) if (fscanf(fp, "%d %d", &v0, &v1) == 2) 2 条件式は真 fscanf 呼び出し後 プログラムの内部状態 ファイル twonum.txt '8' ' ' '1' '3' ' n' fp FILE オブジェクト v0=8 v1=13 代入される 20
fscanf を用いた読み出し (3) if (fscanf(fp, "%d %d", &v0, &v1) == 2) fscanf 呼び出し前 プログラムの内部状態 ファイル twonum.txt 'x' ' ' '2' ' n' fp FILE オブジェクト v0=? v1=? 21
fscanf を用いた読み出し (4) if (fscanf(fp, "%d %d", &v0, &v1) == 2) 0 条件式は偽 fscanf 呼び出し後 プログラムの内部状態 ファイル twonum.txt 'x' ' ' '2' ' n' fp FILE オブジェクト 進まない v0=? v1=? 値は変わらない 22
読み出し方あれこれ ファイル内の 123 を整数値にするには fscanfで整数値として読み出す fgetsで文字列として読み出し,atoiで整数値に変換する fgetsで文字列として読み出し,sscanfで整数値に変換する fgetcで1 文字ずつ読み出し, 整数値を構成する twonum2.c 23
実行時の領域確保について プログラム実行時 (main 関数に制御が移る前 ) に 静的変数のオブジェクトが確保, 初期化される. プログラム終了時に破棄される. ブロック ({...}) が実行されるときに 自動変数のオブジェクトが確保される. ブロック終了時に破棄される. スタック領域 記憶域管理関数 (malloc, calloc など ) を呼び出すと オブジェクトとして使用できる領域が確保される. freeなどの関数が呼び出されるか, プログラム終了時に破棄される. mallocの語源 :memory allocation( メモリ割り当て ) ヒープ領域 strplus.c 24
malloc の使い方 (1) 準備 #include <stdlib.h> int *p; 必ずポインタ型 (int の部分は用途による ) p = (int *)malloc(sizeof(int)); *p が int 型オブジェクトとして利用可能. p *p sizeof(int) バイトの領域 25
malloc の使い方 (2) 準備 #include <stdlib.h> int *p; 必ずポインタ型 (int の部分は用途による ) p = (int *)malloc(sizeof(int) * 10); p[0],p[1],,p[9] が int の配列であるかのように利用可能. p p[0] p[1] p[9] sizeof(int) * 10 バイトの領域 26
malloc 使用の注意点 領域の値は不定であるため, 必要に応じて初期化する. 代わりに calloc を使用すれば, すべて 0 に初期化された領域が得られる. 代入される変数はポインタ変数なので, 左辺値になり得る (p++; などとできる ). 領域確保に失敗するとNULLを返すので, if ((p = (pの型名)malloc( バイト数 )) == NULL) { エラー処理 } とするのが一般的. 必ずポインタ型 27
記憶域管理関数の得失 メリット 何度呼び出しても, そのたびに異なるメモリ領域から確保する 行儀の良い ( リエントラントな ) 関数を作りやすい 領域サイズは実行時 ( コンパイル時 ) に決まるので, 入力に応じて必要な分だけ確保すればよい メモリ利用の効率化 デメリット 領域が確保できないことによる実行時エラーが起こるかもしれない. 確保した領域をいつ開放する (OSに返す) のか, 考えなければならない. 28
後始末 ファイルの読み書きを終えたら,fclose を用いる. fopen/fclose をペアで覚える. fcloseを呼び出す前のファイルの出力内容は, プログラム内に保持されている ( バッファリング ) 可能性がある. プログラム終了時に, 閉じられていないファイルは保存されるが, これに頼らない ( 積極的にfcloseを用いる ) ほうがよい. ヒープ領域の内容を解放するには,free を用いる. malloc/free をペアで覚える. プログラム終了時に,free されていない領域も破棄されるが, できれば頼らない ( 可能なら free を用いる ) ほうがよい. 29