Más contenido relacionado La actualidad más candente (20) Similar a C言語ポインタ講座 (Lecture of Pointer in C) (20) C言語ポインタ講座 (Lecture of Pointer in C)5. お約束
この資料では、以下の環境を仮定します
– sizeof(int) = 4
– sizeof(short int) = 2
– sizeof(float) = 4
– sizeof(double) = 8
– sizeof(ポインタ型) = 4
– 1byte = 8bit
– C言語の規約はANSI C
– float型・double型の表現方法は IEEE754
– 文字コードはASCII
– とくに指示がない限りバイトオーダーは
リトルエンディアン
5
17. 重要な考察
int *p = &intValue; では、pがintValueの
先頭アドレスで初期化される。
*pで、pのアドレスの内容を取り出す
値を取り出すとき、
どこから取り出せば良いのかは分かる。
どこまで取り出せばよいのかは??
17
18. 図解
int *p = &value;
*p
18
valuep
p
どこまで読めば良いの??= 1byte
p
変数pに のアドレスが入っていることを意味注意: =
20. 重要な考察
int *p; としたとき、
*pとすると、pに記憶されているアドレス
から、int型分だけ読み込む
20
pは int型への ポインタ変数
21. 図解
int *p = &value;
*p
21
valuep
p int型分
「int型分」はpの宣言「int *p」から。
= 1byte
22. ポインタ演算
p++; や、p--; とするとポインタに保存さ
れているアドレスが1単位動く
アドレスがいつも1byte動くわけではない
ポインタ変数の宣言から、参照元の型を
見て、その型の大きさを1単位とする。
22
33. float (double)型の格納方法
33
0 x C 0 F 8 0 0 0 0
=1bit
0xC 0x0 0xF 0x8 0x0
1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0
※簡単化のため、バイトオーダーは
ビックエンディアンとする
続きは全て0→
35. float (double)型の格納方法
35
0 x C 0 F 8 0 0 0 0
=1bit
0xC 0x0 0xF 0x8 0x0
1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0
符
号
指数
(129)
基数
符号が1なので「-1」
指数は129 – 127 = 2
基数は1.11110000000000000…
⇒-1 * 2 * 1.1111 = -111.11 = -7.752
36. 型と数値
36
0 x C 0 F 8 0 0 0 0
0xC 0x0 0xF 0x8 0x0
1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0
このビット列をunsigned int型だと思うと
– ビックエンディアンなら
• 0xC0F80000 = 3237478400という整数
– リトルエンディアンなら
• 0x0000F8C0 = 63680という整数
=1bit
37. 型と数値
このビット列をint型だと思うと
– ビックエンディアンなら
• 0xC0F80000 = -1057488896 という整数
– リトルエンディアンなら
• 0x0000F8C0 = 63680という整数
37
0 x C 0 F 8 0 0 0 0
0xC 0x0 0xF 0x8 0x0
1 1 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0
=1bit
43. 図解
43
int a, b, c;
a c b
int a[3];
a[0] a[1] a[2]
必ず連続
連続とは限らない
= 1byte
54. 配列アクセスを理解
*(a + 2)
54
a[0] a[1]
1 2
int *
幅はint
a + 2
a[2]
3
int
+2すると、
sizeof(int) * 2 = 8 byte分進む
8byte
= 1byte
55. 配列アクセスを理解
*(a + 2)
55
a[0] a[1]
1 2
a + 2
a[2]
3
int
この領域をint型で
読み取り
⇒3が読みだされる
= 1byte
56. ポインタを使っても同じ!
int a[3] = {1, 2, 3};
int *p = a; //ポインタ変数にアドレス代入
printf(“%dn”, p[2]);
56
a[0] a[1]
1 2
p + 2
a[2]
3
int*(p + 2)=
= 1byte
63. 配列の配列(図解)
char a[2][3] = {{1,2,3}, {4,5,6}};
63
a
1 2 3 4 5 6
a[1]
a+1
1 2 3 4 5 6
=*(a + 1)
*(a + 1)…char [3](配列)
= 1byte
68. 理解度チェック
バイトオーダーはリトルエンディアンとします。
次のア~カの行で表示される数値は?(次ページに補足有)
68
int arr[] = {0x01234567, 0x89abcdef, 0xc0f80000};
int *p1 = arr;
char *p2 = (char *)arr;
float *p3 = (float *)arr;
printf(“%un”, sizeof(arr));
printf(“%un”, sizeof(p2));
printf(“%xn”, p1[1]);
printf(“%xn”, p2[1]);
printf(“%3.2fn”, p3[2]);
printf(“%xn”, *(int *)(p2 + 2));
/* ア */
/* イ */
/* ウ */
/* エ */
/* オ */
/* カ */
69. 理解度チェック(補足)
バイトオーダーはリトルエンディアンとします。
次のア~カの行で表示される数値は?
69
int arr[] = {0x01234567, 0x89abcdef, 0xc0f80000};
int *p1 = arr;
char *p2 = (char *)arr;
float *p3 = (float *)arr;
printf(“%un”, sizeof(arr));
printf(“%un”, sizeof(p2));
printf(“%xn”, p1[1]);
printf(“%xn”, p2[1]);
printf(“%3.2fn”, p3[2]);
printf(“%xn”, *(int *)(p2 + 2));
/* ア */
/* イ */
/* ウ */
/* エ */
/* オ */
/* カ */
0xで始まる値は16進数を表します
sizeof演算子…演算対象のメモリ上での大きさをバイト単位で計算します
%u…符号なし整数10進数として出力します
%x…16進数として出力します
%3.2f…全体で3桁、小数部分は2桁になるように小数点表示します
70. お約束(再掲)
この資料では、以下の環境を仮定します
– sizeof(int) = 4
– sizeof(short int) = 2
– sizeof(float) = 4
– sizeof(double) = 8
– sizeof(ポインタ型) = 4
– 1byte = 8bit
– C言語の規約はANSI C
– float型・double型の表現方法は IEEE754
– 文字コードはASCII
– とくに指示がない限りバイトオーダーは
リトルエンディアン
70
73. 理解度チェック(解説:前提)
int arr[] = {0x01234567, 0x89abcdef,
0xc0f80000};
のメモリ上の姿を書くと……
73
0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0
arr[0] arr[1] arr[2]
要素はメモリ上で必ず連続して配置
各要素ごとにリトルエンディアンで格納
= 1byte
75. 理解度チェック(解説:イ①)
char *p2 = (char *)arr;
75
0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0
arr[0] arr[1] arr[2] = 1byte
p2
※このarrは先頭要素arr[0]のアドレスとなる。型はint *
arrはint *となるが、(char *)とキャストしているので、
問題なくポインタ変数p2にarr[0]のアドレスが入る
77. 理解度チェック(解説:ウ①)
int *p1 = arr;
77
0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0
arr[0] arr[1] arr[2] = 1byte
p1
78. 理解度チェック(解説:ウ②)
p1[1] は *(p1 + 1)
78
0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0
arr[0] arr[1] arr[2] = 1byte
p1+1
この領域をintで読み取る
読み出すときもバイトオーダを意識する
– 読み出すと 0x89abcdef となる
79. 理解度チェック(解説:エ)
p2[1]は *(p2 + 1)
79
0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0
arr[0] arr[1] arr[2] = 1byte
p2+1
読み出すと、0x45。
バイトオーダーによって結果が変わる!
– (ビックエンディアンの場合……0x23)
この領域をcharで読み取る
80. 理解度チェック(解説:オ①)
float *p3 = (float *)arr;
80
0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0
arr[0] arr[1] arr[2] = 1byte
p3
81. 理解度チェック(解説:オ②)
p3[2] は *(p3 + 2)
81
0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0
arr[0] arr[1] arr[2] = 1byte
p3+2
この領域をfloatで読み取る
バイトオーダーを考えて読むと0xc0f80000
82. 理解度チェック(解説:オ③)
82
0xc0f80000は2進数で……
1100 0000 1111 1000 0000 0000 0000 0000
0xc 0x0 0xf 0x8 0x0 0x0 0x0 0x0
IEEE754で読むと
1 10000001 11110000000000000000000
上位1ビット目…符号はマイナス
上位2-9ビット目…指数は129-127=2
その他…データは2進数で1.11110000……
-1 * (1.1111) * 2^(129-127) = -111.11 = -7.75
2進数 2進数
83. 理解度チェック(解説:カ①)
*(int *)(p2 + 2)
83
0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0
arr[0] arr[1] arr[2] = 1byte
p2+2
p2+2は0x23の領域を指す
84. 理解度チェック(解説:カ②)
*(int *)(p2 + 2)
84
0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0
arr[0] arr[1] arr[2] = 1byte
(int *)(p2+2)
int *にキャストしたことで、指す領域が広がる
85. 理解度チェック(解説:カ③)
*(int *)(p2 + 2)
85
0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0
arr[0] arr[1] arr[2] = 1byte
(int *)(p2+2)
バイトオーダーに気をつけて読み取ると
0xcdef0123となる。
この領域をintで読み取る
89. メモリマップ(図解)
89
int global = 3;
void func2() {
printf(“Hellon”);
}
void func1(int a) {
int b = 1;
func2();
printf(“%d,%dn”, a , b);
}
int main(void) {
func1(1);
}
global 静的
“Hellon”
“%d,%dn”
定数
a
b
スタック(func2)
スタック(func1)
スタック(main)
メモリ
95. ダメな参照(例)
95
int *someFunc() {
int value = 3;
return &value;
}
int main(void) {
int *p = someFunc();
printf(“%dn”, *p);
return 0;
}
main
someFunc
p
value
main
someFunc
p
value
使えないアドレス
102. 文字列(図解)
102
char str[3] = {‘a’, ‘b’, ‘0’};
str
‘a’ ‘b’ ‘0’
printf(“%s”, str);
str
‘a’ ‘b’ ‘0’
strから’0’のアドレスまで表示
“ab”
= 1byte
103. 文字列(図解)
103
char str[3] = {‘a’, ‘b’, ‘0’};
printf(“%s”, str); p = str
‘a’ ‘b’ ‘0’
char *p = str;
while(*p != ‘0’) {
printf(“%c”, *p);
p++;
}
p
‘a’ ‘b’ ‘0’
p
‘a’ ‘b’ ‘0’
p++
p++
‘a’
‘b’
終了
= 1byte
105. ややこしい表記
105
char str[3] = {‘a’, ‘b’, ‘0’};
char str[] = {‘a’, ‘b’, ‘0’};
char str[] = “ab”;
文字列のために
用意された文法
(シンタックス
シュガー)
配列のルール
107. 図解
char str[] = “ab”;
char *str = “ab”;
107
ローカル‘a’ ‘b’ ‘0’
str(コンパイル時定数)
定数‘a’ ‘b’ ‘0’
str
ローカル
= 1byte
108. char *の配列とcharの2次元配列
char *の配列
char *a[] = {“ab”, “cde”};
スタックに各要素(ポインタ)
– ポインタの指す場所は変更可能
定数域にデータ
– ポインタの指すデータは変更不可能
領域を無駄なく利用
char の二次元配列
char a[2][4] = {“ab”, “cde”};
スタックにデータ
– データは書き換え可能
– aはスタック領域の配列
{“ab”, “cde”}の先頭を指すア
ドレス定数
無駄な領域が発生
108
目次方式 表方式
109. 図解
char *a[] = {“ab”, “cde”};
109
定数‘a’ ‘b’ ‘0’
a[0]
ローカル
a[1]
‘c’ ‘d’ ‘e’ ‘0’
a…char *へのポインタ
※a[0], a[1]はchar *
= 1byte
110. 図解
char a[2][4] = {“ab”, “cde”};
110
ローカル‘a’ ‘b’ ‘0’ ‘c’ ‘d’ ‘e’ ‘0’
a…char [4]へのポインタ
※a[0], a[1]はchar [4](配列)だが、
次の演算がsizeof, &以外の場合、
char *(先頭要素へのポインタ)になる
= 1byte
112. ここまでのまとめ
文字列はchar の配列である。
文字列の最後に番兵として’0’を置く
char str[] = {‘a’, ‘b’, ‘0’} は char str[] = “ab”;
と同じ意味
– これもシンタックスシュガー
– 文字列のために用意された文法
char str[][hoge];とchar *str[]は同じように使う
ことができるが、メモリの構造は異なる
112
115. 構造体
構造体を使う
115
typedef struct {
char name[10];
int age;
} person;
…
person john = {“John”, 21};
printf(“%s %dn”, john.name, john.age);
構造体の中の名前でアクセスできる
120. 構造体へのsizeof
120
sizeof(person) = 16
0 12index
レイアウトの結果定まった、構造体全体のサイズ
を返す。
– 正確には、この構造体を配列に入れて並べたときの
隣り合う2要素間のアドレスの差をbyteで返す。
アライメントの関係で、sizeof(構造体)がsizeof(メ
ンバ変数)の総和と等しくなるとは限らない
= 1byte
130. 関数ポインタの使い方
130
char func1(char *p, int n) {
return p[n];
}
char func2(char *p, int n) {
return *p + n;
}
int main(void) {
char (*pfunc)(char *, int);
pfunc = func1;
//pfunc = func2;
printf(“%c”, pfunc(“aaa”, 1));
}
どっちも型
が等しい
関数ポインタ
pfuncの宣言
どちらのアドレ
スでも代入可
呼び出しは
いつも通り
132. 関数ポインタの使い方
132
char func1(char *p, int n) {
return p[n];
}
char func2(char *p, int n) {
return *p + n;
}
int main(void) {
char (*pfunc[2])(char *, int);
pfunc[0] = pfunc1;
pfunc[1] = pfunc2;
printf(“%c”, pfunc[0](“aaa”, 1));
}
どっちも型
が等しい
関数ポインタの
配列pfuncの宣言
どちらのアドレ
スでも代入可
147. 文字の配列
147
char str[3] = {‘a’, ‘b’, ‘0’};
char str[] = {‘a’, ‘b’, ‘0’};
char str[] = “ab”;
文字列のた
めに用意さ
れた文法
配列のルール
148. const
int i = 1; // 普通の変数
const int ic = 1; // 書換不可能
const int *p1 = ⁣ // p1は書換可能。p1
の指す先は書換不可能
int * const p2 = &i; // p2は書換不可能。p2
の指す先は書換可能
const int * const p3 = ⁣ // p3もp3の指
す先も書換不可能
148
150. 複雑な記法
char (*p)[3];
char (*func)(char *, int);
char (*func(int n))(char *, int) {
…
複雑な記法が多い。
どうにかして解読できないか?
150
160. char (*(*p)())[5];
160
p is pointer to function(void) returning
char (*(*p)())[5];
なう
2 )に突き当たるまで右に読む
途中で(に出会う。
function(void) returningをつけて次へ
162. char (*(*p)())[5];
162
p is pointer to function(void) returning
pointer to
char (*(*p)())[5];
なう
3 突き当たった)に対応する(まで戻る
戻るときに、*に当たるので、
pointer to をつける
163. char (*(*p)())[5];
163
p is pointer to function(void) returning
pointer to array[5] of
char (*(*p)())[5];
なう
2 )に突き当たるまで右に読む
[が見える。array[5] of をつける
164. char (*(*p)())[5];
164
p is pointer to function(void) returning
pointer to array[5] of char.
char (*(*p)())[5];
なう
3 突き当たった)に対応する(まで戻る
一番最後に)がいると思って、
まだ読んでいないところまで左に戻ると
char.
165. char (*(*p)())[5];
165
p is pointer to function(void) returning
pointer to array[5] of char.
char (*(*p)())[5];
p は「char[5]配列へのポインタ」
を返す関数へのポインタ
173. コールバック処理の実現
処理の一部を呼び出し側に決定させる
qsort関数など(要素の大小の定義は外部で定義する)
イベント駆動プログラミングにも最適
173
typedef struct {
int w, h;
} Rect;
int comp_by_area(const void *a, const void *b) {
return (Rect *)a->w * (Rect *)a->h – (Rect *)b->w * (Rect *)b->h;
}
int main(void) {
Rect data[] = {{3, 5}, {4, 4}, {2, 7}};
qsort(data, sizeof(data)/sizeof(data[0]), sizeof(Rect), comp_by_area);
// data[] = {{2, 7}, {3, 5}, {4, 4}};
……
}
aの指すオブジェクトがbの指すものより
大きければ正
等しければ0
そうでなければ負の値
を返すような関数を定義
qsortの定義によると定義
すべき関数の型はint
(const void *a, const void
*b)である。これは任意の
型のオブジェクトに対応で
きるようにするため。
大小関係を定義した関数である
comp_by_areaを使ってソート。
大小関係の比較はcomp_by_areaで、
比較順などの最適化はqsortで行う。
175. 参考文献
『 C言語ポインタ完全制覇』,前橋 和弥, 技
術評論社, 2001
『プログラミング言語C 第2版 ANSI規格準
拠』, B.W. カーニハン (著), D.M. リッチー
(著), 石田 晴久 (翻訳), 共立出版, 1989
ポインタ虎の巻
http://www.nurs.or.jp/~sug/soft/tora/
175