娘のためにその8:型[30分]

娘に読ませる以外の意図はなく、よって質問や指摘には対応していません。すなわちネットの浄化作用が働いていない不正確な内容になりますので、正しい情報を求める方は閲覧をご遠慮ください。公開しているのは、通信手段としての利便性のためです。

型(かた)。英語で type 。C言語には型がある。

int a = 0;

この int というのが型の一種で、整数型だ。つまり変数 a には整数を入れる、ということだ。

CPUの構造上、整数と実数(実際は無理数は表現できない)は扱いが異なるので、型も違う。実数は float だ。C言語で登場する型の表記は他の言語でも同じ意味で使用されることが多いので、すべて暗記することになるだろう。少しずつでも覚えていこう。

int : 整数

long : 64bit 整数

short : 16bit 整数

char : 8bit 整数

float : 32bit 浮動小数点数

double : 64bit 浮動小数点数

型ごとに、使用するバイト数が異なる。1byte=8bit (この関係はあらゆる環境で常に成立する。頭に叩き込むこと)なので、64bitなら64/8=8で8バイトだ。何バイト使って表現するかでいくつかの型に分かれるのがわかるだろう。なおint については特殊で、そのCPUが扱いやすいサイズに規定される。

ここで挙げた7つの型は、整数と少数の2種類に分類できる(float と double が少数)。整数の場合、例えば 3 / 2 という除算を実行した結果が 1.5 ではなく 1 になってしまう問題を含む。少数にしておけば 1.5 になるが、何もかも少数にするわけにはいかない。少数にも問題があるからだ。歴史的には、CPUは整数を扱う機能がまず搭載され、少数を扱えるようになったのは一定の進化を遂げたあとの話だ。

さて、いろいろな型があるのはいいとして、異なる型に代入したい場合があるとしよう。以下のプログラムを見てみよう。

long a = 0;
short b = a;

long で宣言した a にゼロを代入し、その a を short で宣言した b に代入している。この記述は許されるだろうか? longは8バイトで、shortは2バイトなので、ここでは「何かまずいこと」が起きると考えるべきだ。が、エラーなどは特に出ず、実行もできてしまう。

void setup() {
 Serial.begin(9600);
 long a = 100000;
 short b = a;
 Serial.println(b);
}
void loop() {
}

実際にこのプログラムを実行してみよう。short は 2バイトなので16bit。2の16乗なので65536種類の数値を表現でき、正負の符号があるので -32768 から 32767 までの値を格納できる。そこに代入する long a = 100000; は short の範囲をオーバーしているので、「何かまずいこと」が起きる。実行結果は

-31072

となる。意図しない値が出てしまっているわけだ。

このように型というのは、使用するメモリを確定するので、その値を格納できるかどうかが決まる。型が異なる代入は気をつけなければいけない。環境によってはエラーとして扱われ、そのほうがミスに気づけてありがたい場合もある。

もちろん、型の変換をあえて行いたい場合がある。先ほどの

 long a = 100000;
 short b = a;

この記述は、コンパイラが勝手に解釈してくれた(=エラーが出なかった)。これを「暗黙的な型変換」と呼ぶ。便利ではあるけれど、書き手がわざとやっているのかウッカリやってしまっているのか区別がつきにくい。

 long a = 100000;
 short b = (short)a;

このように (short) を変数の前におくと「明示的な型変換」になる。実行される内容は変わらない。このように方を明示的に型を変換することを「キャスト」と呼ぶ(正式には type casting かな)。「ある型を別の型とみなして欲しい」という記述だ。

キャストについて、この講座の第一回で、こういった記述があったのを思い出せるだろうか。

*(int*)100=200;

これは100番地に200という数値を書き込む、というプログラムだった。この記述は、100という数値をアドレスとみなすために (int*) で「キャスト」していた、というわけだ。つまりアドレスの表現も型によって行われる。なお * の記述はポインタと呼ばれるもので、後ほど説明していく。

余談だが、ポインタの例に関しては明示的にキャストしないとコンパイルエラーになってしまう。100という数値をアドレスとみなすのは「無茶な」ものなので、勝手にコンパイラが判断しないことになっている。コンパイラが「親切で」解釈することは便利な反面、害悪になりやすい。プログラムは非常に人間的であり、人間の能力の限界で設計されている例と言える。


さて最後にオマケとして、

 long a = 100000;
 short b = a;

このプログラムで、なぜ b に -31072 という値が格納されていたか?これを解き明かしてみよう。もちろんこの値はデタラメではなく根拠がある。

100000(十万)という値を16進数で表現すると 0x186A0 となる。プログラム電卓で試してもらいたい。Mac に標準でついている「計算機」というアプリを起動し、表示→プログラマで16進数を扱えるようになる。100000 を入力してから 16 を選択すれば、0x186A0 と表示されるはずだ。0xというのは16進数であること伝えるための表記だ。

long は 8byte 格納できる。16進数は2桁で1byteなので(これも暗記すべき事実)、0x186A0 は5桁だから、3byte 必要だ。long であればまだまだ余裕だが、shortは 2byte しかないので、0x186A0 のうち 0x86A0 までしか格納できない。つまりこの b には 0x86A0 が格納されてしまうのだ。ここはちょっと複雑なので何度か読んで納得して欲しい。

以降は簡単に説明するにとどめるので、いまは理解できなくても構わない。雰囲気だけ伝われば。

0x86A0の先頭の8は、2進数だと 1000 となり、つまり 0x86A0は最上位ビットが立っている。最上位ビットは「符号」として扱われるので、0x86A0は負の数値と扱われる。負の数値は2の補数で表現されるきまりがあるので、0x86A0で計算機の「補数2」というボタンを押すと 0xFFFFFFFFFFFF7960 となる。この下位4桁の0x7960を10進数で表示すると31072となり、マイナスなので -31072 。これが表示されていた、というわけだ。

2進数・16進数・10進数の関係はプログラマとして生きていく上でとても大事だ。例えば1024が2の10乗、のような事実は頭に叩き込まれているものだ。少しずつでも慣れていこう。

最後は計算の話になったが、プログラムには型があり、それぞれ格納できるバイト数が異なること、コンパイラの解釈により暗黙的に変換されてしまうこともあること、以上が今回の要点だった。

つづく


この記事が気に入ったらサポートをしてみませんか?