プログラミング及び演習
第
8回 ポインタ2 / 標準入出力 /
デバッガ
(教科書第10,9章) (2015/06/12)
講義担当
情報連携統轄本部情報戦略室
大学院情報科学研究科メディア科学専攻
教授 森 健策
ポインタ 第10章 おさらい 多重ポインタ 標準入出力 デバッガ 講義・演習ホームページ http://www.newves.org/~mori/15Programming ところで, ポインタは理解できましたか? またVirtual Boxは使ってみましたか?
出席
NUCTにて出席アンケートに回答
回答
1
今日の合言葉
回答
2
理解度を
5段階で記述
回答
3
前回の演習課題について一言
演習室と同じ
OSを自宅でも再現できるように仮想マ
シンのイメージを用意しました。
詳しくは、
NUCTにあるアナウンス“Virtual Boxの利
用について
”を参照してください。
なお、
Virtual Box上で作成したファイルは、Virtual
Box上のファイルですので、課題提出時には必ず
ICEのコンピュータ上にコピーしてください
scp filename.c 名大ID@ssh.ice.nuie.nagoya-u.ac.jp:/pub1/ensyu/programming/students-2015/名 大ID/ポインタとは
?
変数や関数などがメモリの中で格納されている
場所
メモリ空間上の「アドレス」を指し示すもの
ポインタ変数
ポインタを取り扱うことのできる変数 一般的に
ポインタを使うプログラムは作成や理解が難しい ポインタが常にどこを指すかを考える ポインタ変数を初期化せずにそれ指し示す内容をア クセスするとSegmentation Faultが発生
変数の内容はメモリ空間のどこかに記録される
コンピュータのメモリ
番地と値(大抵の場合1つの番地に1バイト) 書き込み動作 "0x00001234番地に値0x26を書け" 読み込み動作 "0x00001234番地の値を読め" 複数バイトからなる変数は複数の番地を使って記録ポインタを理解するうえで
0 5 00 00 00メモリ空間
0x 00 00 00 00 0x 00 00 00 01 0x 00 00 00 02 0x0 00 00 00 3 0x 00 00 00 04 0x 00 00 00 05 0x 00 00 00 06 0x 00 00 00 07 0x 00 00 00 08 0x0 00 00 00 9 0x 00 00 00 0A 0x 00 00 00 0B 0x 00 00 00 0C 0x0 00 00 00 D 0x 00 00 00 0E 0x0 00 00 00 F 0x 00 00 00 10 0x 00 00 00 11 0x 00 00 00 12 0x 00 00 00 13 0x 00 00 00 14 0x 00 00 00 15 0x 00 00 00 16 0x 00 00 00 17 0x 00 00 00 18 0x 00 00 00 19 番地 値変数はどのように記憶されるか
int i=5;とした場合
iの中身を記憶する場所が どこかに確保される そこに0x00000005が 書き込まれる (intなので4byte) char c=6;とすれば
cの中身を記憶する場所が どこかに確保される そこに0x06が 書き込まれる (charなので1byte) 0x08047abf 0x08047ac0 0x08047ac1 0x08047ac2 0x08047ac3 0x06 0x05 0x00 0x00 0x00ポインタとは
メモリ空間での「番地」を表す
それでは:
ある変数の中身が格納されてい る番地を知るにはどうするの? 番地はわかっているのだけどそ こに格納されている値は何? '*' と '&'が鍵となる記号
*pと書くとpが指し示すアドレス (番地)の内容を表す &iと書くとiの中身が格納されて いるアドレス(番地)を表す 0x08047abf 0x08047ac0 0x08047ac1 0x08047ac2 0x08047ac3 0x06 0x05 0x00 0x00 0x00ポインタ変数の宣言
'*'を変数名の前につける
int i, *p; pがint型データへのポインタ変数 int *p, q; pはポインタ変数 qは単なるint変数 int *p, *q; pもqもポインタ変数 型はその番地にある値が表しているデータの型
を示す
int *p; pが指し示す番地にはint型データがある float *q; qが指し示す番地にはfloat型データがある char *r; rが指し示す番地にはchar型データがある 型によって何バイト分の値を使うかが異なる番地を得るには
?
& 演算子を使う &iと記述すればiが格納される番 地を表す 得られた番地はポインタ型変数 に代入 例 int i=5; char c=6;
int *p1; char *p2; p1 = &i; p2 = &c; p1には0x08047ac0 p2には0x08047abf が代入 0x06 0x05 0x00 0x00 0x00 0x08047abf 0x08047ac0 0x08047ac1 0x08047ac2 0x08047ac3 実際の番地は計算機毎に異なる ここはあくまでも例!!
番地の内容を得るには
?
*演算子を使いその番地に書か れている内容を得る 注意: ポインタ変数の型で値を得 る charなら1byte分をアクセス int なら4byte分をアクセス int i=5; char c=6;int *p1; char *p2; p1 = &i; p2 = &c; printf("%d¥n",(*p1)+(*p2)); *p1は5 *p2は6 を表す 0x08047abf 0x08047ac0 0x08047ac1 0x08047ac2 0x08047ac3 0x06 0x05 0x00 0x00 0x00 実際の番地は計算機毎に異なる ここはあくまでも例!!
ポインタとメモリ空間
0x08047abf 0x08047ac0 0x08047ac1 0x08047ac2 0x08047ac3 0x06 0x05 0x00 0x00 0x00 … … bf 7a 04 08 c0 7a 04 08 int i=5;
char c=6;
int *p1, *p2;
p1=&i; p2=&c;
iの中身が格納 されている場所p2 の中身が格納 されている場所 どこかの番地 cの中身 p1 の中身 p2が指す番地 p1が指す番地 実際の番地は計算機毎に異なる ここはあくまでも例!!
間接参照
ポインタを仲介して別の変数の値を読み書きすること 例 int i, j, *p, *q; p=q=&i; *p=1; p=&j *p=*q ポインタ変数自身に代入を行うこと,それが指し示して いる先に代入を行うことの違いに注意 ポインタ変数が何も指していない状態 NULLポインタを用いる; int *p; p = (int *) NULL; int a[10];と宣言 aは配列先頭へのポインタ *(a+2) で a[2]にアクセス可能 *(a+2) と a[2]は同じ (a+2)と記述したとき単に番地に 2を足すのではなく1要素の(バイ ト数*2)を足した番地となる 配列の2番目を正しくアクセス可能 右の例だと a → 0x08047ac0 a+1 → 0x08047ac4 (int型=4byteのため) a, &(a[0]) a+1, &(a[1]) a+2, &(a[1]) a[0] a[1] a[2] a[3] a[4] a+3, &(a[1]) a+4, &(a[1]) 0x08047ac0 0x08047ac1 0x08047ac2 0x08047ac3 0x08047ac4
ポインタと配列
関数ポインタ
関数のエントリポイントのアドレスを保持する変
数
プログラムをコンパイルすると
プログラムの各関数についてエントリポイントを作成 関数が呼び出されると実行制御はエントリポイントに 移行 エントリポイント=アドレス 関数へのポインタが得られればポインタを使っ
て関数を呼び出すことが可能
関数ポインタの作成
ポインタをその関数の戻り値の型と同じ型を持
つポインタ変数と宣言
仮引数があれば続けて宣言
宣言の例
int (*p)(int x, int y); /* *pは括弧でくくる */
関数ポインタ取得例
int sum(int a, int b);という関数がある場合
p = sum; とすれば関数ポインタ取得可能
呼び出しは result = (*p)(d,e); のように行う
関数ポインタと呼び出し例
#include <stdio.h> int add(int x, int y) {
return(x+y); }
int mult(int x, int y) { return(x*y); } int main() { int(*fp)(int,int); fp=add; printf("add %d¥n",(*fp)(2,3)); fp=mult; printf("mul %d¥n",(*fp)(2,3)); }
多重間接参照
ポインタを使って別のポインタを指すこと
文字列の配列(文字の配列ではない)などを表
すのに用いられる
ポインタ ポインタ 変数 ポインタ 変数 ポインタ ポインタ 変数 ポインタ
宣言時には
*を2つつける
char **str; strは文字ポインタへのポインタを表す ポインタのポインタが示す値にアクセスするには
*演算子を2つつける
char **mp, *p, ch; p = &ch; mp = &p; **mp = 'A';ポインタ配列
配列の要素としてポインタが並んだもの
例えば
文字列の配列 = 各配列の要素は文字列の先頭へ のポインタ 配列の要素の順番を替えるのみで文字列の順番を 変更可能 defghi jklnm fdfsdfs defghi jklnm fdfsdfs
char day1[][10]={"Sunday", "Monday",
"Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday"};
char *day2[]={"Sunday", "Monday",
"Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday"};
printf( "%s %s¥n", day[1], day2[1]);
で月曜日が表示される
2つの違いは一体何?
char day1[][10]タイプ
S u n d a y ¥0 M o n d a y ¥0 T u e s d a y ¥0
W e d n e s d a y ¥0 T h u r s d a y ¥0 F r i d a y ¥0
S a t u r d a y ¥0 day1
day2 S u n d a y ¥0 M o n d a y ¥0 T u e s d a y ¥0 W e d n e s d a y ¥0 T h u r s d a y ¥0 F r i d a y ¥0 S a t u r d a y ¥0 4byte 4byte 4byte 4byte 4byte 4byte 4byte 7byte 7byte 8byte 10byte 9byte 7byte 9byte 7つの曜日名文字列はメモリ空間では連続していない 曜日名文字列の 先頭位置を7個記憶
アルファベット順に分類 (KR p.131を改変)
全体の流れ
入力全行を読み込む
それを分類する
順番に印字する
各行への先頭を表すポインタ配列を確
保し,配列内に格納されているポインタ
の順番を変更することで行を並び替え
#include <stdio.h> #include <string.h>
#define MAXLINES 5000 char *lineptr[MAXLINES];
int readlines(char **lineptr, int nlines); int writelines(char **lineptr, int nlines); int getline(char *line,int maxlen);
void bubble(char *data[], int num); int main() { int nlines; if((nlines=readlines(lineptr,MAXLINES))>=0){ bubble(lineptr,nlines); writelines(lineptr,nlines); return 0; }else{
printf("error: input too big to start!¥n"); return 1;
} }
並べ替えプログラム
(2)
#define MAXLEN 1000
int readlines(char **lineptr, int maxlines) {
int len, nlines;
char *p, line[MAXLEN]; nlines = 0; while((len=getline(line,MAXLEN))>0){ if(nlines>=maxlines||(p=(char *)malloc(len*sizeof(char)))==NULL){ return -1; }else{ line[len-1] = '¥0'; strcpy(p,line); lineptr[nlines++]=p; } } return nlines; }
int writelines(char **lineptr, int nlines){ int i; for(i=0;i<nlines;i++){ printf("%s¥n", lineptr[i]); } }
int getline(char *line,int maxlen) { if(fgets(line,maxlen,stdin)!=NULL) return(strlen(line)); else return -1;) }
並べ替えプログラム
(4)
void bubble(char *data[], int num) { int flag,i; char *tmp; do{ flag=0; for(i=0;i<num-1;i++){ if(strcmp(data[i],data[i+1])>0){
tmp=data[i]; data[i]=data[i+1]; data[i+1]=tmp; flag=1;
} }
}while(flag!=0); }
malloc配列の要素分だけ記憶域を確保すればよい #include <stdlib.h> int *p, num; scanf("%d",&num); p = (int *)malloc(sizeof(int)*num); p[2] = 1; … … free(p); 任意の大きさの配列をプログラム実行中作成可能
2次元配列以上は?
1次元配列で多次元配列を実現
p x q x r の配列を確保 (3次元)
int *data;
data = (int *)malloc(p*q*r*sizeof(int));
アクセス方法
(i,j,k)における値を取り出す (data[k][j][i]) a=data[i+j*p+k*p*q]; (もしくはa=data[i+(j+k*q)*p];) 同様のことを行えば
n次元配列を1次元配列で
代用可能
UNIXにおける3つの標準テキストストリーム
標準入力 通常はキーボード FILE型ポインタ → stdin 標準出力 通常は画面 FILE型ポインタ → stdout 標準エラー 通常は画面 FILE型ポインタ → stderr プログラムの正常出力とエラーメッセージ出力を区別する ために設けられている標準入出力
例
fscanf(stdin "%d", &a);
fprintf(stdout, "%d", i);
fprintf(stderr, "Error: Code = %d",
標準入力・出力・エラーの結合先は
変更可能
通常
入力
:キーボード
出力
:画面、エラー:画面
それぞれの入出力先をファイルに
変更可
「リダイレクト」と呼ばれる
リダイレクションの例
lec8-out > abc.txt
progの標準出力をすべてファイルabc.txtに出力 標準エラーは画面に出力される
多量のデータを出力するプログラムのデバッグに便利
lec8-out >& abc.txt
progの標準出力・エラー共にファイルabc.txtに出力 多量のデータを出力するプログラムのデバッグに便利
lec8-in < def.txt
progの入力をファイルから入力
lec8-in <abc.def >def.txt
taka{mori}20: cat lec7-in.c #include <stdio.h> main() { int i; char c; while((c=fgetc(stdin))!=EOF){ fprintf(stdout,"%c",c); } }
taka{mori}21: cat test.txt fsdf
fsdfdsfsdf fsd
taka{mori}24: ./a.out < test.txt fsdf fsdfdsfsdf fsd taka{mori}25: #include <stdio.h> main() { int i; for(i=0;i<100;i++){
fprintf( stdout, "STDOUT %d¥n", i); fprintf( stderr, "STDERR %d¥n", i); }
パイプ
あるプログラムの標準出力を別のプログラムの
標準出力とすることが可能
例
lec8-pipe1 | lec8-pipe2 lec8-pipe1の標準出力をlec8-pipe2の標準入力として用 いる cat myprog.c | more
myprog.cの内容を画面に書き出し、1ページ毎表示
tail -1000 abc.txt | grep dictionary | more
abc.txtの終わり1000行分の中で'dictionary'が含まれる
GDB をはじめとするデバッガは、プログラムが
実行中もしくはクラッシュした時にそのプログラ
ムの ‘‘内部’’ で何が行なわれているか
/行わ
gdbとは2 (centos: man gdbより)
GDB は、4 つの機能 (加えてこれらをサポート
する機能
) によって実行中にバグを見つけること
を手助けします。
プログラムの動作を詳細に指定してプログラムを実 行させる。 指定した条件でプログラムを停止させる。 プログラムが止まった時に、何が起こったか調べる。 バグによる副作用を修正し、別のバグを調べるため プログラムの状態を 変更する。発生するプログラム lec8-seg1.c
#include <stdio.h> static int func1(); static int *myAlloc(); main() { int val; val = func1(); printf("val = %d¥n",val); }
static int func1() { int retVal; int *retPtr; retPtr = myAlloc(); *retPtr = 10; retVal = *retPtr; return retVal; }
static int *myAlloc() { return 0; } myAllocでint型変数を格納するメ モリ領域を確保するはずなのに, 確保せずに0ポインタを返している
デバッガの起動とコマンド
(1/2)
起動方法 gdb 実行ファイル名 (coreファイル) コマンド run 実行ファイルを実行 backtrace 現在止まっている位置にどのように到達したかを示す list 行番号 行番号のソースファイルを表示 print 変数名 変数名の内容を表示 up (n) 関数フレームを1(n)段up frame 現在のフレームを表示 frame n n番目のフレームを選択 quit gdbを終了
コマンド
break 行番号
breakポイントの設定
delete (or clear) ブレークポイント番号
ブレークポイントの解除
next (or step)
次の行を実行 continue 次のブレークポイントまで実行 examine 変数 変数のアドレスで示されるメモリ内容を表示 x/12 buf x/12b など
list表示と変数表示
デバッグ情報を含め てコンパイル gcc -g lec8-seg1.c -gオプションをつける ことで実行ファイル にデバッグ情報が含 まれる 異常終了した部分 のリストを表示可能 任意の変数の値を 調査可能 ssh.ice.nuie.nagoya-u.ac.jp{mori}50: ./lec8-seg1 Segmentation fault ssh.ice.nuie.nagoya-u.ac.jp{mori}51: gdb lec8-seg1 途中略 (gdb) runStarting program: /home0/mori/lec8-seg1
Program received signal SIGSEGV, Segmentation fault. 0x080483ea in func1 () at lec8-seg1.c:17
17 *retPtr = 10; (gdb) list 17
12 static int func1() 13 { 14 int retVal; 15 int *retPtr; 16 retPtr = myAlloc(); 17 *retPtr = 10; 18 retVal = *retPtr; 19 return retVal; 20 } 21 (gdb) print retPtr $1 = (int(gdb)
発生するプログラム lec8-seg2.c
#include <stdio.h> static char * func1(); static char *myAlloc(); main() { char *retPtr; retPtr = func1(); printf("buffer %s¥n",retPtr); } static char *func1() { char *retPtr; retPtr = myAlloc(); sprintf(retPtr,"%s","ABC"); return retPtr; } static char *myAlloc() { char *retPtr; retPtr = 0; return retPtr; } myAllocで文字列を格納するメモ リ領域を確保するはずなのに,確 保せずに0ポインタを返している
gdbによるbacktrace
ssh.ice.nuie.nagoya-u.ac.jp{mori}54: gcc -g lec8-seg2.c -o lec8-seg2 ssh.ice.nuie.nagoya-u.ac.jp{mori}55: gdb ./lec8-seg2 .. .. 途中略 .. (gdb) run
Starting program: /home0/mori/lec8-seg2
Program received signal SIGSEGV, Segmentation fault. 0x080483ea in func1 () at lec8-seg2.c:17
17 sprintf(retPtr,"%s","ABC"); (gdb) bt
#0 0x080483ea in func1 () at lec8-seg2.c:17 #1 0x080483ba in main () at lec8-seg2.c:8 (gdb)
backtraceで関数の呼び 出し状況をチェック
17行目でエラーが発生