Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

C言語ポインタ講座 (Lecture of Pointer in C)

サークルのC言語ポインタ講座の外部公開用資料です。
2017/11/12 改訂&演習問題追加

This is an document of pointer in C.
English version is not available.

  • Login to see the comments

C言語ポインタ講座 (Lecture of Pointer in C)

  1. 1. C言語ポインタ講座 @kakira9618 2017/11/12
  2. 2. この資料について  この資料は、@kakira9618が所属している サークルでの講座『C言語ポインタ講座』 で使用していた資料を、一部修正・加筆し て、一般公開したものです。  質問、指摘などは@kakira9618まで。 – https://twitter.com/kakira9618 2
  3. 3. この講座の対象 3 1 ポインタの基本的な使い方を知っている ポインタ変数の宣言の仕方と使い方 ポインタを使ったプログラムが一応書ける 2 動作原理、記法がよく分かっていない ポインタ変数が派生型である理由を説明できない 配列の要素へのアクセスの原理を説明できない int (*p)[3]; って何? という人 3 良い使い方がわからない で、どういうときに使うと良いの?という人
  4. 4. ポインタのポイント これらを把握すればポインタは理解できる! 4 1 原理 ポインタがどのようにしてメモリ上の情報を 読んでいるかを理解する。 2 記法 ポインタの記法を整理する。 3 使い方 ポインタを使うと効率よく書ける場面を把握。
  5. 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
  6. 6. 注意  この資料中に掲載しているプログラムは ポインタの文法や原理、使い方の解説をす るために掲載しているものです。  そのため、実務のプログラミングでそのま ま使うと怒られるようなコード、もしくは 他の方法を使ったほうが良いようなコード 等が入っていたりしますがご了承ください。 6
  7. 7. 目次 7 記法 使い方 原理
  8. 8. ポインタの指す”幅”を意識する ポインタはただ場所を保存するだけじゃない! 8
  9. 9. 質問です 9 Q. C言語におけるポインタ変数とは?
  10. 10. 質問です 10 Q. C言語におけるポインタ変数とは? アドレスを保存する変数! 確かにその通り。 だけど、理解するにはもう一歩。
  11. 11. アドレスとポインタ変数の違い アドレス  ポインタ変数の値  メモリ上の位置を示す 番号のこと – 0x12345678とか ポインタ変数  アドレスを格納する変数 – 格納するアドレスを自由に 変更できる 11 int *p = &intValue ; アドレスポインタ変数
  12. 12. さらに質問 12 Q. &演算子(1項演算子)で計算されるものは?
  13. 13. さらに質問 13 Q. 式中の&演算子(1項演算子)で計算されるものは? その変数のアドレス! その変数のアドレス、ですね。 だけど、理解するにはもう一歩。 う~ん... 論理積かな?
  14. 14. &演算子 14  &を変数の前につけると、 が得られる 変数の先頭アドレス
  15. 15. 図解  int value;  &value 15 value value ここのアドレスが得られる = 1byte
  16. 16. 参照  参照するときは、ポインタ変数の前に*を つけます。  これで、pの記憶していたアドレスに書い てある内容を取り出すわけですが・・・ 16 int *p = &intValue; printf(“%d”, *p);
  17. 17. 重要な考察  int *p = &intValue; では、pがintValueの 先頭アドレスで初期化される。  *pで、pのアドレスの内容を取り出す 値を取り出すとき、 どこから取り出せば良いのかは分かる。 どこまで取り出せばよいのかは?? 17
  18. 18. 図解  int *p = &value;  *p 18 valuep p どこまで読めば良いの??= 1byte p 変数pに のアドレスが入っていることを意味注意: =
  19. 19. 重要な考察 19 &のときに「valueの」という 情報も保存してるのでは? →まちがい。pはアドレスしか保存していません。 ポインタ自身の型から推測? →せいかい!
  20. 20. 重要な考察  int *p; としたとき、  *pとすると、pに記憶されているアドレス から、int型分だけ読み込む 20 pは int型への ポインタ変数
  21. 21. 図解  int *p = &value;  *p 21 valuep p int型分 「int型分」はpの宣言「int *p」から。 = 1byte
  22. 22. ポインタ演算  p++; や、p--; とするとポインタに保存さ れているアドレスが1単位動く  アドレスがいつも1byte動くわけではない  ポインタ変数の宣言から、参照元の型を 見て、その型の大きさを1単位とする。 22
  23. 23. 図解  int型へのポインタpの場合: 23 p int型分 p int型分 p++; int型分だけずれる = 1byte
  24. 24. 図解  short int型へのポインタpの場合: 24 p short int 型分 p p++; short int型分だけずれる short int 型分 = 1byte
  25. 25. ここまでのまとめ  ポインタはアドレスを保存する変数  値を参照するときに、自身の型から参照すべ き幅を計算  ポインタ演算(++, --など)をするときも、 自身の型から増やすべき値を計算。  アドレスにも型があり、同じように計算する。 25 ポインタやアドレスの指す先には ”幅”があるイメージ
  26. 26. メモリへの情報格納方法 同じビット列でも格納方法・型が違えば違う値! 26
  27. 27. データの格納順(バイトオーダー)  情報をどういう順番で格納していくか  大きく分けて2つ 27 1 ビックエンディアン 上位バイトを上位バイト、 下位バイトを下位バイトに格納 2 リトルエンディアン 上位バイトを下位バイト、 下位バイトを上位バイトに格納 このような格納順をバイトオーダーという 素直 ひねくれ
  28. 28. ビックエンディアン  0x12345678(0xは16進数を表す)を ビックエンディアンで格納するとき: 28 0 x 1 2 3 4 5 6 7 8 0x12 0x34 0x56 0x78 上位 下位 格納 ソース上 メモリ上 = 1byte
  29. 29. ビックエンディアン  0x12345678(0xは16進数を表す)を ビックエンディアンで取り出すとき: 29 0 x 1 2 3 4 5 6 7 8 0x12 0x34 0x56 0x78 上位 下位 取出 ソース上 メモリ上 = 1byte
  30. 30. リトルエンディアン  0x12345678(0xは16進数を表す)を リトルエンディアンで格納するとき: 30 0 x 1 2 3 4 5 6 7 8 0x78 0x56 0x34 0x12 上位 下位 格納 ソース上 メモリ上 = 1byte
  31. 31. リトルエンディアン  0x12345678(0xは16進数を表す)を リトルエンディアンで取り出すとき: 31 0 x 1 2 3 4 5 6 7 8 0x78 0x56 0x34 0x12 上位 下位 取出 ソース上 メモリ上 = 1byte
  32. 32. float(double)型の格納方法  IEEE 754の規格に基づく。  32bitを3つの領域に分ける – 符号部分(1bit):+なら0, -なら1 – 指数部分(8bit) :基数部分を2の何乗するか – 基数部分(23bit) :基となる数字の並び  (値) = (符号) * 1.(基数) * 2 32 (指数-127) 符号 1bit 指数 8bit 基数 23bit
  33. 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→
  34. 34. float (double)型の格納方法 34 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
  35. 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. 型と数値 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. 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
  38. 38. 型と数値  このビット列をfloat型だと思うと – ビックエンディアンなら • 0xC0F80000 = -7.75という数値 – リトルエンディアンなら • 0x0000F8C0 = 0.0という数値 38 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
  39. 39. 型と数値  0xC0F80000が格納されている領域の 先頭アドレスがポインタに代入されている 39 unsigned int a = 0xC0F80000; unsigned int *p1 = &a; int *p2 = (int *)&a; float *p3 = (float *)&a; ※ &aの左の(int *)などはキャストと呼ばれる。 キャストにより型の変換をすることができる。 アドレスに対してキャストを行った場合、 アドレスの値は変わらないが、アドレスの型のみが変わる。
  40. 40. 型と数値  0xC0F80000が格納されている領域の 先頭アドレスがポインタに代入されている  このとき、保持しているポインタの型に よって、読みだした結果が異なる! 40 unsigned int a = 0xC0F80000; unsigned int *p1 = &a; int *p2 = (int *)&a; float *p3 = (float *)&a; // 3237478400, -1057488896, -7.750000 printf(“%u, %d, %fn”, *p1, *p2, *p3);
  41. 41. 型  つまり、C言語における型は を表す  アドレスのみを保存するポインタ変数は、 指し示す先の型の情報が無いとメモリに アクセスできない! 41 メモリ上のデータの格納方式 メモリ上のデータの読込方式
  42. 42. 普通の変数と配列の(メモリ上の)違い 普通の変数  別個に変数を定義 – int a, b, c;  各変数が連続した領域に 置かれるとは限らない 配列  配列で定義 – int a[3];  各要素は連続した領域に 必ず置かれる。 42
  43. 43. 図解 43  int a, b, c; a c b  int a[3]; a[0] a[1] a[2] 必ず連続 連続とは限らない = 1byte
  44. 44. ここまでのまとめ  ビット列が同じでも、格納方法や型が違えば違う値! 44 0 x C 0 F 8 0 0 0 0 3237478400 63680 -7.75 -1057488896 0.0
  45. 45. ここまでのまとめ  配列は連続した領域に置かれる 45 a[0] a[1] a[2] 必ず連続 int a[3]; = 1byte
  46. 46. 配列のアクセスの原理を理解する 連続した領域+ポインタ=ランダムアクセス 46
  47. 47. 覚えるべき文法(1)  配列名だけを書いたとき、 – sizeofや&の対象ならば、配列全体を意味 – それ以外ならば、配列の先頭要素のアドレス 47 int a[5] = {1, 2, 3, 4, 5}; printf(“%un”, sizeof(a)); someFunction(&a); int (*p)[5] = &a; int *p1 = a; int *p2 = a + 1; someFunction(a); printf(“%pn”, a + 1) 配列全体 配列の先頭要素へのアドレス
  48. 48. 覚えるべき文法(2)  配列名(sizeofや&の対象の場合を除く)を書いたと きに得られるアドレスの型は、(配列要素の型)へ のポインタ型。 48 int a[4]; なら、aの型は、int * float a[2]; なら、aの型は、float * char a[6]; なら、aの型は、char *
  49. 49. 覚えるべき文法(3)  配列の糖衣構文(シンタックスシュガー) 49 array[i] = *(array + i)  この2つの表記は完全に同じ意味  配列の宣言の時の[]は別物。 – この[]は、配列を使うときの[]。
  50. 50. 配列アクセスを理解 50  int a[3] = {1, 2, 3}; a[0] a[1] 必ず連続 1 2 a[2] 3 = 1byte
  51. 51. 配列アクセスを理解  a[2] 51 a[0] a[1] 必ず連続 1 2 a[2] 3 = 1byte
  52. 52. 配列アクセスを理解  *(a + 2) 52 a[0] a[1] 必ず連続 1 2 a[2] 3 = 1byte
  53. 53. 配列アクセスを理解  *(a + 2) 53 a[0] a[1] 必ず連続 1 2 int * 幅はint a int a[2] 3 = 1byte
  54. 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. 55. 配列アクセスを理解  *(a + 2) 55 a[0] a[1] 1 2 a + 2 a[2] 3 int この領域をint型で 読み取り ⇒3が読みだされる = 1byte
  56. 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
  57. 57. 動的配列 (実行時に要素数を指定できる配列)  #include <stdlib.h>をして使える、malloc – memory allocation (メモリ確保)  malloc(確保したいバイト数); – とすると、連続した確保したいバイト数分の 領域が確保される。 – 返り値は、確保した領域の先頭アドレス (指している型の情報無し。つまりvoid *) – 仕方がないので返り値はキャストして使う 57
  58. 58. 動的配列(図解) 58  malloc(8); とりあえず8byte分だけ確保 返り値(アドレス) = 1byte
  59. 59. 動的配列(図解) 59  int *p = (int *)malloc(8); 返り値をpへ p p[0] p[1] 配列の記法で 領域を扱える = 1byte
  60. 60. 動的配列(図解) 60  short int *p = (short int *)malloc(8); 返り値をpへ p p[0] p[1] 配列の記法で 領域を扱える p[2] p[3] = 1byte
  61. 61. 動的配列  環境によってintやshort intなどの型の大き さは変わる  引数には sizeof(型)*欲しい要素数を入れる 61 int *p = (int *)malloc(sizeof(int) * 2); 呪文(?)の完成!
  62. 62. 配列の配列  二次元配列の正体は、配列の配列 – つまり、配列の要素として配列を含んでいる  char a[2][3]; のとき、aはchar型の配列(要 素数3)を要素とする、要素数2の配列。  aのアドレスの型は、 char型の配列(要素 数3)へのポインタ型 62
  63. 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
  64. 64. 配列の配列(図解) 64  *(a+1)はchar [3](配列)だが、次に+2という演算が控えているため 先頭要素へのアドレスに読み替えられる a+1 1 2 3 4 5 6 *(a + 1)…char [3](配列) *(a + 1)…char * (先頭要素へのポインタ型) *(a+1) 1 2 3 4 5 6 = 1byte
  65. 65. 配列の配列(図解) 65  a[1][2] *(a+1)…char * 1 2 3 4 5 6 *(a+1)+2…char *  a[1][2] = *(*(a + 1) + 2) ⇒6が読みだされる = 1byte
  66. 66. 配列へのポインタ  途中で登場した、「char型の配列」へのポ インタの定義がこちら。  間違えても、char *p[5]; と勘違いしてはい けない。 – こちらは、char *が5つ格納されている配列 66 char (*名前)[配列の要素数]; (例) char a[5]; を指すポインタは、char (*p)[5]; char (*p)[5] = a;
  67. 67. ここまでのまとめ  配列の要素は、連続した領域に置かれる  配列名は、(基本的に)先頭要素へのアドレ ス – このアドレスの型は、配列の要素の型へのポイン タ型  このアドレスをポインタ演算したり、ポイン タ変数に入れたりして配列っぽく使用。 – このとき、*(array + i) = array[i]である。  二次元配列は、配列の配列であり、ポインタ 演算の規則をうまく使って要素にアクセスし ている。 67
  68. 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. 理解度チェック(補足)  バイトオーダーはリトルエンディアンとします。 次のア~カの行で表示される数値は? 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. 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
  71. 71. 理解度チェック(ヒント)  迷ったらメモリの図を書こう! 71 0x?? 0x?? 0x?? 0x?? 0x?? 0x?? 0x?? 0x?? 0x?? 0x?? 0x?? 0x?? arr[0] arr[1] arr[2] = 1byte  バイトオーダーに注意。  arrだけでなく、p1, p2, p3もメモリ上に確保 される
  72. 72. 理解度チェック(答え)  ア: 12  イ: 4  ウ: 0x89abcdef  エ: 0x45  オ: -7.75  カ: 0xcdef0123 72
  73. 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
  74. 74. 理解度チェック(解説:ア)  printf(“%un”, sizeof(arr)); 74 0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0 arr[0] arr[1] arr[2] = 1byte  sizeof(配列)は配列全体の大きさをバイト単位で返す  arrは全体で12byteの配列 arr (12byte)
  75. 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]のアドレスが入る
  76. 76. 理解度チェック(解説:イ②)  printf(“%un”, sizeof(p2)); 76 0x67 0x45 0x23 0x01 0xef 0xcd 0xab 0x89 0x00 0x00 0xf8 0xc0 arr[0] arr[1] arr[2] = 1byte p2(ポインタ変数)の領域(4byte)を返す 4byte p2
  77. 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. 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. 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. 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. 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. 理解度チェック(解説:オ③) 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. 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. 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. 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で読み取る
  86. 86. 関数の引数にポインタを渡す 間接的なアクセス方法 86
  87. 87. 関数にポインタを渡す  ここでは原理のみ。表記の問題等は後で 87 void someFunc(int *p) { printf(“%dn”, *p); } int main(void) { int value = 3; int *pValue = &value; someFunc(pValue); return 0; } 何が起こっているのか?
  88. 88. メモリマップ  メモリは、使用用途によって領域が分かれている。  静的領域…プログラムの開始から終了まで読書可  定数領域…絶対に書き換えない。プログラムの開始 から終了まで使える。読込のみ可。  スタック領域… 関数ごとの領域。その関数が実行中 の間使える。読書可。  ヒープ領域…mallocなどで使用。解放するまで読書可 88
  89. 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) メモリ
  90. 90. メモリマップ(図解) 90 global 静的 “Hellon” “%d,%dn” 定数 a b スタック(func2) スタック(func1) スタック(main) メモリ ずっと使える。 書き換え可 ずっと使える。 書き換え不可 関数が始まってから 終わるまで使える。 書き換え可
  91. 91. 関数に値を渡す  関数に値を渡すと、その値は渡した先に コピーされる – ポインタの値だろうがコピーされます。 91 void someFunc(int *p) { printf(“%dn”, *p); } int main(void) { int value = 3; int *pValue = &value; someFunc(pValue); return 0; } value; pValue; p(pValueの値を コピー) スタック( main ) スタック( someFunc )
  92. 92. 関数に値を渡す  渡したものがポインタだと、関数外の領域 にアクセスできるようになる。 92 void someFunc(int *p) { printf(“%dn”, *p); } int main(void) { int value = 3; int *pValue = &value; someFunc(pValue); return 0; } value; pValue; p(pValueの値を コピー) ア ク セ ス スタック( main ) スタック( someFunc )
  93. 93. イメージ  webにアップロードした写真のURLを友達 に教える – URL自身はコピーされている – 写真はコピーされない – 友達も写真にアクセスできる。 93 http://xxx.com/yyy.jpg http://xxx.com/yyy.jpg アクセス アクセス
  94. 94. ダメな参照  呼び出し元と呼び出し先の関数で値をやり 取りする際の注意  呼び出し先関数内の領域を指すポインタを 呼び出し元で使ってはいけない!! – 呼び出し元で使うときには、呼び出し先の関 数の実行は終わっている – メモリマップ的にアウト。 – どうなるかわからない(動作未定義) 94
  95. 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 使えないアドレス
  96. 96. ダメな参照(イメージ) 96  webにアップロードした写真のURLを友達 に教える  何故か教えた瞬間にうp主が写真を削除 ①http://xxx.com/yyy.jpg http://xxx.com/yyy.jpg ②削除 ③アクセス できない 404 NotFound
  97. 97. 良い参照  関数内で作った値へのポインタを返したいときは、 その領域を動的確保します。  動的確保された領域は、ヒープ領域に入るため、 明示的に開放しない限りメモリ上に残ります。 97 int *someFunc() { int *p = (int *)malloc(sizeof(int)); *p = 3; return p; } int main(void) { int *p = someFunc(); printf(“%dn”, *p); free(p); return 0; } ※mallocした領域は、 使い終わったら必ずfreeしましょう。
  98. 98. 関数にポインタを渡すメリット 98 1 コピーコスト削減 アドレスしかコピーしないので、 コピーする量・時間が減る 2 データの一貫性保持 データを更新すればその変更が向こうにも伝わる 3 複数の値を返す関数 複数のアドレスを渡しておけば、 複数の値を返す関数のようなものを作れる。
  99. 99. ここまでのまとめ  関数にポインタを渡すことができる – 渡された値はポインタであろうとコピーされる – 呼び出し先から呼び出し元の変数を参照できる  呼び出し元から呼び出し先の変数の参照を するときは注意。 – 動的確保した領域へのポインタを返せばOK  コピーコスト削減、データの一貫性を保つ、 複数の値を返せるなどの利点がある。 99
  100. 100. 文字列 文字の配列 100
  101. 101. 文字と文字列 文字  通常、‘A’や’b’とかをchar 型変数に格納。 – 実際に格納されるのは文字 に対するアスキーコード (数値)  1文字のみ表す 文字列  「文字」が配列になった もの – char str[3] = {‘a’, ‘b’, ‘0’};  文字配列の最後に番兵と して’0’を入れる – 文字列の終端が分かる 101
  102. 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. 文字列(図解) 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
  104. 104. 文字列とポインタ  配列の先頭をポインタに格納すれば、 ポインタで文字列を扱うこともできる 104 char str[3] = {‘a’, ‘b’, ‘0’}; char *str_p = str; printf(“%s”, str_p); str_p = str ‘a’ ‘b’ ‘0’ str_pは指している場所を 自由に場所を変更できる strの場所の変更は不可能 (strはコンパイル時定数)⇔= 1byte
  105. 105. ややこしい表記 105 char str[3] = {‘a’, ‘b’, ‘0’}; char str[] = {‘a’, ‘b’, ‘0’}; char str[] = “ab”; 文字列のために 用意された文法 (シンタックス シュガー) 配列のルール
  106. 106. ややこしい表記 106 char str[] = “ab”; char *str = “ab”;
  107. 107. 図解  char str[] = “ab”;  char *str = “ab”; 107 ローカル‘a’ ‘b’ ‘0’ str(コンパイル時定数) 定数‘a’ ‘b’ ‘0’ str ローカル = 1byte
  108. 108. char *の配列とcharの2次元配列 char *の配列  char *a[] = {“ab”, “cde”};  スタックに各要素(ポインタ) – ポインタの指す場所は変更可能  定数域にデータ – ポインタの指すデータは変更不可能  領域を無駄なく利用 char の二次元配列  char a[2][4] = {“ab”, “cde”};  スタックにデータ – データは書き換え可能 – aはスタック領域の配列 {“ab”, “cde”}の先頭を指すア ドレス定数  無駄な領域が発生 108 目次方式 表方式
  109. 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. 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
  111. 111. char *の配列とcharの2次元配列(共通点)  共通点は、どちらもa[i]がchar *となること。 – しかもそのポインタは文字列の先頭を指す – 両方とも、文字列の配列として利用できる  さらに、a[i][j]と書くと、i個目の文字列のj 番目の文字を表せる 111 使い方はほぼ同じだが、メモリの構造は違う!
  112. 112. ここまでのまとめ  文字列はchar の配列である。  文字列の最後に番兵として’0’を置く  char str[] = {‘a’, ‘b’, ‘0’} は char str[] = “ab”; と同じ意味 – これもシンタックスシュガー – 文字列のために用意された文法  char str[][hoge];とchar *str[]は同じように使う ことができるが、メモリの構造は異なる 112
  113. 113. 構造体へのポインタ 基本がわかっていれば怖くないけど、応用例が多い 113
  114. 114. 構造体  いろいろな型を並べて新しい型を作れる 114 typedef struct { char name[10]; int age; } person;  person型はchar[10]とintを並べた型
  115. 115. 構造体  構造体を使う 115 typedef struct { char name[10]; int age; } person; … person john = {“John”, 21}; printf(“%s %dn”, john.name, john.age);  構造体の中の名前でアクセスできる
  116. 116. メモリ上の構造体  構造体の宣言は、メモリ上のレイアウトを決めている 116 typedef struct { char name[10]; int age; } person; name age 0 10index = 1byte
  117. 117. メモリ上の構造体  メンバ変数とindex(相対位置)を紐づけている 117 typedef struct { char name[10]; //開始indexは0 int age; //開始indexは10 } person; name age 0 10index = 1byte
  118. 118. アライメント  構造体は配列とは違い、必ずしもその中身が連続 するとは限らない  OS・CPUが扱いやすい単位に揃えられてデータの レイアウトが決まることがある  扱いやすい単位をアライメントという。  charのアライメントは1byte、intのアライメントは 4byteなど。(環境依存) 118 name age 0 12index 4の倍数 データがどのようにアラインされても、 メンバ変数でアクセスすれば問題ない = 1byte
  119. 119. 構造体へのsizeof  レイアウトの結果定まった、構造体全体のサイズ を返す。 – 正確には、この構造体を配列に入れて並べたときの 隣り合う2要素間のアドレスの差をbyteで返す。  アライメントの関係で、sizeof(構造体)がsizeof(メ ンバ変数)の総和と等しくなるとは限らない 119 0 10index sizeof(person) = 14 = 1byte
  120. 120. 構造体へのsizeof 120 sizeof(person) = 16 0 12index  レイアウトの結果定まった、構造体全体のサイズ を返す。 – 正確には、この構造体を配列に入れて並べたときの 隣り合う2要素間のアドレスの差をbyteで返す。  アライメントの関係で、sizeof(構造体)がsizeof(メ ンバ変数)の総和と等しくなるとは限らない = 1byte
  121. 121. 構造体へのポインタ  構造体へのポインタを定義して使う  person *p = &john;  printf(“%s %dn”, p->name, p->age);  ポインタからメンバ変数にアクセスする時 は.ではなく->を使う。 121 a->b = (*a).b これもシンタックスシュガー
  122. 122. 構造体を関数に渡す  構造体はデータの塊。これを関数に渡すと データがコピーされる。  巨大な構造体だとコピーするのに 時間とメモリ領域が消費される。  ポインタを渡せば、一瞬でデータを渡せる。  ただし、データを書き換えると その変更は呼び出し元に伝わる。 122
  123. 123. 構造体を関数に渡す 123 John person *p = &john; p 呼び出し元関数 func関数 120 = 1byte
  124. 124. 構造体を関数に渡す 124 John person *p = &john; p 呼び出し元関数 func関数 func(p); ↑ 4バイトだけコピー 120 p = 1byte
  125. 125. 構造体を関数に渡す 125 John person *p = &john; p 呼び出し元関数 func関数 func(p); p->age = 14; 12 書き換え 0 ageの indexは12 p = 1byte
  126. 126. 構造体へのポインタまとめ  複数の型を融合して新しい型を定義できる  構造体の宣言はメモリ上のレイアウトを決 めている  メンバ変数がメモリ上の相対位置を記憶  アライメントに注意。  巨大な構造体でも、ポインタを経由して他 の関数に素早く渡すことができる。 126
  127. 127. 関数ポインタ とにかく記法が難しい。使い方は難しくない。 関数を持ち運べる便利な変数! 127
  128. 128. まずは関数ポインタ 128  関数名も実はアドレス  アドレスの型として実際に使うときの情報 つまり、「引数の型・順番」「返り値の型」 が分かれば良い。 char func(char *p, int n); 返り値の型と 引数の型・順番が同じ なら同じ型
  129. 129. 関数ポインタ  関数を格納するためのポインタpfuncの宣言  関数を持ち運べる便利な変数と考えると良い 129 char (*pfunc)(char *, int);  何が変わったのか? – かっこを左にもつけた • ポインタであるということを示すため – 引数の名前は削除 • 型が重要だから
  130. 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の宣言 どちらのアドレ スでも代入可 呼び出しは いつも通り
  131. 131. 関数ポインタの配列 131 char (*pfunc[3])(char *, int);  関数を格納するためのポインタ配列の宣言  何が変わったのか – 変数名の右に配列を表す[]がついた
  132. 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の宣言 どちらのアドレ スでも代入可
  133. 133. 関数ポインタを引数に取る  関数ポインタは関数の引数にもできます – すごく記述が複雑になるが、落ち着いてみれ ばOK  関数の引数として関数を与えられる – 関数内から、与えられた関数を呼べる – コールバック関数 133 int func(int n, char (*pfunc)(char *, int)) { … }
  134. 134. 関数ポインタを返り値にする 134 char (*func(int n))(char *, int) { … }  返り値なのに、左右から挟み込む新発想! – C言語の規格なので仕方ない。  後で処理してほしい関数などを返せる
  135. 135. 目次 135 記法 使い方 原理
  136. 136. 表記は同じだけど意味が違う 混同しやすいので注意 136
  137. 137. *  *は3つの意味を持つ。 137 1 ポインタの宣言の* int *p; など、「自分はポインタである」ことを 示すための*。関数の引数の*もこっちの意味。 2 ポインタ参照の* ポインタ(またはアドレス)の前につける*。 そのアドレスの値を参照する。 3 掛け算の* 2 * 3とか。
  138. 138. []  []も3種類。 138 1 配列の定義の[] 配列を定義するときに使う[]。 配列の要素数を表す。 2 配列の要素の参照の[] 配列の要素を参照するときに使う[]。 *(array + i) = array[i]と言い換えられる。 3 関数の仮引数の[] これは、関数の引数にポインタ渡しするとき 見た目だけでも配列にするための[]。後述。
  139. 139. “”  “”は2種類。 139 1 文字列リテラルの”” 定数領域に格納される文字のデータを表す”” ほとんどの””はこちらに分類される。 型としては、char []となる。 2 charの配列を初期化するための”” char str[] = {‘a’, ‘b’, ‘0’}; と書くのが面倒なので char str[] = “ab”; と書けるようにした。 上の””とは関係はない。
  140. 140. 配列名 140 1 配列の先頭要素へのポインタ ほとんどの配列名はこちらに分類される。 type型の配列の場合、配列名を書くとtype *型となる 2 配列全体 sizeof(配列)または、&配列で使われたとき。 前者は配列全体の大きさ(byte)、 後者は配列全体に対するポインタを計算する。  配列名は2種類
  141. 141. 表記は違うけど意味が同じ こっちも混同しやすいので注意 141
  142. 142. 関数の引数  関数引数の宣言 142 void func(int *p) { } void func(int p[]) { }
  143. 143. 関数の引数  どうして?  ⇒関数に渡すときに、元が配列であるという情報 はどのみち消える。呼び出し元が配列であったこ とを明示したい! 143 void func(int *p) { …p[i]… } void func(int p[]) { …p[i]… }  実際に意味を考えるときは、ポインタ記法 に戻して考える。  Q. sizeof(p)は? ⇒ 4
  144. 144. 関数の引数  関数引数の宣言 144 void func(int **p) { } void func(int *p[]) { } Q. sizeof(p)は? ⇒ 4
  145. 145. 関数の引数  関数引数の宣言 145 void func(int (*p)[3]) { } void func(int p[][3]) { } Q. sizeof(p)は? ⇒ 4
  146. 146. 配列のルール 146 a[i] *(a + i) もちろん、 参照時の[] (式の中の[])
  147. 147. 文字の配列 147 char str[3] = {‘a’, ‘b’, ‘0’}; char str[] = {‘a’, ‘b’, ‘0’}; char str[] = “ab”; 文字列のた めに用意さ れた文法 配列のルール
  148. 148. const  int i = 1; // 普通の変数  const int ic = 1; // 書換不可能  const int *p1 = &ic; // p1は書換可能。p1 の指す先は書換不可能  int * const p2 = &i; // p2は書換不可能。p2 の指す先は書換可能  const int * const p3 = &ic; // p3もp3の指 す先も書換不可能 148
  149. 149. 複雑な記法 英訳→和訳 149
  150. 150. 複雑な記法  char (*p)[3];  char (*func)(char *, int);  char (*func(int n))(char *, int) {  …  複雑な記法が多い。  どうにかして解読できないか? 150
  151. 151. まず英訳 151 1 中心の名前を見つける 変数や変数の名前。 真ん中にあることが多い。 見つかったら(名前) is をつける。 2 )に突き当たるまで右に読む この時、[にあたったら array[要素数] of をつける (にあたったらfunction(引数リスト) returning をつける。 3 突き当たった)に対応する(まで戻る このとき*にあたったらpointer toをつける 終わったら②へ
  152. 152. char (*p)[3] 152 1 中心の名前を見つける p p is char (*p)[3]; なう
  153. 153. char (*p)[3] 153 2 )に突き当たるまで右に読む すでに突き当たっている char (*p)[3]; p is なう
  154. 154. char (*p)[3] 154 3 突き当たった)に対応する(まで戻る 戻るときに、*に当たるので、 pointer to をつける char (*p)[3]; p is pointer to なう
  155. 155. char (*p)[3] 155 2 )に突き当たるまで右に読む [が見える。array[3] of をつける char (*p)[3]; p is pointer to array[3] of なう
  156. 156. char (*p)[3] 156 3 突き当たった)に対応する(まで戻る 一番最後に)がいると思って、 まだ読んでいないところまで左に戻ると char. char (*p)[3]; p is pointer to array[3] of char p はchar[3]配列へのポインタ なう
  157. 157. char (*(*p)())[5]; 157 1 中心の名前を見つける p p is char (*(*p)())[5]; なう
  158. 158. char (*(*p)())[5]; 158 p is char (*(*p)())[5]; なう 2 )に突き当たるまで右に読む すでに突き当たっている
  159. 159. char (*(*p)())[5]; 159 p is pointer to char (*(*p)())[5]; なう 3 突き当たった)に対応する(まで戻る 戻るときに、*に当たるので、 pointer to をつける
  160. 160. char (*(*p)())[5]; 160 p is pointer to function(void) returning char (*(*p)())[5]; なう 2 )に突き当たるまで右に読む 途中で(に出会う。 function(void) returningをつけて次へ
  161. 161. char (*(*p)())[5]; 161 p is pointer to function(void) returning char (*(*p)())[5]; なう 2 )に突き当たるまで右に読む 突き当たった
  162. 162. char (*(*p)())[5]; 162 p is pointer to function(void) returning pointer to char (*(*p)())[5]; なう 3 突き当たった)に対応する(まで戻る 戻るときに、*に当たるので、 pointer to をつける
  163. 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. 164. char (*(*p)())[5]; 164 p is pointer to function(void) returning pointer to array[5] of char. char (*(*p)())[5]; なう 3 突き当たった)に対応する(まで戻る 一番最後に)がいると思って、 まだ読んでいないところまで左に戻ると char.
  165. 165. char (*(*p)())[5]; 165 p is pointer to function(void) returning pointer to array[5] of char. char (*(*p)())[5]; p は「char[5]配列へのポインタ」 を返す関数へのポインタ
  166. 166. 目次 166 記法 使い方 原理
  167. 167. ポインタの使い方(1)  代表的な使い方 167 1 関数間で構造体を渡す データの塊を渡す。 ポインタを通してデータにアクセス 2 構造体をつなげる 構造体が他の構造体を知ることが できる。リスト構造とかもこれ 3 文字列を扱う 先頭アドレスだけで文字列を表す
  168. 168. ポインタの使い方(2)  その他の使い方 168 1 動的配列 動的に確保した領域の先頭を指すポインタ C++では配列だけではなく、 オブジェクトも動的にとれる 2 コールバック処理の実現 関数内の一部処理を外部に決めてもらう 3 メモリをいじる char *を使えば1バイトずつ読み書き可能
  169. 169. 関数間で構造体を渡す  構造体(データの塊)を渡すには、アドレ スだけ渡せば十分なことが多い。 169 typedef struct { char datatype; int data[10000]; } Bigdata; void dataProc(Bigdata *p) { p->data[hogehoge]…… } int main(void) { Bigdata p; …… dataProc(&p); return 0; } main dataProc 10000*sizeof(int)+1 byte 4 byte コピーの時間と容量を削減 = 1byte …
  170. 170. 構造体をつなげる  リスト構造など。定義中の構造体へのポイン タを、その構造体のメンバ変数に追加する。 170 struct node_{ int value; struct node_ *p; }; typedef struct node_ node; value p value p value p 要素同士のつながりをpで表現 要素の追加・削除の計算量が (配列と比べて)少ない = 1byte
  171. 171. 文字列を扱う  文字列は文字の配列(の終端に’0’が付加さ れたもの)  文字列の先頭文字へのアドレスを捕まえてお けば、文字列全体を持っていることになる 171 void func(char *p) { (pを使った処理) } int main(void) { char str[5] = “test”; char *p = “hello”; func(str); func(p); … } ‘t’ ‘e’ ‘s’ ‘t’ ‘h’ ‘e’ ‘l’ ‘l’ ‘0’ ‘o’ ‘0’ p func main 定数 = 1byte
  172. 172. 動的配列  呼び出し元で配列の大きさを決定できる  型宣言・キャストの仕方によってその領域を使う ときの型を決められる 172 int *p = (int *)malloc(sizeof(int) * 2); p p[0] p[1] char (*p)[3] = (char (*)[3])malloc(sizeof(char) * 6); p p[0] p[1] p[0][0] p[0][1] p[0][2] p[1][0] p[1][1] p[1][2] char [2][3]の2次元配列を動的確保したい場合: int [2] の配列を動的確保したい場合: = 1byte
  173. 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で行う。
  174. 174. メモリをいじる  char *は1byte単位でのメモリ操作が可能  組み込み等のプログラミングでは良く出てくる 174 char *p = (char *)0x12345678; // 整数をアドレスに変換 *p = 0xff; …  OSなどの実行環境によってアクセスでき る領域が限られることがあるので注意
  175. 175. 参考文献  『 C言語ポインタ完全制覇』,前橋 和弥, 技 術評論社, 2001  『プログラミング言語C 第2版 ANSI規格準 拠』, B.W. カーニハン (著), D.M. リッチー (著), 石田 晴久 (翻訳), 共立出版, 1989  ポインタ虎の巻 http://www.nurs.or.jp/~sug/soft/tora/ 175

×