第2週 配列とポインタ
プログラミング演習2
2
ポインタ(pointer)とは
変数,配列,関数などの(メモリ上の)アドレスを
指し示す
• ポインタ変数の中身はアドレス
• 「~へのポインタ」
C言語を代表する非常に便利な機能
メモリ空間をイメージすることが理解への早道
• アドレスの理解
今日のキーワード
・メモリ空間
・アドレス
・ポインタ変数
・ポインタの演算
3
メモリとアドレス
CPU
Core(TM) 2 Quad プロセッサ 2.50GHz
メモリ
4GB デュアルチャネル DDR2-SDRAM メモ
4
メモリ空間とアドレス
4Gバイト
=0x0 ~ 0xffffffff
0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xffffffff
1バイト
アドレスの先頭 アドレス
16進数で表すことが多い
5
復習:変数
変数
• 数値や文字を格納する箱のようなもの
• 型によってサイズが違う
– char 型:1 バイト
– short 型:2バイト
– int 型:4バイト(環境によって異なる)
1バイト
2バイト
4バイト int main(void){
char c; short s; int num; ...
return 0; }
型名 変数名
6
変数とアドレスの関係
int main(void){ int a;
a=100; ...
return 0; }
7
変数とアドレスの関係
int main(void){ int a;
a = 100; ...
return 0; }
4バイト
8
変数とアドレスの関係
int main(void){ int a;
a = 100; ...
return 0;
}
今日のポイント1
変数aのアドレスとは、
変数aの置かれた先頭の
アドレスのことである
この場合は0x1004
9
変数のアドレスの取得方法
int main(void){ int a;
a = 100;
printf(“変数aのアドレスは%p”,&a); ...
return 0; }
今日のポイント2
変数の前に&をつけると
変数のアドレスを取得できる
変数aのアドレスは0x1004 実行結果
10
復習:ポインタ (pointer) とは
変数,配列,関数などの(メモリ上の)アドレスを
指し示す(参照する)
• ポインタの中身はアドレス
• 「~へのポインタ」
4バイト ポインタ変数
11
ポインタの使い方 (ポインタ変数の宣言)
int main(void){
int *pa;
int a;
pa = &a;
a = 100;
printf(“ 変数aのアドレスは%p”,pa);
printf(“ 変数aの値は%d”,*pa);
...
return 0;
}
今日のポイント3
ポインタ変数の宣言は
型名 * ポインタ変数名;
12
ポインタの使い方(変数の宣言)
int main(void){
int *pa;
int a;
pa = &a;
a = 100;
printf(“ 変数aのアドレスは%p”,pa);
printf(“ 変数aの値は%d”,*pa);
...
return 0;
}
13
ポインタの使い方 (アドレスの取得)
int main(void){
int *pa;
int a;
pa = &a;
a = 100;
printf(“ 変数aのアドレスは%p”,pa);
printf(“ 変数aの値は%d”,*pa);
...
return 0;
}
今日のポイント2
変数の前に&をつけると
変数のアドレスを取得できる
14
ポインタの使い方 (値の代入)
int main(void){
int *pa;
int a;
pa = &a;
a = 100;
printf(“ 変数aのアドレスは%p”,pa);
printf(“ 変数aの値は%d”,*pa);
...
return 0;
}
15
ポインタの使い方(ポインタ変数の出力)
int main(void){
int *pa;
int a;
pa = &a;
a = 100;
printf(“ 変数aのアドレスは%p”,pa);
printf(“ 変数aの値は%d”,*pa);
...
return 0;
}
変数aのアドレスは0x1008 実行結果
今日のポイント4
ポインタ変数の中身は
アドレスである
16
ポインタの使い方(ポインタの指す先変数の値)
変数aのアドレスは0x1008 変数の値は100
実行結果
int main(void){
int *pa;
int a;
pa = &a;
a = 100;
printf(“ 変数aのアドレスは%p”,pa);
printf(“ 変数aの値は%d”, *pa);
...
return 0;
}
今日のポイント5
ポインタ変数に*をつけると ポインタ変数の指している先 の変数の値を参照できる
*
17
ポインタを利用して何がうれしい/できる?
アルゴリズムとデータ構造
• リスト(7,8週)
• 木構造(12週)
関数にデータを渡す(4週)
• 2つ以上の結果を受け取りたい場合
• 配列を渡したい場合
• 関数内で渡したデータを編集したい場合
動的メモリ確保(3週)
関数ポインタ(上級)
• コールバック
18
必須課題2-1
1. p4 のプログラムを入力
2. 実行
3. 次ページの理解補助シートと出力結果を照らし合
わせて考えてみよう
4. コメントを入力
5. TA/ES に確認してもらう
(良くわからない人は1ステップ毎に、
TA/ES に確認してもらいましょう)
19
必須課題2-1:理解補助シート(1/2)
a = 10;
printf("a: %d¥n", a);
printf("&a: %p¥n¥n", &a);
//上の続き p = &a;
printf("p: %p¥n", p); printf("*p: %d¥n", *p); printf("&p: %p¥n¥n", &p); ステップ1
ステップ2
20
必須課題2-1:理解補助シート(2/2)
//前ページの続き a = 20;
printf("a: %d¥n", a);
printf("&a: %p¥n¥n", &a); printf("p: %p¥n", p);
printf("*p: %d¥n", *p); printf("&p: %p¥n", &p); ステップ3
21
復習:配列
配列
• 複数の同じ型の変数をまとめたもの
int a[ 4 ];
型 名前 要素数
22
文字列(char型の配列)
char s[9] =“Ritsumei”;
文字列の決まり事
文字列の最後は
‘¥0’
“ (ダブルクォーテーション):文字列を”で囲む
‘(シングルクォーテーション):文字1文字を’で囲む
23
文字列とポインタ
char s[9] =“Ritsumei”;
s
s はs[0]のアドレス(&s[0])
を示す
(s+1)
=&s[1]
(s+2) =&s[2]
*(s+1)
=s[1]
*(s+2)
=s[2]
24
必須課題2-2:理解補助シート(1/4)
c = 'A'; p = &c;
printf("c: %c¥n", c);
printf("&c: %p¥n¥n", &c); printf("p: %p¥n", p);
printf("*p: %c¥n¥n", *p);
ここの代入が終了した時点の状態を考えてみましょう ステップ1
25
必須課題2-2:理解補助シート(2/4)
//前ページの続き
*p = 'B';
printf("c: %c¥n", c);
printf("&c: %p¥n¥n", &c); printf("p: %p¥n", p);
printf("*p: %c¥n¥n", *p);
ここの代入が終了した時点の値とアドレスを考えてみよう ステップ2
26
必須課題2-2:理解補助シート(3/4)
//前ページの続き
printf("s: %s¥n", s);
printf("s[0]: %c¥n", s[0]); printf("s[1]: %c¥n", s[1]); printf("s: %p¥n", s);
printf("&s[0]: %p¥n", &s[0]); printf("*s: %c¥n", *s);
printf("*(s+1): %c¥n¥n", *(s+1));
出力結果と照らし合わせて、何が出力されているか考えてみよう ステップ3
char s[12] = "Ritsumeikan";
27
必須課題2-2:理解補助シート(4/4)
//前ページの続き
*(s+2) = 'T';
printf("s: %s¥n", s);
Tはどこに代入されるか考えてみよう ステップ4
28
ポインタの加算、減算
char s[9] =“Ritsumei”;
char *p = s;
p++;
ここの時点&s[0]
29
ポインタの加算、減算
char s[9] =“Ritsumei”;
char *p = s;
p++;
ここの時点&s[1]
ポインタを加算/減算すると、ポイン
タ型のサイズ分(一箱分)アドレスが
ずれる
例:ポインタ変数名がpの場合
加算:p++ または p = p+1
減算:p-- または p = p-1
30
必須課題2-3:理解補助シート(1/3)
char *p1, *p2;
p2 = “ Winter ”; p1 = p2;
A1 A2 A3 A4 A5 A6 A7
p1とp2の値はA1~A7のどのアドレスどれでしょう?
31
必須課題2-3:理解補助シート(2/3)
//前ページの続き
while ( *p1 != '¥0' ) {
__________________________ ; // 文字列の最後を検索 }
A1 A2 A3 A4 A5 A6 A7
文字列の最後を検索するためのp1の動作を考えてみよう
32
必須課題2-3:チェックシート(3/3)
//前ページの続き
while ( _________________ ) { // アドレスを比較 __________________________ ;
putchar( *p1 ); //アドレスを制御して文字を1つずつ出力 }
A1 A2 A3 A4 A5 A6 A7
p1の動作をA1~A7を使って考えよう
p1とp2の関係も考えてみよう
33
まとめ(通常の変数とポインタ変数)
通常変数 ポインタ変数
宣言方法 int a; int *pa;
変数名: a
型: int 型 変数名: pa 型: int 型へのポインタ型
値の代入 a = 16; pa = &a;
変数aの中身は(int型の)数値 変数paの中身は(int型変数であ る)aのアドレス
値の表示 printf("%d", a); printf("%p", pa);
整数値を表示するには%dを使用 アドレスを表示するには%pを使用
演算子 読み方 意味 使用法
* (変数宣言の時) アスタリスク
(asterisk)
ポインタ変数 int *pa;
* (変数宣言以外) アスタリスク ポインタが指す
先の内容 *pa = 100;
printf("%d", *pa);
& アンパサンド
(ampersand)
変数のアドレス int a; pa = &a;
標準入出力 (stdin, stdout)
プログラム
入力 出力
file file
stdin stdout
fp
(自分で決めたファイルポインタ)
fprintf(stdout, "Hello");
printf("Hello");
同じ意味
34
リダイレクション
35
プログラム
input.txt outp.txt
stdin stdout
stdout
stdinの入力元, stdoutの出力先を変更
• プログラムを実行するときに次のように入力
% ./a.out <input.txt >outp.txt
stdin
× ×
入力元変更 出力先変更
35
不正な参照(1)
segmentation fault (セグメント例外)
• プログラム中で使えるアドレスの範囲の外を
参照すること
• 例: int *p=NULL;
(*p)++;
• 例: int *p;
int x=0;
p=x;
NULLはアドレス0 (無効なアドレス)を示す
無効なアドレスの値に対して 演算しようとした
構文エラーではないが、 不正な参照の原因
36
不正な参照(2)
互換性のないポインタ型の代入
• char ch=‘A’;
char *q=&ch;
int *p=&ch;
構文エラーではない が、不正な参照の原
因
0x0a01
… …
‘A’ ch
p
…
0x0a02 ?????
q
0x0a01
0x0a01
q の参照先
p の参照先
(int 型は 4 バイト )
37
補足: コンパイルエラーの読み方
gccが出力するメッセージを読むには
• 英語に慣れる
• 専門用語を知る
• エラーや警告の例:
– test.c:10: error: 'y' undeclared (first use in this function) (10行目で宣言されていない変数yを使っている)
→対処例: yを宣言する、変数名の間違いを直す – test.c:5: error: redeclaration of 'x'
(5行目ですでに宣言されている変数xを重複して宣言した)
→対処例: 重複している宣言の削除
– test.c:10: 警告: 互換性のないポインタ型からの代入です
(違う型のポインタを代入した ex. int型ポインタにchar型ポインタを代入)
→対処例: ポインタの型を修正
38
発展:セグメント例外の箇所の確認方法
39
gcc –g –o prog prog.c
gdb prog (gdb) run ...
Program received signal SIGSEGV, Segmentation fault. 0x0804837e in test ()
セグメント例外が起きた箇所の 関数名がわかる
著者リスト
40