19.01.2013 Views

Lbu0h9

Lbu0h9

Lbu0h9

SHOW MORE
SHOW LESS

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

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!