プログラミング演習 第8回
やり残したこと
on 2012.12.13
電気通信大学情報理工学部
知能機械工学科
長井隆行
2
Outline
ファイルを使う
メモリの管理
簡単なデバッグの方法
演習課題
http://apple.ee.uec.ac.jp/isyslab
入出力
(ファイルを使う)
ファイルポインタとは、
FILE構造体への
ポインタ
FILE構造体は、入出力の現在位置、ファ
イルの終端に達したかの情報、エラー
情報、関連するバッファへのポインタな
どのファイルの入出力を行う上での必
要不可欠な情報を管理
ハードディスクにあるデータを使ったり、ハードディスクに結果を
記録するためには、ファイルを読み書きできないといけない
ファイル名との関連付けを行う
3
http://apple.ee.uec.ac.jp/isyslab
細かいことはどうでもいいので
まず
やるべきこと
は、操作するファイルの名前を指定して、
ファイルをオープンし、ファイルポインタを取得する
最後に必ずクローズする
fopen(
const
char *filename, const char *mode )
ファイル名(filename)で示されるファイルを、指定モード(mode)で
オープンする
fclose(FILE *fp);
fopenでオープンされたファイルポインタ(fp)で示されるファイルをク
ローズする
モード 動作
ファイルがあるとき
ファイルがないとき
"r"
読み出し専用
正常
エラー(NULL返却)
"w"
書き込み専用
サイズを 0 にする(上書き)
新規作成
"a"
追加書き込み専用
最後に追加する
新規作成
4
実際の読み書きをする関数
fprintf(FILE *fp, const char *format, ...);
ファイルポインタ(
fp)で指定したファイルへ書式つ
きで出力する。
printf のファイル版。
ファイルポインタを指定する以外は、
printfと同じ。
fscanf(FILE *fp, const char *format, ...);
ファイルポインタ(
fp)で指定したファイルから書式
つきで入力する。
scanf のファイル版。
ファイルポインタを指定する以外は
scanf と同じ。
5
ファイルへの出力例
#include<stdio.h>
int main(void)
{
FILE* fp;
char moji[] = "software engineering";
int hoge = 100;
float foo = 153.5f;
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);
return 0;
}
> gcc -o p8-1 p8-1.c
> ./p8-1
> cat out_file.txt
software engineering
hoge is 100 foo is 153.500000
>
ファイルポインタの宣言
ファイルのオープン
ファイルのクローズ
実際に書き込み
p8-1.c
6
http://apple.ee.uec.ac.jp/isyslab
メモリを確保する
配列の欠点は何でしょう?
要素数を予め決めておく必要がある
プログラムを実行しないとデータの数が分からない場合がたくさんある
(例)画像ファイルを開いて表示
⇒画像サイズは開いてみないと分からない
ものすごく大きな要素数の配列をその都度
用意するのは大変(時間がかかる)
関数が呼ばれる毎にスタックに積むのは大変
スタック領域ではなくヒープ領域を使い長期的
にメモリを使用する
ヒープ領域:長期的に使用される大きなサイズのメモリ
を格納する領域
7
http://apple.ee.uec.ac.jp/isyslab
メモリの動的確保
関数
malloc(サイズ) を使用する
malloc(size) 、を実行すると、size バイト分のメモリ領域が確保さ
れ
(OSが確保してくれる)、その先頭を指すポインタが返される
不必要になった段階でメモリを開放する必
要がある
free(ポインタ)を使用する
0番
1番
2番
3番
4番
・・・
・・・
data = malloc(sizeof(int)*5);
data[0] data[1] data[2] data[3] data[4]
dataはポインタ(確保された領域の先頭のアドレスが入る)
確保された領域は、data[i]や*(data+i)のように
配列と同じ方法で
使うことができる
8
変数のスコープと寿命(復習)
スコープのお話
宣言方法
寿命
スコープ
初期化
全てのブロック外
プログラム実行中
プログラム全体
宣言時の一度だけ
全てのブロック外でstatic
(static+global)
プログラム実行中
モジュール内
プログラム開始時の一度だけ
ブロック内
ブロック内のみ一時的
ブロック内
ブロックに入るたび
ブロック内でのstatic
プログラム実行中
ブロック内
プログラム開始時の一度だけ
malloc関数の使用
malloc()からfree()まで ポインタ変数の
宣言による
関数の仮引数として
関数ブロック内のみ
関数ブロック内
注)
モジュール:プログラムをいくつかのファイルに分割した場合の各ファイルに相当する
malloc(), free()に関しては後日説明予定
9
mallocの使用例
#include <stdio.h> #include <stdlib.h> int main(void) { int* data; int I, memsize; scanf("%d", &memsize); data= malloc(sizeof(int)*memsize); /*エラー処理(メモリが確保できなかった場合)*/ if(data==NULL) { printf("メモリが足りません¥n"); return 0; } /*確保したメモリに順番に値を格納する*/ for(i=0; i<memsize; i++){ data[i]=i } /*確保したメモリの開放*/ free(data); return 0; }
メモリが確保できなかった場合
mallocはNULLを返す
配列と同じ使い方ができる
p8-2.c
int data[memsize];
10
http://apple.ee.uec.ac.jp/isyslab
練習問題
テキストファイルの値を見てメモリを確保しデータを読み込むプログラム
5
123
456
789
321
654
data.txt
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int* Data;
int i, DataSize;
FILE* fp;
fp=fopen("data.txt", "r");
if(!fp)
{
printf("ファイルが開けません");
return 0;
}
/*データサイズを読み込み*/
fscanf(fp,"%d", &DataSize);
/*メモリの確保*/
Data= malloc(sizeof(int)*DataSize);
/*データの読み込み*/
for(i=0; i<DataSize; i++)
{
fscanf(fp,"%d", Data+i);
}
/*データの表示*/
for(i=0; i<DataSize; i++)
{
printf("%d¥n", Data[i]);
}
free(Data);
fclose(fp);
return 0;
}
データ数
データ本体
11
p8-3.c
http://apple.ee.uec.ac.jp/isyslab
解答例
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int* Data;
int i, DataSize;
FILE* fp;
fp=
fopen("data.txt", "r");
if(!fp)
{
printf("ファイルが開けません");
return 0;
}
/*データサイズを読み込み*/
fscanf(fp,"%d", &DataSize);
/*メモリの確保*/
Data= malloc(sizeof(int)*DataSize);
/*データの読み込み*/
for(i=0; i<DataSize; i++)
{
fscanf(fp,"%d", Data+i);
}
/*データの表示*/
for(i=0; i<DataSize; i++)
{
printf("%d¥n", Data[i]);
}
free(Data);
fclose(fp);
return 0;
}
&Data[i]も可
*(Data+i)も可
p8-3.c
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
http://apple.ee.uec.ac.jp/isyslab
対処法その1
プログラムを書いたがコンパイルがうまくいかない
これはデバッグというよりは、
言葉(文法)
の問題です
エラーメッセージをよーく見る
とにかく間違いを探す
どこに間違いがあるかをはっきりさせることが先決
怪しいところをコメントアウトしてコンパイルしてみる
15
http://apple.ee.uec.ac.jp/isyslab
対処法その1 つづき
よくある間違い
セミコロン
(;)忘れ
{}、[]、()、” ”などの不整合
変数や関数の宣言忘れ
変数の綴りの間違い
記号の打ち間違え
代入の際に型があっていない
全角文字が入ってしまっている
引数の間違え
16
エラーメッセージ
(borlandC++の場合)
#include<stdio.h>
int main(void)
{
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 ***
>
Borland C++の場合
警告(warning)は無視してよい場合とそうでない場合がある
p8-4.c
17
デバッグライトによるデバッグ
コンパイルはできたが実行すると
結果がおかしい
⇒デバッグしましょう
怪しいところをコメントアウトしてみる
間違いのある場所を徐々に特定していく
printfを使ったデバッグ
デバッグライト
とにかく計算の
途中結果を確認
することが大事
多くの場合はこれで間違いを探すことができる
18
http://apple.ee.uec.ac.jp/isyslab
デバッグライトによるデバッグ
#include <stdio.h>
int main(void){
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");
}
return 0;
}
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
http://apple.ee.uec.ac.jp/isyslab
練習問題
#include <stdio.h>
int main(void){
int count;
/* 奇数の数 */
int odd;
/* 現在の奇数値 */
count = 0;
odd = 1;
/* 奇数の初期値 */
while(odd != 10){
/* 10になるまで */
count++; /*
奇数の数を加算 */
odd = odd+2;
/* 次の奇数へ */
}
printf("count = %d¥n", count); /* 奇数の数を表示 */
}
1から10までに奇数がいくつあるか数えるプログラム
このままだと無限ループにはまります
どうやって間違いを探せばよいでしょう?
間違いはどこにあるのでしょう?
p8-6.c
20
解答例
#include <stdio.h>
int main(void){
int count;
/* 奇数の数 */
int odd;
/* 現在の奇数値 */
count = 0;
odd = 1;
/* 奇数の初期値 */
while(odd != 10){
/* 10になるまで */
count++; /*
奇数の数を加算 */
odd = odd+2;
/* 次の奇数へ */
}
printf("count = %d¥n", count); /* 奇数の数を表示 */
}
1から10までに奇数がいくつあるか数えるプログラム
このままだと無限ループにはまります
どうやって間違いを探せばよいでしょう?
間違いはどこにあるのでしょう?
p8-7.c
ここがまずい!
oddが10になることはない
While (odd < 10){
21
対処法その3
デバッガを使う
デバッガは、プログラムの実行時の動作を確認し、エラーの場
所を特定できる強力なツール
デバッグライトは有効なデバッグ方法だが、問題の場所を特定
して解決した後に、コード全体を見直して余分な関数呼び出し
をすべて削除する必要がある
printf などの呼び出しを1つ追加しただけでも、新しいコードが
追加されたことで、デバッグを行っているコードの動作が変更さ
れることがある
デバッガを使用すると、変数値を出力する追加の呼び出
しを挿入せずに、プログラムの変数値をチェックできる!
コードに
ブレークポイント
を挿入すると、調べたい位置で実行が
中断される
22
http://apple.ee.uec.ac.jp/isyslab
gccの場合
gdbを使う
基本は、ブレークポイントをつけてステップ実行
変数の中身(値)をチェックしていく
#include <stdio.h>
int main(void){
int i, count;
count = 0;
for(i=0; i<3; i++)
{
count++;
}
printf("count = %d¥n", count);
return 0;
}
コンパイル時に –g オプションをつける
>gcc –g ファイル名(xxx.c)
gdb:デバッガの起動
run:プログラムの実行
next:次の1行を実行(関数は1行とみなす)
step:次の1行を実行(関数内も順次実行)
quit:デバッガの終了
list:プログラムの表示
break:ブレイクポイントの設定
delete:ブレイクポイントの削除
print:変数の値を調べる
whatis:変数の型を調べる
gdbの主要なコマンド
このプログラムをトレースしてみる
23
http://apple.ee.uec.ac.jp/isyslab
> 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 #include <stdio.h> 2 3 int main(void){ 4 int i, count; 5 count = 0; 6 for(i=0; i<3; i++) 7 {
8 count++; 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; (gdb) next
6 for(i=0; i<3; i++) (gdb) print count $1 = 0 (gdb) next
8 count++; (gdb) next
6 for(i=0; i<3; i++)
(gdb) print count $2 = 1 (gdb) print i $3 = 0 (gdb) next 8 count++; (gdb) next
6 for(i=0; i<3; i++) (gdb) print count $4 = 2 (gdb) print i $5 = 1 (gdb) next 8 count++; (gdb) next
6 for(i=0; i<3; i++) (gdb) print count $6 = 3 (gdb) print i $7 = 2 (gdb) next 10 printf("count = %d¥n", count); (gdb) continue Continuing. count = 3
Program exited normally. (gdb) quit
25
本日の演習
ー準備ー
まずは、
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
⇒ 諸注意
今日の講義の感想・質問をメール本文に書いてください
よく分かった、ここが分からない、など
26
#include <stdio.h> #include <math.h> #define CHECK_MAX 10000 /*この値までの素数を探す*/ int main(void) { int count; int i, j, k;int prime[CHECK_MAX]; /*CHECK_MAX個の配列を用意しておく*/
printf("2 "); /*最初の素数*/ count = 1; /*1カウント*/ prime[0]=2; /*2を登録*/
/*素数計算メイン*/
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");
return 0;
}
p8-8.c
#define CHECK_MAX 10000
マクロ定義
プログラム中のCHECK_MAX
という文字列を10000で置き換えろ
という(プリプロセッサ)命令
http://apple.ee.uec.ac.jp/isyslab
27
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <math.h> int main(void) {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; /*素数判定*/
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"); /**************************/ /* ここでファイルに書き出す */ /**************************/ /******************/ /* ここでメモリ解放 */ /******************/ return 0; }