C言語のすべての変数には型があり、宣言時に選ばれ、その生存期間を通じて固定されます。型は3つのことを決めます。変数が何バイトを占めるか、それらのバイトがどう解釈されるか、そしてどんな演算が意味を持つかです。
C言語の型のリストは短いものです。複雑さは修飾子のほうにあります。
4つの基本型
charは1バイトを保持します。1文字('A')に、文字列のバイトに、そしてときには小さな整数として使われます。intは整数型の主力です。ループカウンタ、サイズ、ID に使われます。floatとdoubleは実数を保持します。doubleはおよそ2倍の精度を持ちます。voidはある意味で4つ目の型ですが、「値がない」ことを意味します -voidの変数を宣言することはできず、関数の戻り値の型、空の仮引数リスト、型のないポインタとして使うだけです。
3.14f の f サフィックスに注目してください。これがないとリテラルは double になり、float に代入すると静かに精度を失います。警告するコンパイラもあります。
修飾子: short、long、unsigned
基本の整数型には、サイズや符号を変える修飾子が付きます。
整数型にはそれぞれ専用の書式指定子が必要です: short には %hd、int には %d、long には %ld、long long には %lld、unsigned には %u。間違ったものを使うのは丸め誤差ではなく未定義動作です。
unsigned は符号ビットを取り除き、正の範囲を2倍にします。unsigned int は -21億〜+21億ではなく、0 から約43億までを保持します。本当に負になり得ないもの - サイズ、バイト数、ビットパターン - には正しい選択で、ゼロを下回る引き算をされ得るものにとっては罠です。
signed は int、short、long、long long のデフォルトなので、書くことはほとんどありません。唯一効いてくるのは char です。素の char が signed か unsigned かは処理系定義なので、符号が重要なときは signed char か unsigned char と書きましょう。
サイズ: 実際に得られるもの
C言語の規格は正確なサイズではなく最小値を定めています。現代の64ビットの Linux、macOS、Windows マシンであれば、こうなるでしょう:
| 型 | 典型的なサイズ | 典型的な範囲 |
|---|---|---|
char | 1バイト | -128〜127(または 0〜255) |
short | 2バイト | -32,768〜32,767 |
int | 4バイト | -2,147,483,648〜2,147,483,647 |
long | 8バイト(Windows では4) | おおよそ ±9.2×10^18 |
long long | 8バイト | おおよそ ±9.2×10^18 |
float | 4バイト | 有効数字およそ7桁 |
double | 8バイト | 有効数字およそ15桁 |
long double | 16バイト(環境による) | double より多い |
人が引っかかるのは long の行です。Linux と macOS では8バイトですが、64ビット Windows では4バイトです。long が64ビットの値を保持できると仮定したコードには移植性がありません。long long を使うか、サイズが要件の一部であるときは stdint.h の正確な幅の型(int32_t、uint64_t)を使いましょう。
sizeof: コンパイラに尋ねる
サイズを推測してはいけません - 測りましょう:
sizeof は関数ではなく演算子で、コンパイル時に評価されます。結果の型は size_t で、%zu で出力します。
sizeof(char) はちょうど 1 であることが保証されています - それがC言語におけるバイトの定義です。他のすべてはそれを基準に測られます。
limits.h と float.h
お使いのコンパイラでの正確な範囲は、名前の付いた定数として利用できます:
演算がオーバーフローするかどうかを知りたいときは、これらの値と比較します。a + b を計算する前に if (a > INT_MAX - b) を確認するのが、オーバーフローを起きる前に検出する方法です - これは重要で、signed のオーバーフローは起きた後では検出できないからです。
整数オーバーフロー
値が型の範囲を超えたときに何が起こるかは、完全に符号次第です。
unsigned のオーバーフローは定義されています: 値は 2^N を法として巻き戻ります。
この2つ目のケースは本物のバグの源です。for (unsigned i = n - 1; i >= 0; i--) のようなループは決して終わりません。unsigned の値は常に >= 0 だからです。
signed のオーバーフローは未定義動作です。 「巻き戻る」のではなく、未定義です。コンパイラはそれが決して起きないと仮定して最適化してよいので、事実の後に書かれたオーバーフローのチェックは削除され得ます:
int sum = a + b;
if (sum < a) { /* コンパイラはこれを丸ごと取り除いてよい */ }
上限値を使って、前にチェックしましょう:
if (b > 0 && a > INT_MAX - b) {
/* a + b はオーバーフローする - ここで処理する */
}
浮動小数点の精度
float と double は数値を2進数で格納しますが、ほとんどの十進の小数には正確な2進表現がありません - 1/3 に正確な十進表現がないのと同じです。
そこから導かれる原則: 浮動小数点の値を == で比較してはいけません。代わりに絶対差を小さな許容誤差と比べましょう。
そして、お金に浮動小数点を使ってはいけません。セントを整数として格納しましょう。セントの long long は正確ですが、ドルの double はそうではありません。
型を選ぶ
ほとんどのコードをカバーする短い判断リストです:
- 整数: 理由がない限り
int。CPU が最も効率よく扱う型であり、すべての算術ルールが中心に据えている型です。 - 20億を超えるもの:
long long、またはstdint.hのint64_t。 sizeofやstrlenから来るサイズ・長さ・配列インデックス:size_t。unsigned であり、あらゆるオブジェクトに対して十分な大きさが保証されています。- 小数:
double。floatは大きな配列でメモリを半分にしたいときか、倍精度ユニットのない組み込みハードウェアでのみ使いましょう。 - 1文字と生のバイト: テキストには
char、バイナリデータにはunsigned char。 - 真/偽:
stdbool.hのbool- C言語の真偽値を参照。 - 正確なビット幅(ファイル形式、ネットワークプロトコル、ハードウェアレジスタ):
stdint.hのuint8_t、int16_t、uint32_tなど。
型を混ぜる
1つの式で異なる2つの型を組み合わせると、C言語は算術を行う前に裏で変換します。たいていは助けになりますが、ときには破滅的です:
最初のものは整数除算です。両方のオペランドが int なので結果も int となり、小数部は捨てられます。3つ目のほうが厄介です - signed の値を unsigned の値と比較すると、signed のほうが unsigned に変換され、-1 が巨大な正の数になります。
こうした変換のルールと、明示的なキャストでそれを制御する方法は C言語の型キャストのテーマです。
よくある質問
C言語の基本的なデータ型は何ですか?
基本となる4つの型は、1文字やバイトを表す char、整数を表す int、小数を表す float と double です。修飾子がそのサイズと符号を変え - short、long、long long、signed、unsigned - 全体のセットができあがります。
C言語の int は何バイトですか?
現代のデスクトップやサーバーではほぼ常に4バイト(32ビット)で、およそ -21億から21億の範囲になります。規格が保証するのは最低2バイトだけで、16ビットのマイコンでは実際に2バイトです。確実に知る必要があれば sizeof(int) を使いましょう。
C言語の float と double の違いは何ですか?
float は4バイトで有効な十進数が約7桁、double は8バイトで約15桁です。浮動小数点リテラルや数学関数のデフォルトは double であり、何百万もの値を格納するのでも組み込みチップを対象にするのでもない限り、double が正しい選択です。
C言語で int がオーバーフローすると何が起こりますか?
signed の int では、オーバーフローは未定義動作です - コンパイラは巻き戻しても、飽和させても、チェックそのものを最適化で消してもかまいません。unsigned の int では完全に定義されており、値は 2^N を法として巻き戻るので UINT_MAX + 1 は 0 になります。signed のオーバーフローには決して頼らないでください。