Outline プログラミング演習第 8 回やり残したこと on 2012.12.13 ファイルを使う メモリの管理 簡単なデバッグの方法 演習課題 電気通信大学情報理工学部知能機械工学科長井隆行 2 入出力 ( ファイルを使う ) ハードディスクにあるデータを使ったり ハードディスクに結果を記録するためには ファイルを読み書きできないといけない 細かいことはどうでもいいので まずやるべきことは 操作するファイルの名前を指定して ファイルをオープンし ファイルポインタを取得する 最後に必ずクローズする ファイルポインタとは FILE 構造体へのポインタ FILE 構造体は 入出力の現在位置 ファイルの終端に達したかの情報 エラー情報 関連するバッファへのポインタなどのファイルの入出力を行う上での必要不可欠な情報を管理 ファイル名との関連付けを行う fopen(const char *filename, const char *mode ) ファイル名 (filename) で示されるファイルを 指定モード (mode) で オープンする モード 動作 ファイルがあるとき ファイルがないとき "r" 読み出し専用 正常 エラー (NULL 返却 ) "w" 書き込み専用 サイズを 0 にする ( 上書き ) 新規作成 "a" 追加書き込み専用 最後に追加する 新規作成 fclose(file *fp); fopen でオープンされたファイルポインタ (fp) で示されるファイルをクローズする 3 4
実際の読み書きをする関数 fprintf(file *fp, const char *format,...); ファイルポインタ (fp) で指定したファイルへ書式つきで出力する printf のファイル版 ファイルポインタを指定する以外は printf と同じ ファイルへの出力例 #include<stdio.h ファイルポインタの宣言 FILE* fp; ファイルのオープン char moji[] = "software engineering"; int hoge = 100; float foo = 153.5f; 実際に書き込み fscanf(file *fp, const char *format,...); ファイルポインタ (fp) で指定したファイルから書式つきで入力する scanf のファイル版 ファイルポインタを指定する以外は scanf と同じ 5 fp = fopen("out_file.txt", "w"); fprintf(fp, "%s n", moji); fprintf(fp, "hoge is %d ", hoge); fprintf(fp, "foo is %f n", foo); fclose(fp); p8-1.c gcc -o p8-1 p8-1.c./p8-1 cat out_file.txt software engineering hoge is 100 foo is 153.500000 ファイルのクローズ 6 メモリを確保する メモリの動的確保 配列の欠点は何でしょう? 要素数を予め決めておく必要がある プログラムを実行しないとデータの数が分からない場合がたくさんある ( 例 ) 画像ファイルを開いて表示 画像サイズは開いてみないと分からない ものすごく大きな要素数の配列をその都度用意するのは大変 ( 時間がかかる ) 関数が呼ばれる毎にスタックに積むのは大変 スタック領域ではなくヒープ領域を使い長期的 にメモリを使用する 関数 malloc( サイズ ) を使用する malloc(size) を実行すると size バイト分のメモリ領域が確保され (OS が確保してくれる ) その先頭を指すポインタが返される 不必要になった段階でメモリを開放する必要がある free( ポインタ ) を使用する 0 番 1 番 2 番 3 番 4 番 data[0] data[1] data[2] data[3] data[4] data = malloc(sizeof(int)*5); ヒープ領域 : 長期的に使用される大きなサイズのメモリを格納する領域 7 dataはポインタ ( 確保された領域の先頭のアドレスが入る ) 確保された領域は data[i] や *(data+i) のように配列と同じ方法で使うことができる 8
変数のスコープと寿命 ( 復習 ) malloc の使用例 スコープのお話 宣言方法 寿命 スコープ 初期化 全てのブロック外 プログラム実行中 プログラム全体 宣言時の一度だけ 全てのブロック外でstatic プログラム実行中 モジュール内 プログラム開始時の一度だけ (static+global) ブロック内 ブロック内のみ一時的 ブロック内 ブロックに入るたび ブロック内でのstatic プログラム実行中 ブロック内 プログラム開始時の一度だけ malloc 関数の使用 malloc() からfree() まで ポインタ変数の宣言による 関数の仮引数として 関数ブロック内のみ 関数ブロック内 注 ) モジュール : プログラムをいくつかのファイルに分割した場合の各ファイルに相当する malloc(), free() に関しては後日説明予定 9 int* data; int I, memsize; scanf("%d", &memsize); data= malloc(sizeof(int)*memsize); /* エラー処理 ( メモリが確保できなかった場合 )*/ if(data==null) printf(" メモリが足りません n"); /* 確保したメモリに順番に値を格納する */ for(i=0; i<memsize; i++) data[i]=i /* 確保したメモリの開放 */ free(data); int data[memsize]; メモリが確保できなかった場合 malloc は NULL を返す 配列と同じ使い方ができる p8-2.c 10 練習問題 テキストファイルの値を見てメモリを確保しデータを読み込むプログラム データ数 5 123 456 789 321 654 data.txt データ本体 int* Data; int i, DataSize; FILE* fp; fp=fopen("data.txt", "r"); if(!fp) printf(" ファイルが開けません "); /* データサイズを読み込み */ fscanf(fp,"%d", &DataSize); /* メモリの確保 */ Data= malloc(sizeof(int)*datasize); /* データの読み込み */ fscanf(fp,"%d", Data+i); /* データの表示 */ printf("%d n", Data[i]); p8-3.c free(data); fclose(fp); 11 int* Data; int i, DataSize; FILE* fp; fp=fopen("data.txt", "r"); if(!fp) printf(" ファイルが開けません "); /* データサイズを読み込み */ fscanf(fp,"%d", &DataSize); /* メモリの確保 */ Data= malloc(sizeof(int)*datasize); 解答例 p8-3.c /* データの読み込み */ fscanf(fp,"%d", Data+i); /* データの表示 */ printf("%d n", Data[i]); free(data); fclose(fp); &Data[i] も可 *(Data+i) も可 12
こんなときどうする? プログラムを書いたがコンパイルがうまくいかない ( エラーが出る ) もーやめた と言う前に どんなに間違いを見つけようとしてもどうしても見つからない もーやめた! とりあえず落ち着きましょう bcc32 EXerror.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland EXerror.c: エラー E2451 EXerror.c 9: 未定義のシンボル hoge1( 関数 main ) 警告 W8004 EXerror.c 11: 'foo' に代入した値は使われていない ( 関数 main ) 警告 W8004 EXerror.c 11: 'hoge' に代入した値は使われていない ( 関数 main ) *** 1 errors in Compile *** ちょっと休憩するかその日はやめて次の日取り組む ( 間をおく ) 休憩後ソースファイルを印刷して眺めてみる 以外に簡単な間違いだったことに気づく こともある 最初から作り直す もっと具体的な対処方法はないの? 13 14 対処法その 1 プログラムを書いたがコンパイルがうまくいかないこれはデバッグというよりは 言葉 ( 文法 ) の問題です エラーメッセージをよーく見る とにかく間違いを探す どこに間違いがあるかをはっきりさせることが先決 怪しいところをコメントアウトしてコンパイルしてみる 対処法その 1 つづき よくある間違い セミコロン (;) 忘れ [] () などの不整合 変数や関数の宣言忘れ 変数の綴りの間違い 記号の打ち間違え 代入の際に型があっていない 全角文字が入ってしまっている 引数の間違え 15 16
エラーメッセージ (borlandc++ の場合 ) デバッグライトによるデバッグ #include<stdio.h double hoge = 123.456 /* 変数の宣言 */ int foo =100; /* 変数の宣言 */ double* hoge_p; /* ポインタ変数の宣言 */ hoge_p = &foo; printf("%f n", hoge1); return 0 bcc32 p8-4.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland p8-4.c: エラー E2141 p8-4.c 6: 宣言の構文エラー ( 関数 main ) エラー E2451 p8-4.c 9: 未定義のシンボル foo( 関数 main ) エラー E2451 p8-4.c 11: 未定義のシンボル hoge1( 関数 main ) エラー E2378 p8-4.c 13: Return 文に ; がない ( 関数 main ) 警告 W8004 p8-4.c 13: 'hoge' に代入した値は使われていない ( 関数 main ) *** 4 errors in Compile *** p8-4.c Borland C++ の場合 コンパイルはできたが実行すると結果がおかしい デバッグしましょう 怪しいところをコメントアウトしてみる 間違いのある場所を徐々に特定していく printfを使ったデバッグ デバッグライト とにかく計算の途中結果を確認することが大事 多くの場合はこれで間違いを探すことができる 警告 (warning) は無視してよい場合とそうでない場合がある 17 18 デバッグライトによるデバッグ int value; /* キーボードから入力された数値 */ printf("enter number : "); scanf("%d", value); if(value = 10) /* 数値が10であるか判定 */ printf("input number is 10. n"); else printf("input number is not 10. n"); p8-5.c どこに printf を入れますか? どこがいけないのでしょう? bcc32 p8-5.c Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland p7-5.c: 警告 W8060 p7-5.c 12: おそらく不正な代入 ( 関数 main ) Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland p8-5 Enter number : 2 Input number is 10. おかしい! 19 練習問題 1 から 10 までに奇数がいくつあるか数えるプログラム int count; /* 奇数の数 */ int odd; /* 現在の奇数値 */ count = 0; odd = 1; /* 奇数の初期値 */ while(odd!= 10) /* 10 になるまで */ count++; /* 奇数の数を加算 */ odd = odd+2; /* 次の奇数へ */ printf("count = %d n", count); /* 奇数の数を表示 */ p8-6.c このままだと無限ループにはまりますどうやって間違いを探せばよいでしょう? 間違いはどこにあるのでしょう? 20
解答例 1 から 10 までに奇数がいくつあるか数えるプログラム int count; /* 奇数の数 */ int odd; /* 現在の奇数値 */ count = 0; odd = 1; While (odd < 10) /* 奇数の初期値 */ while(odd!= 10) /* 10になるまで */ count++; /* 奇数の数を加算 */ odd = odd+2; /* 次の奇数へ */ printf("count = %d n", count); /* 奇数の数を表示 */ p8-7.c ここがまずい! oddが10になることはない このままだと無限ループにはまりますどうやって間違いを探せばよいでしょう? 間違いはどこにあるのでしょう? 21 デバッガを使う 対処法その 3 デバッガは プログラムの実行時の動作を確認し エラーの場所を特定できる強力なツール デバッグライトは有効なデバッグ方法だが 問題の場所を特定して解決した後に コード全体を見直して余分な関数呼び出しをすべて削除する必要がある printf などの呼び出しを1つ追加しただけでも 新しいコードが追加されたことで デバッグを行っているコードの動作が変更されることがある デバッガを使用すると 変数値を出力する追加の呼び出しを挿入せずに プログラムの変数値をチェックできる! コードにブレークポイントを挿入すると 調べたい位置で実行が中断される 22 gcc の場合 gdb を使う 基本は ブレークポイントをつけてステップ実行 変数の中身 ( 値 ) をチェックしていく コンパイル時に g オプションをつける gcc g ファイル名 (xxx.c) int i, count; count = 0; for(i=0; i<3; i++) count++; printf("count = %d n", count); gdb: デバッガの起動 run: プログラムの実行 next: 次の1 行を実行 ( 関数は1 行とみなす ) step: 次の1 行を実行 ( 関数内も順次実行 ) quit: デバッガの終了 list: プログラムの表示 break: ブレイクポイントの設定 delete: ブレイクポイントの削除 print: 変数の値を調べる whatis: 変数の型を調べる gdbの主要なコマンド このプログラムをトレースしてみる 23 gcc -g deb.c gdb a.exe GNU gdb 2003-09-20-cvs (cygwin-special) Copyright 2003 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i686-pc-cygwin"... (gdb) list 1 2 3 4 int i, count; 5 count = 0; 7 9 10 printf("count = %d n", count); (gdb) break 5 Breakpoint 1 at 0x40108a: file deb.c, line 5. (gdb) run Starting program: /home/tnagai/soft_eng/a.exe Breakpoint 1, main () at deb.c:5 5 count = 0; $1 = 0 $2 = 1 (gdb) print i $3 = 0 $4 = 2 (gdb) print i $5 = 1 $6 = 3 (gdb) print i $7 = 2 10 printf("count = %d n", count); (gdb) continue Continuing. count = 3 Program exited normally. (gdb) quit 24
本日の演習 ー準備ー まずは p8-8.c(check_maxまでの整数からすべての素数を探すプログラム ) をダウンロードする 配列を使っていくつまでできるか試してみる CHECK_MAXを10 倍しながら実行してみる 変な挙動をしたら ( 多分 ) 配列の数が限界を超えている ー課題ー ( ここから先をメールで送る ) p8-9.cをダウンロード P8-8.cをメモリ確保することで実現する 配列でできなかったものでもメモリを確保すればできることを確かめてみる 見つかった素数をファイルに書き出してみる p8-3.cのdata.txtを参考にする ( 余裕があれば書き出した素数を再度プログラムで読み込んでみる ) ソースコードと実行結果をメールで送る 送る際には注意事項をよく確認すること http://apple.ee.uec.ac.jp/comprog 諸注意 今日の講義の感想 質問をメール本文に書いてください よく分かった ここが分からない など 25 #include <math.h #define CHECK_MAX 10000 /* この値までの素数を探す */ int count; int i, j, k; int prime[check_max]; /*CHECK_MAX 個の配列を用意しておく */ printf("2 "); /* 最初の素数 */ count = 1; /*1 カウント */ prime[0]=2; /*2 を登録 */ #define CHECK_MAX 10000 マクロ定義プログラム中の CHECK_MAX という文字列を 10000 で置き換えろという ( プリプロセッサ ) 命令 /* 素数計算メイン */ for(i=3; i<=check_max; i+=2) k=0; for(j=3; j<=sqrt(i); j+=2) if(i%j==0) k=1; break; /* 素数じゃない */ if(k==0) count++; /* 素数発見 */ prime[count-1]=i; printf("%d ",prime[count-1]); printf(" n"); p8-8.c 26 #include <string.h #include <math.h int check_max, count; int i, j, k; int* prime; scanf("%d", &check_max); /* キーボードからの入力待ち */ /********************************************************/ /* ここで malloc を使って必要なメモリを確保する */ /* prime = malloc(????????????????????); */ /********************************************************/ memset(prime, 0, sizeof(int)*check_max); /* ゼロで埋める ( 初期化 )*/ printf("2 "); count = 1; prime[0]=2; p8-9.c /* 素数判定 */ for(i=3; i<=check_max; i+=2) k=0; for(j=3; j<=sqrt(i); j+=2) if(i%j==0) k=1; break; if(k==0) count++; prime[count-1]=i; printf("%d ",prime[count-1]); printf(" n"); /**************************/ /* ここでファイルに書き出す */ /**************************/ /******************/ /* ここでメモリ解放 */ /******************/ 27