はじめに
今回はISO/IEC9899というC言語の規格を記している資料から型の正しい定義について学んでみる
なぜ今さら型について学ぶのか
移植性のあるコードを書くためです。
「long型が実行環境によって32bitなのか64bitなのか変わる」という話は聞いたことがあると思います。ただ僕はこの話を聞いてから「それぞれの型の"正しい定義"を知らないと移植性のあるコードは書けない」と、前々から感じていました。
なぜなら環境によってその"正しい定義"は守られているはずだからです。
逆にいえばそれ以外はその環境が自由に決めていると考えた方が良いのかもしれません。
型
型は以下のように定義されています
(原文)
Types are defined in the following categories:
— integer types having certain exact widths;
— integer types having at least certain specified widths;
— fastest integer types having at least certain specified widths;
— integer types wide enough to hold pointers to objects;
— integer types having greatest width.
(和訳)
型は以下のカテゴリで定義されます。
ー ある確かな幅を持つ整数型
ー 少なくとも確かな特定の幅を持つ整数型
ー 少なくとも確かな特定の幅を持つ最速の整数型
ー オブジェクトへのポインタを保持するのに十分な幅を持つ整数型
ー 最大の幅を持つ整数型
なんとなくC言語を学んできた人であれば納得できると思います。
ここから実際に僕らがよく使う整数型の定義を見ていきます。
整数型
bool型
数値型と言って最初にbool型を紹介するのは違和感があるかもしれません。ですが、bool型はfalse(0)とtrue(0以外)を扱う型なので整数型に属します。
そしてbool型は
「0と1を格納するのに十分な大きさを持つ整数型」
と定義されています。
なので環境によっては0か1かしか表さないことがあるということです。
char型
char型は文字型だ、という意見があると思います。ですがchar型は数値を格納してそれに対応する文字を出力することもできるので文字型でありながら数値型に属しています。
char型は
「基本実行文字セットのメンバーを格納するのに十分な大きさを持つ整数型」
と定義されています。
ここでいう基本文字セットはASCIIからきています。
int型
int型は
「ヘッダで定義されているINT_MINからINT_MAXを格納するのに十分な、その実行環境で自然とされている大きさを持つ整数型」
と定義されています。
なので規格上ではint型はbit数が決まっていません。
しかし、limit.hについて
INT_MIN <= -(2^15 - 1) <= (2^15 - 1) <= INT_MAX
と定義されているのでint型は少なくとも16bit以上であることが保証されています。
long型、long long型
long = long int
long long = long long int
上記のように省略した形です。
これらについて定義はint型と同様です。
そしてlimits.hについて
LONG_MIN <= -(2^31 - 1) <= (2^31 - 1) <= LONG_MAX
LLONG_MIN <= -(2^63 - 1) <= (2^63 - 1) <= LLONG_MAX
と定義されているので、long型は32bit以上、long long型は64bit以上であることが保証されています。
signed、unsignedについて
それぞれ符号あり(signed)、符号なし(unsigned)の整数型を宣言するための修飾子です。
signedは付けなくてももともと整数型は符号ありなので変わりませんが、unsignedを付けると符号分の1bitを多く数値を表すのに使えるので表せる数値の絶対値の大きさが1bit分増えます。
INT_MAX -> 2^15 - 1
UINT_MAX -> 2^16 - 1
char型のみ特殊で
「char型はsigned char型またはunsigned char型のいずれかと同じ範囲で定義しなければなりません」
と書いてあります。つまりchar型だけはsigned char型と定義してあげないと符号なしの可能性があります。
また、型の最大値を超える計算について、signedであればオーバーフローが発生し未定義の動作(処理系に依存する)になりますが、unsignedだとその型の最大値+1を法として剰余演算が行われるのでオーバーフローが発生しません。
UINT_MAX -> 2^16 - 1 = 65535
65535 + 10 = (65535) ≡ 9(mod65536)
定義を知った上で...
型について知った上で実際にコードを書く時に意識すべきことは
文字型であるchar型を除いて
整数型はintN_tやuintN_tで表記すべき
ということだと思います。
また、char型はsigned char型で定義すべきです。
規格でも定められていますが、Nbitの整数型をintN_tと表記することができます。
なので32bitの整数型が欲しいときはint32_tと宣言してあげるのがより良いコードであると言えます。
また、それに伴うINTN_MAXも定義されています。
終わりに
小数型についても調べてみると面白そうです。
参考文献:https://www.open-std.org/jtc1/sc22/wg14/www/standards