Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

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

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

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

  • Sé el primero en comentar

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

×