学生さんから「ポインタの先には何があるの?」との質問があった。
私が「そのポインタの型のデータ」と答えると、さらに「ポインタはメモリの場所。でもメモリには int や char や double といった色んなデータがある。そんな色々なデータの中からデータを取り出すんだから、そこにはどんなデータが入っているのか判らないとデータを取り出せないんじゃ?」と疑問をぶつけてきた。
なるほど、本当の疑問点が見えてきた。
最近のPython等の動的型付け言語の場合
# ポインタの質問だから、C言語の場合を答えればいいんだけど…
最近の Python , PHP といった変数が型を持たない「動的型付け言語」は、まさに質問の通り。データを取り出すためには、型の情報が必要。こういう言語は、基本型以外のデータはすべて参照型(要はポインタ)なので、変数の指し示す先には型情報とそのデータがペアで保存されているので、その型情報をみてデータを取り出している。
C言語の場合(静的型付け言語)
C言語では、ポインタは単なるメモリの場所を表すだけ。ポインタの先にはデータがある。(だからデータしかないって!)
メモリからデータを読み出すときに、int 4byte で取り出すのか、 double 8byte で取り出すかどうやってわかるの?と思うかもしれないけど、そのポインタの変数がどういう型へのポインタで定義されているかプログラムを読めばわかる。それに従って取り出せばいい。こういう言語は「静的型付け言語」という。
となると「じゃあ int のデータを char として読めるの?」と思うかもしれないけど「読めるよ!」
#include <stdio.h> int main() { // 型を偽って参照するのは間違いの元なので型のチェックは厳格。 // だから 以下の様なヤバイことをする時は、型キャスト で // だますことが定番。 int x = 0x41424344 ; char* p = (char*)( &x ) ; // int型の場所をchar型にする printf( "%c\n" , *p ) ; // int型は4バイト、次のアドレスは? int y[] = { 0x11223344 , 0x12345678 , } ; printf( "%p %p\n" , y , y + 1 ) ; int *r = y + 1 ; printf( "%04d\n" , *r ) ; // 12345678 が表示 // intの1byteとなりをintとして読める? int *q = (int*)((char*)( &y ) + 1) ; printf( "%04x\n" , *q ) ; // 処理系によってはメモリエラー // ポインタは番地を表す数値だよね? // 0x100番地のデータは読める? int* s = (int*)0x100 ; printf( "%d\n" , *s ) ; // Segmentation Fault. return 0 ; }
さて、上記のプログラムをみてどう思った?
C言語って自由奔放で、やばくね? — ポインタなんか使えるからだよね、そう思うんなら Java 使え。ポインタなんか使えないから。
でも型宣言が面倒なんだよね — Python, Ruby などの動的型付けな言語使え。
でも変数参照でいちいち型情報しらべる言語って遅くね? — あるよ。「型推論」。型を明記しなくても、プログラムの文脈から型を推論してくれる静的型付け言語。Go , Swift , Kotlin…といった、今 流行りのプログラム言語がソレ。最新のJavaやC++も型推論機能が使えるようになってるよ。んで、今話題の中学生が作ったプログラム言語 Blawn も、型推論の言語!すげーな。