You also want an ePaper? Increase the reach of your titles
YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.
ポインタの裏話<br />
岡﨑 直観<br />
okazaki at ecei.tohoku.ac.jp<br />
http://www.chokkan.org/<br />
@chokkanorg<br />
ポインタの裏話<br />
プログラミング演習A 1
このような説明を<br />
覚えていますか?<br />
知らなくても全く問題ありません<br />
ポインタの裏話 プログラミング演習A 2
int x = 0;<br />
&xは変数xの「アドレス」<br />
「番地」「住所」を返す<br />
0<br />
変数x<br />
&x<br />
メモリ空間,<br />
記憶空間<br />
変数xのアド<br />
レス(住所)<br />
ポインタの裏話 プログラミング演習A 3
____<br />
/ \ 何言ってたんだこいつ?<br />
/ ⌒ ⌒ \ 番地 アドレス ぬるぽ<br />
/ (●) (●) \ \ /<br />
| 、“ ゙)(__人__)" ) __________<br />
\ 。` ⌒゚:j´ ,/ j゙~~| | | |<br />
__/ \ |__| | | |<br />
| | / , \n|| | | |<br />
| | / / r. ( こ) | | |<br />
| | | ⌒ ーnnn |\ (⊆ソ .|_|____________|<br />
¯ \__、("二)¯¯¯¯¯l二二l二二 _|_|__|_<br />
ポインタの裏話 プログラミング演習A 4
ポインタの裏話 プログラミング演習A 5
int x = 0;<br />
int *p = &x;<br />
*p = 1;<br />
変数xの値が1になるのは分かる<br />
ポインタの裏話 プログラミング演習A 6
.|<br />
.|<br />
∩___∩ |<br />
| ノ\ ,_ ヽ .|<br />
/ ●゛ ● | .J<br />
| ∪ ( _●_) ミ<br />
彡、 |∪| |<br />
/ ∩ノ ⊃ ヽ<br />
( \ / _ノ | |<br />
\ " / | |<br />
\ /¯¯¯ /<br />
¯¯¯¯<br />
何故こんな<br />
回りくどい<br />
ことを?<br />
ポインタの必然性が感じられない<br />
ポインタの裏話 プログラミング演習A 7
ポインタの裏話 プログラミング演習A 8
計算機のメモリの<br />
仕組みを知らない<br />
. .: : : : : : : : :: :::: :: :: : :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::<br />
. . : : : :: : : :: : ::: :: : :::: :: ::: ::: ::::::::::::::::::::::::::::::::::::::<br />
. . .... ..: : :: :: ::: :::::: :::::::::::: : :::::::::::::::::::::::::::::::::::::::::::::<br />
Λ_Λ . . . .: : : ::: : :: ::::::::: :::::::::::::::::::::::::::::<br />
/:彡ミ゛ヽ;)ー、 . . .: : : :::::: :::::::::::::::::::::::::::::::::<br />
/ :::/:: ヽ、ヽ、 ::i . .:: :.: ::: . :::::::::::::::::::::::::::::::::::::::<br />
/ :::/;;: ヽ ヽ ::l . :. :. .:: : :: :: :::::::: : ::::::::::::::::::<br />
¯¯¯(_,ノ ¯¯¯ヽ、_ノ¯¯¯¯<br />
ポインタの裏話 プログラミング演習A 9
ポインタの必要性が<br />
分からない<br />
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯」<br />
―――――――――――――‐┬┘<br />
|<br />
____.____ | .__ いらないポインタを<br />
| | | | |\_\ 窓から<br />
| | ∧_∧ | | | |.◎.| 投げ捨てろ<br />
| |( ´∀`)つ ミ | | |.: |<br />
| |/ ⊃ ノ | | .\|.≡.|<br />
¯¯¯¯'¯¯¯¯ | ¯<br />
ポインタの裏話 プログラミング演習A 10
今回はポインタに関する<br />
知識を⼿加減せずに<br />
すべて説明する<br />
ポインタの裏話 プログラミング演習A 11
変数があれば⼗分じゃね?<br />
ポインタの裏話 プログラミング演習A 12
その答えは<br />
「機械語」と<br />
C⾔語の「⽴ち位置」<br />
ポインタの裏話 プログラミング演習A 13
CPU<br />
ポインタの裏話 プログラミング演習A 14
⼈間の読むものではありません<br />
ポインタの裏話 プログラミング演習A 15
#include <br />
#include <br />
int main(int argc, char *argv[])<br />
{<br />
int x = atoi(argv[1]);<br />
printf("x = %d, &x = %08X¥n", x, &x);<br />
return 0;<br />
}<br />
機械語への翻訳例<br />
(コンパイル結果)<br />
cc<br />
gcc<br />
※Visual C++ 2010を⽤い<br />
Intel x86系CPUの機械語に変換<br />
※主要コード部のみ表⽰<br />
55 8b ec 51 8b 45 0c 8b 48 04 51 ff 15 a4 20 39<br />
00 83 c4 04 89 45 fc 8d 55 fc 52 8b 45 fc 50 68<br />
18 30 39 00 ff 15 9c 20 39 00 83 c4 0c 33 c0 8b<br />
e5 5d c3<br />
ポインタの裏話 プログラミング演習A 16
機械語のプログラムは16進数の羅列<br />
CPUのレジスタは⼀時変数のようなもの<br />
ポインタの裏話 プログラミング演習A 17
機<br />
械<br />
語<br />
int main(int argc, char *argv[])<br />
{<br />
55 push ebp<br />
8B EC mov ebp,esp<br />
51 push ecx<br />
int x = atoi(argv[1]);<br />
8B 45 0C mov eax,dword ptr [ebp+0Ch]<br />
8B 48 04 mov ecx,dword ptr [eax+4]<br />
51 push ecx<br />
FF 15 A4 20 2F 01 call dword ptr ds:[012F20A4h]<br />
83 C4 04 add esp,4<br />
89 45 FC mov dword ptr [ebp-4],eax<br />
printf("x = %d, &x = %08X¥n", x, &x);<br />
8D 55 FC lea edx,[ebp-4]<br />
52 push edx<br />
8B 45 FC mov eax,dword ptr [ebp-4]<br />
50 push eax<br />
68 18 30 2F 01 push 12F3018h<br />
FF 15 9C 20 2F 01 call dword ptr ds:[012F209Ch]<br />
83 C4 0C add esp,0Ch<br />
return 0;<br />
33 C0 xor eax,eax<br />
}<br />
8B E5 mov esp,ebp<br />
5D pop ebp<br />
C3 ret<br />
C⾔語のコンパイル結果<br />
強引に解釈すると<br />
ebp-4が変数xへ<br />
のポインタです<br />
ポインタの裏話 プログラミング演習A 18<br />
ア<br />
セ<br />
ン<br />
ブ<br />
リ<br />
⾔<br />
語
ポインタの裏話 プログラミング演習A 19
ポインタの裏話 プログラミング演習A 20
実⾏時間が速い<br />
計算機の能⼒を最⼤限に引き出せる<br />
ポインタの裏話 プログラミング演習A 21
ポインタを隠蔽しきれず<br />
ちょっと中途半端<br />
ポインタの裏話 プログラミング演習A 22
ポインタの裏話 プログラミング演習A 23
* + 巛 ヽ<br />
〒 ! + 。 + 。<br />
* 。<br />
+ 。 | |<br />
* + / / イヤッッホォォォオオォオウ!<br />
∧_∧ / /<br />
(´∀` / / + 。 + 。 * 。<br />
,- f<br />
/ ュヘ | * + 。 + 。 +<br />
〈_} ) |<br />
/ ! + 。 + + *<br />
./ ,ヘ |<br />
ガタン ||| j / | | |||<br />
――――――――――――<br />
ポインタの裏話 プログラミング演習A 24
── =≡∧_∧ =!!<br />
── =≡(,, ・∀・) ≡ ガッ ∧_∧<br />
─ =≡○_ ⊂)_=_ \ 从/-=≡ r( .)<br />
── =≡ > __ ノ ))< > -= 〉# つ<br />
─ =≡ ( / ≡ /VV\-=≡⊂ 、 ノ<br />
── .=≡( ノ =≡ -= し'<br />
¯¯¯¯¯¯¯¯¯|<br />
|<br />
|<br />
| ~~~~~~~~~~~~~~<br />
| ポ イ ン タ 海 溝<br />
ポインタの裏話 プログラミング演習A 25
||¯¯¯¯¯¯¯¯¯¯||<br />
|| 基礎こそ ∧_∧ いいですね。<br />
|| 重要! \ (゚Д゚,,)<br />
||________⊂⊂ |<br />
∧ ∧ ∧ ∧ ∧ ∧ |¯¯¯¯|<br />
( ∧ ∧ ( ∧ ∧ ( ∧ ∧ | |<br />
~(_( ∧ ∧ __( ∧ ∧__( ∧ ∧¯¯¯<br />
~(_( ∧ ∧_( ∧ ∧_( ∧ ∧ は~い、先生。<br />
~(_( ,,)~(_( ,,)~(_( ,,)<br />
~(___ノ ~(___ノ ~(___ノ<br />
ポインタの裏話 プログラミング演習A 26
ポインタの裏話 プログラミング演習A 27
メモリ(主記憶)の構造<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
0x00000000 7F 45 4C 46 01 02 01 06 01 00 00 00 00 00 00 00<br />
0x00000010 00 02 00 12 00 00 00 01 00 01 08 90 00 00 00 34<br />
0x00000020 00 00 1A A4 00 00 01 00 1A 34 00 20 00 05 00 28<br />
0x00000030 00 1A 00 19 00 00 00 06 00 00 00 34 00 01 00 34<br />
0x00000040 00 00 00 00 00 00 00 A0 00 00 00 A0 00 00 00 05<br />
0x00000050 00 00 00 00 00 00 00 03 00 00 00 D4 00 01 00 D4<br />
0x00000060 00 00 00 00 00 00 00 11 00 00 00 11 00 00 00 04<br />
0x00000070 00 00 00 00 00 00 00 01 00 00 00 00 00 01 00 00<br />
…<br />
…<br />
…<br />
0xFFFFFFF0<br />
ポインタの裏話 プログラミング演習A 28
主記憶の記憶状態の表現<br />
0111111101000101010011000100011000000001000000100000……<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
0x00000000 7F 45 4C 46 01 02 01 06 01 00 00 00 00 00 00 00<br />
0x00000010 00 02 00 12 00 00 00 01 00 01 08 90 00 00 00 34<br />
0x00000020 00 00 1A A4 00 00 01 00 1A 34 00 20 00 05 00 28<br />
0x00000030 00 1A 00 19 00 00 00 06 00 00 00 34 00 01 00 34<br />
0x00000040 00 00 00 00 00 00 00 A0 00 00 00 A0 00 00 00 05<br />
…<br />
0xFFFFFFF0<br />
• 8ビット(=1バイト)をまとめて⼀つの升で表現<br />
• ひとつの升は(0x00〜0xFF; 0〜255)の値を記憶できる<br />
• 計算機が持っている升の数: 主記憶の量(バイト)<br />
• この場合,0x100000000バイト = 4294967296バイト ≒ 4GB<br />
• 16個の升をまとめて1⾏で⽰すのが慣例(← いろいろ便利なので)<br />
• このため,番地(アドレス)を表現するときに16進数を使う<br />
• ⾏と列を結合したものが番地(アドレス): 例えば0x00000028番地の値は0x1A<br />
ポインタの裏話 プログラミング演習A 29
記憶内容の読み出し/書き込み<br />
• 番地(位置)と型(⼤きさ)を指定する<br />
• 00010100番地に対して…<br />
• char (1バイト): 0x41<br />
• short (2バイト): 0x4142<br />
• int (4バイト): 0x41424344<br />
• double (8バイト): 2393736.0<br />
• char[3] (3バイト): {0x41, 0x42, 0x43}<br />
• char*: “ABCD”<br />
※演習室の計算機のCPUは<br />
ビッグエンディアン<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
…<br />
0x00010100 41 42 43 44 00 00 00 00 00 00 00 00 00 00 00 00<br />
…<br />
ポインタの裏話 プログラミング演習A 30
C⾔語の変数の<br />
裏で番地と型が<br />
管理されている<br />
主記憶の仕組みを知らなくても<br />
変数や配列が使えている<br />
ポインタの裏話 プログラミング演習A 31
ポインタの裏話 プログラミング演習A 32
ポインタの裏話 プログラミング演習A 33<br />
:
0x00000028<br />
ポインタの裏話 プログラミング演習A 34
char(1バイト)として読む<br />
// p: 0x00000028番地からchar(1バイト)にアクセスする手段<br />
char *p = (char *)0x00000028;<br />
// pが指している番地(0x00000028)の値を読み込む<br />
v = *p;<br />
vに0x1A (= 26) が代入される<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
0x00000000 7F 45 4C 46 01 02 01 06 01 00 00 00 00 00 00 00<br />
0x00000010 00 02 00 12 00 00 00 01 00 01 08 90 00 00 00 34<br />
0x00000020 00 00 1A A4 00 00 01 00 1A 34 00 20 00 05 00 28<br />
0x00000030 00 1A 00 19 00 00 00 06 00 00 00 34 00 01 00 34<br />
0x00000040 00 00 00 00 00 00 00 A0 00 00 00 A0 00 00 00 05<br />
0x00000050 00 00 00 00 00 00 00 03 00 00 00 D4 00 01 00 D4<br />
0x00000060 00 00 00 00 00 00 00 11 00 00 00 11 00 00 00 04<br />
0x00000070 00 00 00 00 00 00 00 01 00 00 00 00 00 01 00 00<br />
…<br />
…<br />
…<br />
0xFFFFFFF0<br />
ポインタの裏話 プログラミング演習A 35
short(2バイト)として読む<br />
// p: 0x00000028番地からshort(2バイト)にアクセスする手段<br />
short *p = (short *)0x00000028;<br />
// pが指している番地(0x00000028)の値を読み込む<br />
v = *p;<br />
vに0x1A34 (= 6708) が代入される<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
0x00000000 7F 45 4C 46 01 02 01 06 01 00 00 00 00 00 00 00<br />
0x00000010 00 02 00 12 00 00 00 01 00 01 08 90 00 00 00 34<br />
0x00000020 00 00 1A A4 00 00 01 00 1A 34 00 20 00 05 00 28<br />
0x00000030 00 1A 00 19 00 00 00 06 00 00 00 34 00 01 00 34<br />
0x00000040 00 00 00 00 00 00 00 A0 00 00 00 A0 00 00 00 05<br />
0x00000050 00 00 00 00 00 00 00 03 00 00 00 D4 00 01 00 D4<br />
0x00000060 00 00 00 00 00 00 00 11 00 00 00 11 00 00 00 04<br />
0x00000070 00 00 00 00 00 00 00 01 00 00 00 00 00 01 00 00<br />
…<br />
…<br />
…<br />
0xFFFFFFF0<br />
※ビックエンディアンを仮定<br />
ポインタの裏話 プログラミング演習A 36
int(4バイト)として読む<br />
// p: 0x00000028番地からint(4バイト)にアクセスする手段<br />
int *p = (int *)0x00000028;<br />
// pが指している番地(0x00000028)の値を読み込む<br />
v = *p;<br />
vに0x1A340020 (= 439615520) が代入される<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
0x00000000 7F 45 4C 46 01 02 01 06 01 00 00 00 00 00 00 00<br />
0x00000010 00 02 00 12 00 00 00 01 00 01 08 90 00 00 00 34<br />
0x00000020 00 00 1A A4 00 00 01 00 1A 34 00 20 00 05 00 28<br />
0x00000030 00 1A 00 19 00 00 00 06 00 00 00 34 00 01 00 34<br />
0x00000040 00 00 00 00 00 00 00 A0 00 00 00 A0 00 00 00 05<br />
0x00000050 00 00 00 00 00 00 00 03 00 00 00 D4 00 01 00 D4<br />
0x00000060 00 00 00 00 00 00 00 11 00 00 00 11 00 00 00 04<br />
0x00000070 00 00 00 00 00 00 00 01 00 00 00 00 00 01 00 00<br />
…<br />
…<br />
…<br />
0xFFFFFFF0<br />
※ビックエンディアンを仮定<br />
ポインタの裏話 プログラミング演習A 37
double(8バイト)として読む<br />
// p: 0x00000028番地からdouble(8バイト)にアクセスする手段<br />
double *p = (double *)0x00000028;<br />
// pが指している番地(0x00000028)の値を読み込む<br />
v = *p;<br />
vに0x1A34002000050028を浮動小数点で解釈したもの(1.882796×10 -182 )を代入<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
0x00000000 7F 45 4C 46 01 02 01 06 01 00 00 00 00 00 00 00<br />
0x00000010 00 02 00 12 00 00 00 01 00 01 08 90 00 00 00 34<br />
0x00000020 00 00 1A A4 00 00 01 00 1A 34 00 20 00 05 00 28<br />
0x00000030 00 1A 00 19 00 00 00 06 00 00 00 34 00 01 00 34<br />
0x00000040 00 00 00 00 00 00 00 A0 00 00 00 A0 00 00 00 05<br />
0x00000050 00 00 00 00 00 00 00 03 00 00 00 D4 00 01 00 D4<br />
0x00000060 00 00 00 00 00 00 00 11 00 00 00 11 00 00 00 04<br />
0x00000070 00 00 00 00 00 00 00 01 00 00 00 00 00 01 00 00<br />
…<br />
…<br />
…<br />
0xFFFFFFF0<br />
※ビックエンディアンを仮定<br />
ポインタの裏話 プログラミング演習A 38
こんなことも出来ます(voidポインタ)<br />
// p: 0x00000028番地を指す手段(アクセス方法は指定しない)<br />
void *p = (void *)0x00000028;<br />
// 読み込み方法が指定されていないので,下のコードはコンパイルできない<br />
v = *p;<br />
// アクセス方法を一時的にintに指定(キャスト)してアクセス<br />
v = *(int *)p;<br />
vに0x1A340020 (= 439615520) が代入される<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
0x00000000 7F 45 4C 46 01 02 01 06 01 00 00 00 00 00 00 00<br />
0x00000010 00 02 00 12 00 00 00 01 00 01 08 90 00 00 00 34<br />
0x00000020 00 00 1A A4 00 00 01 00 1A 34 00 20 00 05 00 28<br />
0x00000030 00 1A 00 19 00 00 00 06 00 00 00 34 00 01 00 34<br />
0x00000040 00 00 00 00 00 00 00 A0 00 00 00 A0 00 00 00 05<br />
0x00000050 00 00 00 00 00 00 00 03 00 00 00 D4 00 01 00 D4<br />
0x00000060 00 00 00 00 00 00 00 11 00 00 00 11 00 00 00 04<br />
0x00000070 00 00 00 00 00 00 00 01 00 00 00 00 00 01 00 00<br />
…<br />
…<br />
…<br />
0xFFFFFFF0<br />
ポインタの裏話 プログラミング演習A 39
ポインタ: 番地+アクセス⽅法の組<br />
char *p = (char *)0x00000028;<br />
番地: *pでアクセスしたい番地<br />
pの値 → 0x00000028<br />
アクセス方法: *pとしたときの型<br />
*pの値 → 0x1A (char型)<br />
ポインタの裏話 プログラミング演習A 40
つまり,ポインタ変数(例えばp)とは<br />
• 基本的には番地を記憶する変数<br />
char *p = (char *)0x00000028;<br />
• このときpの値は0x00000028<br />
• つまりint型の普通の変数と同じと思えばよい<br />
• ただし,以下の特殊機能が⽤意されている<br />
• *p: pの番地の値にアクセス<br />
• p[i]: pからiだけ離れた番地の値にアクセス<br />
• p+i: pからiだけ離れた番地を計算する<br />
配列の正体<br />
ポインタの裏話 プログラミング演習A 41
p[i] p+i<br />
ポインタの裏話 プログラミング演習A 42
char*に対する操作<br />
char *p = (char *)0x00000028;<br />
v = *p; ++p;<br />
v = *p;<br />
v → 0x1A<br />
v = p[1];<br />
v = *(p+2);<br />
p→0x00000029<br />
v → 0x34 v → 0x34 v → 0x00<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
0x00000000 7F 45 4C 46 01 02 01 06 01 00 00 00 00 00 00 00<br />
0x00000010 00 02 00 12 00 00 00 01 00 01 08 90 00 00 00 34<br />
0x00000020 00 00 1A A4 00 00 01 00 1A 34 00 20 00 05 00 28<br />
0x00000030 00 1A 00 19 00 00 00 06 00 00 00 34 00 01 00 34<br />
0x00000040 00 00 00 00 00 00 00 A0 00 00 00 A0 00 00 00 05<br />
0x00000050 00 00 00 00 00 00 00 03 00 00 00 D4 00 01 00 D4<br />
0x00000060 00 00 00 00 00 00 00 11 00 00 00 11 00 00 00 04<br />
0x00000070 00 00 00 00 00 00 00 01 00 00 00 00 00 01 00 00<br />
…<br />
…<br />
…<br />
0xFFFFFFF0<br />
ポインタの裏話 プログラミング演習A 43
short*に対する操作<br />
short *p = (short *)0x00000028;<br />
v = *p; ++p;<br />
v = *p;<br />
v → 0x1A34<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
0x00000000 7F 45 4C 46 01 02 01 06 01 00 00 00 00 00 00 00<br />
0x00000010 00 02 00 12 00 00 00 01 00 01 08 90 00 00 00 34<br />
0x00000020 00 00 1A A4 00 00 01 00 1A 34 00 20 00 05 00 28<br />
0x00000030 00 1A 00 19 00 00 00 06 00 00 00 34 00 01 00 34<br />
0x00000040 00 00 00 00 00 00 00 A0 00 00 00 A0 00 00 00 05<br />
0x00000050 00 00 00 00 00 00 00 03 00 00 00 D4 00 01 00 D4<br />
0x00000060 00 00 00 00 00 00 00 11 00 00 00 11 00 00 00 04<br />
0x00000070 00 00 00 00 00 00 00 01 00 00 00 00 00 01 00 00<br />
…<br />
…<br />
…<br />
0xFFFFFFF0<br />
v = p[1];<br />
v = *(p+2);<br />
p→0x0000002A<br />
v → 0x0020 v → 0x0020 v → 0x0005<br />
連続したshort型(2バイト)のデータにアクセスしやすいように,<br />
ポインタ変数の加減算の移動単位が⾃動的に2になる仕様!<br />
ポインタの裏話 プログラミング演習A 44
int*に対する操作<br />
int *p = (int *)0x00000028;<br />
v = *p; ++p;<br />
v = *p;<br />
v→0x1A340020<br />
v = p[1];<br />
v = *(p+2);<br />
p→0x0000002C<br />
v→0x00050028 v→0x00050028 v→0x001A0019<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
0x00000000 7F 45 4C 46 01 02 01 06 01 00 00 00 00 00 00 00<br />
0x00000010 00 02 00 12 00 00 00 01 00 01 08 90 00 00 00 34<br />
0x00000020 00 00 1A A4 00 00 01 00 1A 34 00 20 00 05 00 28<br />
0x00000030 00 1A 00 19 00 00 00 06 00 00 00 34 00 01 00 34<br />
0x00000040 00 00 00 00 00 00 00 A0 00 00 00 A0 00 00 00 05<br />
0x00000050 00 00 00 00 00 00 00 03 00 00 00 D4 00 01 00 D4<br />
0x00000060 00 00 00 00 00 00 00 11 00 00 00 11 00 00 00 04<br />
0x00000070<br />
…<br />
00 00 00 00 00 00 00 01 00 00 00 00 00 01 00 00<br />
…<br />
…<br />
0xFFFFFFF0<br />
ポインタ変数の加減算の移動単位が⾃動的に4になる仕様!<br />
連続したint型(4バイト)のデータにアクセスしやすいように,<br />
ポインタの裏話 プログラミング演習A 45
ポインタの番地に対する加減算<br />
int *p = (int *)0x00000028;<br />
++p;<br />
--p;<br />
p += 2;<br />
pの値 → 0x0000002C<br />
pの値 → 0x00000024<br />
pの値 → 0x00000030<br />
C言語の仕様により,自動的にポイン<br />
タの型を考慮し,加減算が実行される<br />
ポインタの裏話 プログラミング演習A 46
配列とポインタの関係<br />
int *p = (int *)0x00000028;<br />
p[0];<br />
0x00000028番地の値<br />
p[1]; 0x0000002C番地の値<br />
*(p+1);<br />
0x0000002C番地の値<br />
p[i] == *(p+i)<br />
配列は加減算付きのポインタへのアクセス<br />
ポインタの裏話 プログラミング演習A 47
ポインタの裏話 プログラミング演習A 48
char *p = (char *)0x00000018;<br />
みたいなコードは<br />
最近⾒かけない<br />
ポインタの裏話 プログラミング演習A 49
実際に実⾏すると<br />
セグメンテーション<br />
フォルトが発⽣<br />
ヘ⌒ヽフ<br />
( ・ω・) そっか、ぬるぽか!<br />
/ ~つと)<br />
ポインタの裏話 プログラミング演習A 50
ポインタの裏話 プログラミング演習A 51
プログラムはOS<br />
から割り当てられた<br />
番地のみ利⽤できる<br />
ポインタの裏話 プログラミング演習A 52
プログラムがどの<br />
番地を利⽤できるか<br />
実⾏するまで不明<br />
ポインタの裏話 プログラミング演習A 53
プログラムを実⾏する流れ<br />
プログラム・データ<br />
a.out<br />
実⾏<br />
コンパイル結果そのもの<br />
通常は0x00010000から<br />
貼付けられる<br />
プログラムの実⾏につれ<br />
サイズが増減する<br />
プログラムがアクセスして<br />
はいけない領域<br />
プログラム・データ<br />
ヒープ<br />
スタック<br />
メモリ空間<br />
0x00000000<br />
0x00010000<br />
実⾏ファイル<br />
サイズ<br />
追加要求<br />
追加要求<br />
0xFFBEC000<br />
※32ビットSPARC上で動作する<br />
Solarisの場合<br />
※状況に応じてアドレスが変わる<br />
参考: プログラムの読み込み (http://docs.oracle.com/cd/E19253-01/819-0391/chapter6-34713/index.html)<br />
ポインタの裏話 プログラミング演習A 54
セグメンテーション違反<br />
(Segmentation Fault)<br />
≒ 許可されない(予期せぬ)<br />
番地への読み書き(バグ)<br />
\ | /<br />
_┌┬┬┬┐_<br />
――┴┴┴┴┴―、 __________<br />
// ∧// ∧ ∧||. \ /<br />
__[//____(゚_//[ ].゚Д゚,,) || _ \__ < ぬるぽを逮捕しにきました<br />
lロ|=☆= |ロロ゚|■■|■■ OS ■■|| \__________<br />
| ∈口∋¯_l__l⌒l____|___l⌒l___||<br />
¯¯`ー'¯ `ー' `ー' `ー'<br />
ポインタの裏話 プログラミング演習A 55
ポインタの裏話 プログラミング演習A 56
ポインタの番地を<br />
決めるのはOSや<br />
コンパイラの仕事<br />
ポインタの裏話 プログラミング演習A 57
ポインタの番地を<br />
直接指定する<br />
機会は殆ど無い<br />
ポインタの裏話 プログラミング演習A 58
ポインタの番地を正<br />
しくセットしないと<br />
Segmentation Fault<br />
ポインタの裏話 プログラミング演習A 59
ポインタの裏話 プログラミング演習A 60
C⾔語におけるポインタの使い道<br />
1. 配列<br />
• 配列とポインタは等価<br />
2. ⽂字列<br />
• char型の⽂字の特殊な配列として表現される<br />
3. 関数の引数の参照渡し<br />
• 引数の変数の値を関数内で更新して返す場合<br />
4. メモリ領域の動的な確保<br />
• 配列や構造体を必要なサイズだけ確保する<br />
5. 関数ポインタ<br />
• ⾼等テクニックなので説明は省略<br />
ポインタの裏話 プログラミング演習A 61
①配列としての<br />
ポインタ<br />
ポインタの裏話 プログラミング演習A 62
配列を宣⾔するとどうなるか<br />
int data[6] = {0,1,1,0,1,0};<br />
• コンパイラは,以下の処理を⾃動で⾏う<br />
• 6(個) ×4(byte/int)=24 (byte) の記憶領域を予約<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
…<br />
0x00010220 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD<br />
0x00010230 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD<br />
0x00010240 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD<br />
0x00010250 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD<br />
0x00010260 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD<br />
…<br />
…<br />
0xFFFFFFF0<br />
ポインタの裏話 プログラミング演習A 63
配列を宣⾔するとどうなるか<br />
int data[6] = {0,1,1,0,1,0};<br />
• コンパイラは,以下の処理を⾃動で⾏う<br />
• 6(個) ×4(byte/int)=24 (byte) の記憶領域を予約<br />
• (指定された初期化値を確保した記憶領域にセット)<br />
• メモリ領域の先頭の番地をポインタ変数dataにセット<br />
• この例では,int *data = (int *)0x00010234;<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
…<br />
0x00010220 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD<br />
0x00010230 CD CD CD CD 00 00 00 00 00 00 00 01 00 00 00 01<br />
0x00010240 00 00 00 00 00 00 00 01 00 00 00 00 CD CD CD CD<br />
0x00010250 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD<br />
0x00010260 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD<br />
…<br />
…<br />
0xFFFFFFF0<br />
ポインタの裏話 プログラミング演習A 64
このように<br />
配列のメモリ管理は<br />
⾃動で⾏われる<br />
ポインタの裏話 プログラミング演習A 65
配列がポインタであ<br />
ることを意識せずに<br />
data[i]と書ける<br />
ポインタの裏話 プログラミング演習A 66
②⽂字列を表現する<br />
ポインタ<br />
ポインタの裏話 プログラミング演習A 67
⽂字列を宣⾔するとどうなるか<br />
char *msg = "hello";<br />
• コンパイラは⽂字 (char) 配列に⾃動で変換<br />
char str[] = {'h','e','l','l','o',0};<br />
= {0x68,0x65,0x6C,0x6C,0x6F,0};<br />
• ⽂字列の終端を⽰す0が追加される(⽂字列特有の仕様)<br />
• この例では,msg = (char *)0x00010250;<br />
アドレス 0 1 2 3 4 5 6 7 8 9 A B C D E F<br />
…<br />
0x00010220 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD<br />
0x00010230 CD CD CD CD 00 00 00 00 00 00 00 01 00 00 00 01<br />
0x00010240 00 00 00 00 00 00 00 01 00 00 00 00 CD CD CD CD<br />
0x00010250 68 65 6C 6C 6F 00 CD CD CD CD CD CD CD CD CD CD<br />
0x00010260 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD<br />
…<br />
…<br />
0xFFFFFFF0<br />
ポインタの裏話 プログラミング演習A 68
実は””で⽂字列を<br />
使う度に,裏で⽂字<br />
配列が⽣成される<br />
ポインタの裏話 プログラミング演習A 69
printf(“hello!”);<br />
でも⽂字の配列が<br />
メモリ上に確保される<br />
ポインタの裏話 プログラミング演習A 70
③関数への参照渡し<br />
ポインタの裏話 プログラミング演習A 71
関数とは<br />
• C⾔語の関数<br />
• 0個以上の値を引数として受け取る<br />
• 0個または1個の値を戻り値として返す<br />
戻り値の型 関数名 引数1 引数2<br />
double func(double x, double y)<br />
{<br />
return sqrt(x * x + y * y);<br />
}<br />
• 使い⽅<br />
func(x, y)の戻り値: � � �� �<br />
d = func(3, 4); if (func(x, y) < 1)<br />
ポインタの裏話 プログラミング演習A 72
関数から2つ以上<br />
の値を返すには?<br />
func<br />
直交座標系 極座標系<br />
ポインタの裏話 プログラミング演習A 73
陥りやすいダメな例(cf. 確認問題)<br />
#include <br />
#include <br />
void func(double x, double y, double r, double th)<br />
{<br />
}<br />
r = sqrt(x * x + y * y);<br />
th = atan2(y, x);<br />
変数rとthはfunc関数内で<br />
のみ有効.main関数側に<br />
変更が反映されない!<br />
int main()<br />
{<br />
double x = 1, y = 1, r = 0, th = 0;<br />
func(x, y, r, th);<br />
printf("(r, th) = (%f, %f)¥n", r, th);<br />
}<br />
$ ./a.out<br />
(r, th) = (0.00000, 0.00000)<br />
ポインタの裏話 プログラミング演習A 74
関数の呼び出し元の変数の値を更新する<br />
#include <br />
#include <br />
void func(double x, double y, double *r, double *th)<br />
{<br />
*r = sqrt(x * x + y * y); ポインタ変数が指している<br />
}<br />
*th = atan2(y, x);<br />
値を更新する ③<br />
int main()<br />
{<br />
更新したい変数の番地を渡す<br />
double x = 1, y = 1, r = 0, th = 0;<br />
func(x, y, &r, &th);<br />
printf("(r, th) = (%f, %f)¥n", r, th);<br />
}<br />
$ ./a.out<br />
(r, th) = (1.4142, 0.7854)<br />
更新したい変数をポインタに<br />
ポインタの裏話 プログラミング演習A 75<br />
①<br />
②<br />
④
どんなトリックなのか(1/3)<br />
①: double x = 1, y = 1, r = 0, th = 0;<br />
コンパイラは,main関数内でdouble型(8バイト)<br />
の変数,x, y, r, thの値を保持するメモリを予約<br />
1.0: 0x3FF0000000000000<br />
0.0: 0x0000000000000000<br />
②: func(x, y, &r, &th);<br />
※浮動⼩数点のやや<br />
こしい仕様です.気<br />
にしないで下さい.<br />
※対⽐のため,浮動⼩数点を無理<br />
⽮理8バイトで表記しています.<br />
func(<br />
0x3FF0000000000000,0x3FF0000000000000,<br />
0x00010270, 0x00010278);<br />
アドレス<br />
…<br />
0<br />
x<br />
1 2 3 4 5 6 7 8<br />
y<br />
9 A B C D E F<br />
0x00010260 3F F0 00 00 00 00 00 00 3F F0 00 00 00 00 00 00<br />
0x00010270<br />
…<br />
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00<br />
r th<br />
ポインタの裏話 プログラミング演習A 76
どんなトリックなのか(2/3)<br />
③: *r = sqrt(x * x + y * y);<br />
*th = atan2(y, x);<br />
1 � �1 � � 1.4142 …<br />
tan �� 1 � �/4 � 0.7854 …<br />
②によりmain関数の変数r,thのメモリ番地は,<br />
r=0x00010270, th=0x00010278と伝えられている<br />
*r = 1.4142…<br />
*th = 0.7854…<br />
1.4142…: 0x3FF6A09E667F3BCD<br />
0.7854…: 0x3FE921FB54442D18<br />
※浮動⼩数点のややこしい仕様<br />
です.気にしないで下さい.<br />
アドレス<br />
…<br />
0<br />
x<br />
1 2 3 4 5 6 7 8<br />
y<br />
9 A B C D E F<br />
0x00010260 3F F0 00 00 00 00 00 00 3F F0 00 00 00 00 00 00<br />
0x00010270<br />
…<br />
3F F6 A0 9E 66 7F 3B CD 3F E9 21 FB 54 44 2D 18<br />
r th<br />
00010270番地に1.4142…<br />
00010278番地に0.7854…<br />
ポインタの裏話 プログラミング演習A 77
どんなトリックなのか(3/3)<br />
変数r,thの値を格納しているメモリの内容が書き換え<br />
られたので,r = 1.4142…, th = 0.7854…<br />
つまり,main関数が所有している変数の値を,<br />
func関数から直接変更していたことになる<br />
④: printf("(r, th) = (%f, %f)¥n", r, th);<br />
$ ./a.out<br />
(r, th) = (1.4142, 0.7854)<br />
アドレス<br />
…<br />
0<br />
x<br />
1 2 3 4 5 6 7 8<br />
y<br />
9 A B C D E F<br />
0x00010260 3F F0 00 00 00 00 00 00 3F F0 00 00 00 00 00 00<br />
0x00010270<br />
…<br />
3F F6 A0 9E 66 7F 3B CD 3F E9 21 FB 54 44 2D 18<br />
r th<br />
ポインタの裏話 プログラミング演習A 78
関数内で呼び出し元<br />
の変数の値を更新す<br />
るには,ポインタ化<br />
ポインタの裏話 プログラミング演習A 79
参照渡しの有名な例<br />
ポインタの裏話 プログラミング演習A 80
scanf関数は複数の<br />
値の同時読み込みが<br />
可能になっている<br />
ポインタの裏話 プログラミング演習A 81
このような仕様のため<br />
scanf(“%d %d”, &x, &y);<br />
のように&をつける<br />
ポインタの裏話 プログラミング演習A 82
④メモリ領域の<br />
動的な確保<br />
ポインタの裏話 プログラミング演習A 83
メモリを使うには,<br />
OSに割り当てて<br />
もらう必要がある<br />
ポインタの裏話 プログラミング演習A 84
プログラム執筆時点で<br />
要素数が確定している<br />
配列・⽂字列の確保は<br />
コンパイラの仕事<br />
ポインタの裏話 プログラミング演習A 85
プログラムを書いた時<br />
点で必要な要素数が分<br />
からない場合,⼿動で<br />
メモリ確保する必要<br />
ポインタの裏話 プログラミング演習A 86
例えば・・・<br />
ポインタの裏話 プログラミング演習A 87
ワードプロセッサに<br />
何⽂字⼊⼒されるか<br />
分からない<br />
ポインタの裏話 プログラミング演習A 88
受信するメールの<br />
⼤きさが分からない<br />
ポインタの裏話 プログラミング演習A 89
こういう時は・・・<br />
ポインタの裏話 プログラミング演習A 90
400⽂字格納できる⽂字<br />
列⽤のメモリを下さい<br />
「原稿⽤紙をもう⼀枚下さい!」<br />
ポインタの裏話 プログラミング演習A 91
3,487,394バイトの添付<br />
ファイルを保存・閲覧す<br />
るためのメモリを下さい<br />
ポインタの裏話 プログラミング演習A 92
メモリ管理関数の抜粋<br />
• void *malloc(size_t size);<br />
• sizeバイトの連続したメモリ領域を確保し,そのメモリ<br />
領域の先頭の番地を返す<br />
• void *calloc(size_t n, size_t size);<br />
• メモリ領域を0に初期化しながら確保(詳細は省略)<br />
• void *realloc(void *p, size_t size);<br />
• mallocやcallocで確保されたメモリ領域(先頭の番<br />
地がp)のサイズをsizeに拡張・縮⼩し,新しいメモリ<br />
領域の先頭の番地を返す<br />
• char *strdup(const char *str);<br />
• ⽂字列strを格納できるメモリ領域を確保した後,⽂字<br />
列strを新しいメモリ領域にコピーし,<br />
• void free(void *p);<br />
• 上に挙げた関数で確保されたメモリを解放する<br />
ポインタの裏話 プログラミング演習A 93
400⽂字格納できる⽂字<br />
列⽤のメモリを下さい<br />
char *p = (char *)malloc(400);<br />
ポインタの裏話 プログラミング演習A 94
メモリが不要になった<br />
のでお返しします<br />
free(p);<br />
ポインタの裏話 プログラミング演習A 95
ポインタの裏話 プログラミング演習A 96
___<br />
/ \ キリッ<br />
/ \ , , /\<br />
/ (●) (●) \<br />
| (__人__) |<br />
\ ` ⌒ ´ ,/<br />
. /⌒~"¯,¯¯〆⌒,ニつ<br />
| ,___゙___、rヾイソ⊃<br />
| `l¯<br />
. | |<br />
メモリのイメージが<br />
掴めればポインタは<br />
難しくない!<br />
ポインタの裏話 プログラミング演習A 97
ポインタの⽤途は<br />
限られているので<br />
実例を⾒て慣れよ<br />
|¯¯¯¯¯¯¯¯¯¯¯|<br />
| ここでポインタ! |<br />
|___________|<br />
∧∧ ||<br />
( ゚д゚)||<br />
/ づΦ<br />
ポインタの裏話 プログラミング演習A 98
今回扱わなかった内容<br />
• malloc/freeの実例<br />
• ⼆次元配列<br />
• ポインタのポインタ<br />
• constなどの修飾⼦<br />
• 関数ポインタ<br />
• 構造体のポインタ<br />
ポインタの裏話 プログラミング演習A 99