本記事について
C言語のプログラミングしていると、どうしても「何故ダブルポインタを使用しなくてはいけないのか?」「関数の引数にダブルポインタを使用しなくてはいけない理由は?」といった疑問が出てくるので、一旦整理してみた。
実行環境
・macOS Ventura 13.0.1
・Homebrew GCC 12.2.0
・lldb-1400.0.38.13
・MacBook Pro 13インチ M2
サンプルソース
下記のサンプルソースをもとに、LLDBデバッガでデバッグしてみた。
1 #include <stdio.h>
2
3 static int data = 1000;
4
5 void getPtr(int **ptr){
6 *ptr = &data;
7 }
8
9 int main(void){
10
11 int *p;
12
13 getPtr(&p);
14
15 printf("%d\n", *p);
16
17 return 0;
18 }
デバッグ結果
まずは、下記のように関数main()および関数getPtr()にブレイクポイントを仕掛けて、実行してみた。
(lldb) br set -n main
Breakpoint 1: where = main`main + 12 at main.c:13:2, address = 0x0000000100003f44
(lldb) br set -n getPtr
Breakpoint 2: where = main`getPtr + 8 at main.c:6:7, address = 0x0000000100003f1c
(lldb) r
すると、関数getPtr()が実行される直前でストップしたので、ここで関数main()内の変数pとグローバル型変数dataの中身とアドレス値(=メモリのどの番地に割り当てられているか)を調べてみた。
Process 21984 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100003f44 main`main at main.c:13:2
10
11 int *p;
12
-> 13 getPtr(&p);
14
15 printf("%d\n", *p);
16
Target 0: (main) stopped.
(lldb) po &p
0x000000016fdff4b8
(lldb) po p
0xc206800189d5fe3c
(lldb) po &data
0x0000000100008000
(lldb) po data
1000
表にすると、こんな感じ。
グローバルint型変数dataには0x0000000100008000番地のメモリが、
関数main()内のローカルint型変数pには0x000000016fdff4b8番地のメモリが割り当てられているのがわかります。
変数 | アドレス値 | そのアドレス値に格納されている値 |
---|---|---|
data | 0x0000000100008000 | 1000 |
p | 0x000000016fdff4a8 | 0xf7528001a8f07e3c |
このまま、「c(=Continue)」を入力すると、下記の箇所でストップします。
ここで関数getPtr()内のローカルint型引数ptrの中身とアドレス値を調べてみた。
(lldb) c
Process 1389 resuming
Process 1389 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
frame #0: 0x0000000100003f1c main`getPtr(ptr=0x000000016fdff4a8) at main.c:6:7
3 static int data = 1000;
4
5 void getPtr(int **ptr){
-> 6 *ptr = &data;
7 }
8
9 int main(void){
Target 0: (main) stopped.
(lldb) po &ptr
0x000000016fdff478
(lldb) po ptr
0x000000016fdff4a8
(lldb) po *ptr
0xf7528001a8f07e3c
表にすると、こんな感じ。
こうしてみると、関数getPtr()の引数ptrに関数main()内の変数pのアドレス値が入っていることがわかります。
変数 | アドレス値 | そのアドレス値に格納されている値 |
---|---|---|
ptr | 0x000000016fdff478 | 0x000000016fdff4a8 (=関数main()内の変数pのアドレス値) |
*ptr (=関数main()内の変数p) |
0x000000016fdff4a8 (=関数main()内の変数pのアドレス値) |
0xf7528001a8f07e3c |
このまま、「n(=Next)」を入力すると、下記の箇所でストップします。
再度、引数ptrの中身を確認してみると…
(lldb) n
Process 1389 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x0000000100003f2c main`getPtr(ptr=0x000000016fdff4a8) at main.c:7:1
4
5 void getPtr(int **ptr){
6 *ptr = &data;
-> 7 }
8
9 int main(void){
10
Target 0: (main) stopped.
(lldb) po ptr
0x000000016fdff4a8
(lldb) po *ptr
0x0000000100008000
(lldb) po **ptr
1000
ポインタ変数*ptrにグローバル変数dataのアドレス値が入っていることがわかります。
これによりダブルポインタ変数**ptrとグローバル変数dataのアドレス値が同一になったことがデバッグ上でもわかります。
変数 | アドレス値 | そのアドレス値に格納されている値 |
---|---|---|
ptr | 0x000000016fdff478 | 0x000000016fdff4a8 (=関数main()内の変数pのアドレス値) |
*ptr (=関数main()内の変数p) |
0x000000016fdff4a8 (=関数main()内の変数pのアドレス値) |
0x0000000100008000 (=グローバル変数dataのアドレス値) |
**ptr (=関数main()内のポインタ変数*p) (=グローバル変数data) |
0x0000000100008000 (=グローバル変数dataのアドレス値) |
1000 |
最終的に関数main()に戻った際、ポインタ変数pは以下のように変わっております。
(lldb) n
Process 1389 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x0000000100003f4c main`main at main.c:15:2
12
13 getPtr(&p);
14
-> 15 printf("%d\n", *p);
16
17 return 0;
18 }
Target 0: (main) stopped.
(lldb) po &p
0x000000016fdff4a8
(lldb) po p
0x0000000100008000
(lldb) po *p
1000
変数 | アドレス値 | そのアドレス値に格納されている値 |
---|---|---|
data | 0x0000000100008000 | 1000 |
p (=関数main()内の変数p) |
0x000000016fdff4a8 (=関数main()内の変数pのアドレス値) |
0x0000000100008000 (=グローバル変数dataのアドレス値) |
*p (=関数main()内のポインタ変数*p) (=グローバル変数data) |
0x0000000100008000 (=グローバル変数dataのアドレス値) |
1000 |
よく間違えるパターン(ダブルポインタを使用しないケース)
ちなみに、下記のようなダブルポインタを使用しないソースでもデバッグしてみた。
1 #include <stdio.h>
2
3 static int data = 1000;
4
5 void getPtr(int *ptr){
6 ptr = &data;
7 }
8
9 int main(void){
10
11 int *p;
12
13 getPtr(p);
14
15 printf("%d\n", *p);
16
17 return 0;
18 }
lldbデバッガでデバッグしてみると、こんな感じ。
(lldb) br set -n main
Breakpoint 1: where = main`main + 12 at main.c:13:2, address = 0x0000000100003f44
(lldb) br set -n getPtr
Breakpoint 2: where = main`getPtr + 8 at main.c:6:6, address = 0x0000000100003f20
(lldb)
Breakpoint 3: where = main`getPtr + 8 at main.c:6:6, address = 0x0000000100003f20
(lldb) r
Process 1324 launched: '/Users/satoshi/CProgram/test20221108/main' (arm64)
Process 1324 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100003f44 main`main at main.c:13:2
10
11 int *p;
12
-> 13 getPtr(p);
14
15 printf("%d\n", *p);
16
Target 0: (main) stopped.
(lldb) po &p
0x000000016fdff4b8
(lldb) po p
0x77220001aac6fe3c
(lldb) po &data
0x0000000100008000
(lldb) po data
1000
変数 | アドレス値 | そのアドレス値に格納されている値 |
---|---|---|
data | 0x0000000100008000 | 1000 |
p | 0x000000016fdff4b8 | 0x77220001aac6fe3c |
このまま、関数getPtr()内の下記の箇所まで処理を進めると、関数getPtr()の引数ptrに関数main()内の変数pの中身が入っていることがわかります
(lldb) c
Process 1324 resuming
Process 1324 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1 3.1
frame #0: 0x0000000100003f20 main`getPtr(ptr=0x77220001aac6fe3c) at main.c:6:6
3 static int data = 1000;
4
5 void getPtr(int *ptr){
-> 6 ptr = &data;
7 }
8
9 int main(void){
Target 0: (main) stopped.
(lldb) po &ptr
0x000000016fdff488
(lldb) po ptr
0x77220001aac6fe3c
変数 | アドレス値 | そのアドレス値に格納されている値 |
---|---|---|
ptr | 0x000000016fdff488 | 0x77220001aac6fe3c (=関数main()内の変数pの中身) |
このまま、「n(=Next)」を入力すると、下記の箇所でストップします。
再度、引数ptrの中身を確認してみると…
(lldb) n
Process 1324 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x0000000100003f2c main`getPtr(ptr=0x0000000100008000) at main.c:7:1
4
5 void getPtr(int *ptr){
6 ptr = &data;
-> 7 }
8
9 int main(void){
10
Target 0: (main) stopped.
(lldb) po ptr
0x0000000100008000
(lldb) po *ptr
1000
関数getPtr()内の変数ptrの値がグローバル変数dataのアドレス値が変わっていることがわかります。
ただこれだと、関数main()内のポインタ変数*ptrにグローバル変数dataのアドレス値を割り当てられていないことがデバッグ上でもわかります。
変数 | アドレス値 | そのアドレス値に格納されている値 |
---|---|---|
ptr | 0x000000016fdff488 | 0x0000000100008000 (=グローバル変数dataのアドレス値) |
*ptr (=グローバル変数data) |
0x0000000100008000 (=グローバル変数dataのアドレス値) |
1000 |
(lldb) n
Process 1324 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x0000000100003f4c main`main at main.c:15:2
12
13 getPtr(p);
14
-> 15 printf("%d\n", *p);
16
17 return 0;
18 }
Target 0: (main) stopped.
(lldb) po p
0x77220001aac6fe3c
変数 | アドレス値 | そのアドレス値に格納されている値 |
---|---|---|
data | 0x0000000100008000 | 1000 |
p | 0x000000016fdff4b8 | 0x77220001aac6fe3c |
↑これだと、関数main()内のポインタ変数pの中身を変えられない…
結論
ダブルポインタを使用する最大の要因は、やはりコレですね…
呼び元の関数内のポインタ変数に割り当てるアドレスを変えたい場合は、ダブルポインタを使うべし!!
っちゅうことですね…