• 検索結果がありません。

プログラミング及び演習 第1回 講義概容・実行制御

N/A
N/A
Protected

Academic year: 2021

シェア "プログラミング及び演習 第1回 講義概容・実行制御"

Copied!
45
0
0

読み込み中.... (全文を見る)

全文

(1)

プログラミング及び演習

8回 ポインタ2 / 標準入出力 /

デバッガ

(教科書第10,9章) (2015/06/12)

講義担当

情報連携統轄本部情報戦略室

大学院情報科学研究科メディア科学専攻

教授 森 健策

(2)

 ポインタ  第10章  おさらい  多重ポインタ  標準入出力  デバッガ  講義・演習ホームページ  http://www.newves.org/~mori/15Programming  ところで,  ポインタは理解できましたか?  またVirtual Boxは使ってみましたか?

(3)

出席

NUCTにて出席アンケートに回答

回答

1

今日の合言葉

回答

2

理解度を

5段階で記述

回答

3

前回の演習課題について一言

(4)

演習室と同じ

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/

(5)
(6)

ポインタとは

?

変数や関数などがメモリの中で格納されている

場所

メモリ空間上の「アドレス」を指し示すもの

ポインタ変数

 ポインタを取り扱うことのできる変数 

一般的に

 ポインタを使うプログラムは作成や理解が難しい  ポインタが常にどこを指すかを考える  ポインタ変数を初期化せずにそれ指し示す内容をア クセスするとSegmentation Faultが発生

(7)

変数の内容はメモリ空間のどこかに記録される

コンピュータのメモリ

 番地と値(大抵の場合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 番地 値

(8)

変数はどのように記憶されるか

int i=5;とした場合

 iの中身を記憶する場所が どこかに確保される  そこに0x00000005が 書き込まれる (intなので4byte) 

char c=6;とすれば

 cの中身を記憶する場所が どこかに確保される  そこに0x06が 書き込まれる (charなので1byte) 0x08047abf 0x08047ac0 0x08047ac1 0x08047ac2 0x08047ac3 0x06 0x05 0x00 0x00 0x00

(9)

ポインタとは

メモリ空間での「番地」を表す

それでは:

 ある変数の中身が格納されてい る番地を知るにはどうするの?  番地はわかっているのだけどそ こに格納されている値は何? 

'*' と '&'が鍵となる記号

 *pと書くとpが指し示すアドレス (番地)の内容を表す  &iと書くとiの中身が格納されて いるアドレス(番地)を表す 0x08047abf 0x08047ac0 0x08047ac1 0x08047ac2 0x08047ac3 0x06 0x05 0x00 0x00 0x00

(10)

ポインタ変数の宣言

'*'を変数名の前につける

 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型データがある  型によって何バイト分の値を使うかが異なる

(11)

番地を得るには

?

 & 演算子を使う  &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 実際の番地は計算機毎に異なる ここはあくまでも例!!

(12)

番地の内容を得るには

?

 *演算子を使いその番地に書か れている内容を得る  注意: ポインタ変数の型で値を得 る  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 実際の番地は計算機毎に異なる ここはあくまでも例!!

(13)

ポインタとメモリ空間

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が指す番地 実際の番地は計算機毎に異なる ここはあくまでも例!!

(14)

間接参照

 ポインタを仲介して別の変数の値を読み書きすること  例  int i, j, *p, *q; p=q=&i; *p=1; p=&j *p=*q  ポインタ変数自身に代入を行うこと,それが指し示して いる先に代入を行うことの違いに注意  ポインタ変数が何も指していない状態  NULLポインタを用いる;  int *p; p = (int *) NULL;

(15)

 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

ポインタと配列

(16)

関数ポインタ

関数のエントリポイントのアドレスを保持する変

プログラムをコンパイルすると

 プログラムの各関数についてエントリポイントを作成  関数が呼び出されると実行制御はエントリポイントに 移行  エントリポイント=アドレス 

関数へのポインタが得られればポインタを使っ

て関数を呼び出すことが可能

(17)

関数ポインタの作成

ポインタをその関数の戻り値の型と同じ型を持

つポインタ変数と宣言

仮引数があれば続けて宣言

宣言の例

 int (*p)(int x, int y); /* *pは括弧でくくる */

関数ポインタ取得例

 int sum(int a, int b);という関数がある場合

 p = sum; とすれば関数ポインタ取得可能

 呼び出しは result = (*p)(d,e); のように行う

(18)

関数ポインタと呼び出し例

#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)); }

(19)

多重間接参照

ポインタを使って別のポインタを指すこと

文字列の配列(文字の配列ではない)などを表

すのに用いられる

ポインタ ポインタ 変数 ポインタ 変数 ポインタ ポインタ 変数 ポインタ

(20)

宣言時には

*を2つつける

 char **str;  strは文字ポインタへのポインタを表す 

ポインタのポインタが示す値にアクセスするには

*演算子を2つつける

 char **mp, *p, ch; p = &ch; mp = &p; **mp = 'A';

(21)

ポインタ配列

配列の要素としてポインタが並んだもの

例えば

 文字列の配列 = 各配列の要素は文字列の先頭へ のポインタ  配列の要素の順番を替えるのみで文字列の順番を 変更可能 defghi jklnm fdfsdfs defghi jklnm fdfsdfs

(22)

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つの違いは一体何?

(23)

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

(24)

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個記憶

(25)

アルファベット順に分類 (KR p.131を改変) 

全体の流れ

入力全行を読み込む

それを分類する

順番に印字する

各行への先頭を表すポインタ配列を確

保し,配列内に格納されているポインタ

の順番を変更することで行を並び替え

(26)

#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;

} }

(27)

並べ替えプログラム

(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; }

(28)

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;) }

(29)

並べ替えプログラム

(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); }

(30)

 malloc配列の要素分だけ記憶域を確保すればよい  #include <stdlib.h> int *p, num; scanf("%d",&num); p = (int *)malloc(sizeof(int)*num); p[2] = 1; … … free(p);  任意の大きさの配列をプログラム実行中作成可能

2次元配列以上は?

(31)

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次元配列で

代用可能

(32)

UNIXにおける3つの標準テキストストリーム

 標準入力  通常はキーボード  FILE型ポインタ → stdin  標準出力  通常は画面  FILE型ポインタ → stdout  標準エラー  通常は画面  FILE型ポインタ → stderr  プログラムの正常出力とエラーメッセージ出力を区別する ために設けられている

(33)

標準入出力

fscanf(stdin "%d", &a);

fprintf(stdout, "%d", i);

fprintf(stderr, "Error: Code = %d",

(34)

標準入力・出力・エラーの結合先は

変更可能

通常

入力

:キーボード

出力

:画面、エラー:画面

それぞれの入出力先をファイルに

変更可

「リダイレクト」と呼ばれる

(35)

リダイレクションの例

 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

(36)

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); }

(37)

パイプ

あるプログラムの標準出力を別のプログラムの

標準出力とすることが可能

 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'が含まれる

(38)

GDB をはじめとするデバッガは、プログラムが

実行中もしくはクラッシュした時にそのプログラ

ムの ‘‘内部’’ で何が行なわれているか

/行わ

(39)

gdbとは2 (centos: man gdbより)

GDB は、4 つの機能 (加えてこれらをサポート

する機能

) によって実行中にバグを見つけること

を手助けします。

 プログラムの動作を詳細に指定してプログラムを実 行させる。  指定した条件でプログラムを停止させる。  プログラムが止まった時に、何が起こったか調べる。  バグによる副作用を修正し、別のバグを調べるため プログラムの状態を 変更する。

(40)

発生するプログラム 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ポインタを返している

(41)

デバッガの起動とコマンド

(1/2)

 起動方法  gdb 実行ファイル名 (coreファイル)  コマンド  run  実行ファイルを実行  backtrace  現在止まっている位置にどのように到達したかを示す  list 行番号  行番号のソースファイルを表示  print 変数名  変数名の内容を表示  up (n)  関数フレームを1(n)段up  frame  現在のフレームを表示  frame n  n番目のフレームを選択  quit  gdbを終了

(42)

コマンド

 break 行番号

 breakポイントの設定

 delete (or clear) ブレークポイント番号

 ブレークポイントの解除

 next (or step)

 次の行を実行  continue  次のブレークポイントまで実行  examine 変数  変数のアドレスで示されるメモリ内容を表示  x/12 buf x/12b など

(43)

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) run

Starting 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)

(44)

発生するプログラム 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ポインタを返している

(45)

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行目でエラーが発生

参照

関連したドキュメント

つの表が報告されているが︑その表題を示すと次のとおりである︒ 森秀雄 ︵北海道大学 ・当時︶によって発表されている ︒そこでは ︑五

「系統情報の公開」に関する留意事項

(注)本報告書に掲載している数値は端数を四捨五入しているため、表中の数値の合計が表に示されている合計

Google マップ上で誰もがその情報を閲覧することが可能となる。Google マイマップは、Google マップの情報を基に作成されるため、Google

排出量取引セミナー に出展したことのある クレジットの販売・仲介を 行っている事業者の情報

排出量取引セミナー に出展したことのある クレジットの販売・仲介を 行っている事業者の情報

LUNA 上に図、表、数式などを含んだ問題と回答を LUNA の画面上に同一で表示する機能の必要性 などについての意見があった。そのため、 LUNA

・グリーンシールマークとそれに表示する環境負荷が少ないことを示す内容のコメントを含め