はじめに
なぜみんなポインタで躓くのか。
ポインタなんて簡単じゃなイカ。
というわけでこれでわかるといいなという感じで書いてみました。
はなしをしよう
まずは配列から
まず配列のおさらいからしましょう。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int array[10] = {1,2,3,4,5,6,7,8,9,10};
int index = 5;
printf("%d : %d\n", index, array[index]);
return EXIT_SUCCESS;
}
まずはこのコードのprintfで何が出力されるかを考えてみましょう。
正解は[5 : 5]と思った人はもう一回配列から勉強しなおしましょう。
もちろん正解は[5 : 6]ですよ。
さっさとポインタについて教えろ
アッハイ。さて貴方はポインタのなにがわからないんでしょうか?
参考書に乗っていたクソみたいな例題で頭がいたくなったのでしょうか?
それともそもそも概念が理解できずに頭を抱えたのでしょうか?
それとも理解に苦しむコンパイルエラーに嫌気がさしたのでしょうか?
それとも理解できない挙動で心が折れたのでしょうか?
上記に上げた項目に一つでも該当する方は以下の内容で理解できるかもしれません。
もしくはできないかもしれません。
そもそも概念がわからん
よくポインタを説明するときに例として棚や住所などと例えて説明がなされます。
実際にこの例えは実に的確です。ポインタはいうなれば場所を指すのです。
以下のような0x0001~0x0009とかかれた棚があるとします。
0x0001 | 0x0002 | 0x0003 |
0x0004 | 0x0005 | 0x0006 |
0x0007 | 0x0008 | 0x0009 |
あなたは今自分がポインタさんだとして0x0003の場所を指差してください。
この状態がポインタなのです。
ちょっとなにいってるかわからない?
わかりました。では以下のコードを見てください。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i;
int array[10] = {1,2,3,4,5,6,7,8,9,10};
int index = 5;
int *p = &array[5];
for (i = 0; i < 10; ++i)
{
printf("%2d : %p\n", i, &array[i]);
}
printf("array[%d] %p == %p\n", index, &array[index], p);
return EXIT_SUCCESS;
}
実行結果は以下のようになります。
0 : 0x7ffe33df4000
1 : 0x7ffe33df4004
2 : 0x7ffe33df4008
3 : 0x7ffe33df400c
4 : 0x7ffe33df4010
5 : 0x7ffe33df4014
6 : 0x7ffe33df4018
7 : 0x7ffe33df401c
8 : 0x7ffe33df4020
9 : 0x7ffe33df4024
array[5] 0x7ffe33df4014 == 0x7ffe33df4014
5 : 0x7ffe33df4014
デデーン!
つまりポインタとはアドレスを指しているだけなのです。
概念はわかったぞ!
さて問題はここからです。ポインタは場所を指しているということは
ポインタの中を書き換えると当然指している先もかわります。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i;
int array[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &array[5];
for (i = 0; i < 10; ++i)
{
printf("%2d : addr=%p : (value=%d)\n", i, &array[i], array[i]);
}
puts("---------");
// 先頭にアスタリスクをつけなければいけない!
*p = 999;
for (i = 0; i < 10; ++i)
{
printf("%2d : addr=%p : (value=%d)\n", i, &array[i], array[i]);
}
return EXIT_SUCCESS;
}
0 : addr=0x7fffe762cd20 : (value=1)
1 : addr=0x7fffe762cd24 : (value=2)
2 : addr=0x7fffe762cd28 : (value=3)
3 : addr=0x7fffe762cd2c : (value=4)
4 : addr=0x7fffe762cd30 : (value=5)
5 : addr=0x7fffe762cd34 : (value=6)
6 : addr=0x7fffe762cd38 : (value=7)
7 : addr=0x7fffe762cd3c : (value=8)
8 : addr=0x7fffe762cd40 : (value=9)
9 : addr=0x7fffe762cd44 : (value=10)
---------
0 : addr=0x7fffe762cd20 : (value=1)
1 : addr=0x7fffe762cd24 : (value=2)
2 : addr=0x7fffe762cd28 : (value=3)
3 : addr=0x7fffe762cd2c : (value=4)
4 : addr=0x7fffe762cd30 : (value=5)
5 : addr=0x7fffe762cd34 : (value=999)
6 : addr=0x7fffe762cd38 : (value=7)
7 : addr=0x7fffe762cd3c : (value=8)
8 : addr=0x7fffe762cd40 : (value=9)
9 : addr=0x7fffe762cd44 : (value=10)
** 5 : addr=0x7fffe762cd34 : (value=999)**
デデーン!!
ではアスタリスクをつけない場合はどうなるのでしょう?
最初にいったとおりポインタはアドレスを指します。
つまり。
*p = 999; を p = (int *)999;に書き換えてみましょう。
値に変化がないことを確認できると思います。
また*pの中身をみようとするとほぼ確実に「Segmentation fault」となるはずです。
またポインタはアドレスを指すので、以下のようなことも可能です。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(void)
{
int value = 10000;
/// value のアドレスをlong型の変数に代入。
int64_t addr = (int64_t)((int *)&value);
// addr変数内の値をポインタに変換
int *p = ((int *)(void *)addr);
*p = 1000;
printf("%d", value);
return EXIT_SUCCESS;
}
実行すると以下のようになります。
1000
0
このメリットは、ある意味がんばればlong型にいろいろ仕込んでおき、
それを実際に使うときに展開して使用するといったことが可能になります。
昔のCで書かれたオープンソースなコードでもみることができますね。
ただし、long型に保存したアドレス値が当然有効な値でなければ、落ちたり予期せぬ挙動を起こす原因になったりするので
できる限り使わないですむなら使わないほうがいいに決まっています。
一応サンプルコードを提示しますが、正直理解が足りないうちは混乱するので読み飛ばしていただいて結構です。
なお当時このようなコードを見た僕は、スゲーと素直に思いました。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
typedef enum addr_type_
{
addr_type_int64,
addr_type_struct_data,
addr_type_string,
}addr_type;
typedef struct data_t_
{
int value;
char string[256];
}data_t;
void output_value(addr_type type, int64_t value)
{
switch (type)
{
case addr_type_int64:
printf("int64 %ld\n", value);
break;
case addr_type_struct_data:
printf("value=%d srting=%s\n",
((data_t *)value)->value,
((data_t *)value)->string );
break;
case addr_type_string:
printf("string=%s\n",
(const char *)value);
break;
}
}
int main(void)
{
data_t data;
strcpy(data.string, "hello world");
data.value = 1919;
int64_t value = 114514;
const char *string = "黒塗りの高級車";
output_value(addr_type_int64, value);
output_value(addr_type_struct_data, (int64_t)&data);
output_value(addr_type_string, (int64_t)string);
return EXIT_SUCCESS;
}
実行結果
int64 114514
value=1919 srting=hello world
string=黒塗りの高級車
ポインタと配列の話
次回へ続く。