C言語の整数の大きさ(整数型が表現できる値の範囲)について、規格として定めていること、実際に処理系が採用している大きさ、このブログでの方針についてまとめました。
規格としての整数型の範囲
C言語の言語仕様については、ISO/IEC 9899 で定められています。C言語は、広い範囲の計算機での利用も視野に入れており、整数の大きさを規格として厳密に定義しませんでした。
規格のバージョン
ISO/IEC 9899 は、何回か改訂されています。バージョンについてまとめます。
- C90 1990年に発行されたため、「C90」と呼ばれています。ISO規格がベースとした ANSI規格が1989年に発行されたため、「C89」とも呼ばれることがありますが、「C89」と「C90」は言語仕様として差がありません。多くの人が C言語と認識している多くの仕様がこのバージョンの仕様に基づいていると思われます。
- C99 1999年に発行されました。これより後に続く規格と比較すると大きく変わりました。整数については、long long int と stdint.h がこのバージョンから導入されました。
- C11 2011年に発行されました。いくつかの仕様が追加されましたが、整数の大きさについては影響がなかったと考えています。
- C17 2018年に発行されました。2017年に規格発行の準備を予定していたため「C17」と呼ばれています。発行年に合わせて「C18」と呼ぶ場合があるかもしれません。C11 の仕様欠陥の修正が主だと認識されています。
動作しているコンパイラがどのバージョンで動作しているかは、マクロ定数 __STDC_VERSION__ として定義されている long int の値を見れば分かります。
バージョン | __STDC_VERSION__ の値 |
C89/C90 | マクロが定義されていない。 |
C99 | 199901L |
C11 | 201112L |
C17/C18 | 201710L |
規格として定まっていること
規格としては、以下しか決まっていません。なお、long long int については、C99 からの導入となります。
- 整数として表現できる範囲の大きさは以下の関係になります。
singed char ≦ short int ≦ int ≦ long int ≦ long long int - singed char の大きさは8ビット以上
- short int の大きさは16ビット以上
- int の大きさは16ビット以上
- long int の大きさは32ビット以上
- long long int の大きさは64ビット以上
- 上記はすべて符号付き、signed がつけば符号付き、unsigned がつけば符号無しになります。注意としては、規格として char を符号付きか符号無しか規定していません(単なる char が符号無しになる処理系がありえます)。
極端な話、char も short も int も long も long long もすべて64ビットの規格準拠のコンパイラが(規格上は)ありえます。
limits.h の規定
規格としては、整数の大きさを厳密に決めていませんが、コンパイラとして何らかの大きさを採用することになります。コンパイラが採用した大きさは、標準ヘッダファイル limits.h でマクロ定数式として定義された値により分かります。
定数名 | 意味 |
SCHAR_MIN | 型signed charのオブジェクトにおける最小値 |
SCHAR_MAX | 型signed charのオブジェクトにおける最大値 |
UCHAR_MAX | 型unsigned charのオブジェクトにおける最大値 |
CHAR_MIN | 型charのオブジェクトにおける最小値 |
CHAR_MAX | 型charのオブジェクトにおける最大値 |
SHRT_MIN | 型short intのオブジェクトにおける最小値 |
SHRT_MAX | 型short intのオブジェクトにおける最大値 |
USHRT_MAX | 型unsigned short intのオブジェクトにおける最大値 |
INT_MIN | 型intのオブジェクトにおける最小値 |
INT_MAX | 型intのオブジェクトにおける最大値 |
UINT_MAX | 型unsigned intのオブジェクトにおける最大値 |
LONG_MIN | 型long intのオブジェクトにおける最小値 |
LONG_MAX | 型long intのオブジェクトにおける最大値 |
ULONG_MAX | 型unsigned long intのオブジェクトにおける最大値 |
LLONG_MIN | 型long long intのオブジェクトにおける最小値(C99から) |
LLONG_MAX | 型long long intのオブジェクトにおける最大値(C99から) |
ULLONG_MAX | 型unsigned long long intのオブジェクトにおける最大値(C99から) |
stdint.h の規定(C99から)
C言語の int は、(その計算機環境に最適化することを意図して)規格としては定めていませんが、それではプログラムが書きにくい場合があります。そこで、サイズを決めた整数型を導入しました。この新しい型は、stdint.h をインクルードすることによって使うことができます。
型名の $N$ には、16や32などの具体的な数字が入ります。
型名 | 意味 |
int$N$_t | ちょうど$N$ビットの大きさを持つ符号付き整数 例)$N$ = 16であれば、-32768から32767の値がちょうど表現できる。 どの$N$に対して、型を用意するかは、処理系依存となる。 |
uint$N$_t | ちょうど$N$ビットの大きさを持つ符号無し整数 例)$N$ = 16であれば、0から65535の値がちょうど表現できる。 どの$N$に対して、型を用意するかは、処理系依存となる。 |
int_least$N$_t | 少なくとも$N$ビットの大きさを持つ符号付き整数 C99では、$N$ = 8、16、32、64 について型を定義する必要がある。 他の$N$に対しては、処理系依存となる。 |
uint_least$N$_t | 少なくとも$N$ビットの大きさを持つ符号無し整数 C99では、$N$ = 8、16、32、64 について型を定義する必要がある。 他の$N$に対しては、処理系依存となる。 |
int_fast$N$_t | 少なくとも$N$ビットの大きさを持ち、最速で動作する符号付き整数 C99では、$N$ = 8、16、32、64 について型を定義する必要がある。 他の$N$に対しては、処理系依存となる。 |
uint_fast$N$_t | 少なくとも$N$ビットの大きさを持ち、最速で動作する符号無し整数 C99では、$N$ = 8、16、32、64 について型を定義する必要がある。 他の$N$に対しては、処理系依存となる。 |
多倍長整数
C の規格としては、大きな値が扱える多倍長整数を定めていません。多倍長演算が利用できるライブラリとして、The GNU Multiple Precision Arithmetic Library(GMP)があります。GMP は別途紹介することにします。
多倍長整数を使いたい場合は、Python や Ruby では無限長の整数が使えるため、これらの言語を使う方が良いかもしれません。
実際に処理系が採用している大きさ
規格としては、整数の大きさは決まっていませんが、現実的には、以下の実装が多かったという印象です。
- char は、8ビット
符号付き:-128~127
符号無し:0~255 - short int は、16ビット
符号付き:-32768~32767
符号無し:0~65535 - int は、32ビット
符号付き:-2147483648~2147483647
符号無し:0~4294967295
ただし、組込み向けで昔は、16ビットもあった。 - long int は、32ビット(int と同じ)
符号付き:-2147483648~2147483647
符号無し:0~4294967295 - long long int は、64ビット
符号付き:ー9223372036854775808~9223372036854775807
符号無し:0~18446744073709551615
32ビットの大きさは、だいだいプラスマイナス20億(符号無しの場合は40億)、64ビットの大きさは、だいだい19桁(符号無しの場合は20桁)と概算していました。
コンパイラ環境が printf 出力できるなら次のプログラムで、コンパイラが採用している整数の大きさが分かります。
#include <stdio.h>
int main(void)
{
printf("signed char : %d\n", (int)sizeof(signed char));
printf("short int : %d\n", (int)sizeof(short int));
printf("int : %d\n", (int)sizeof(int));
printf("long int : %d\n", (int)sizeof(long int));
printf("long long int: %d\n", (int)sizeof(long long int));
return 0;
}
わたくしは、Windows 環境で cygwin を使っています。その環境でこのプログラムを動作させました。結果は以下です。
- signed char は、8ビット
- short int は、16ビット
- int は、32ビット
- long int は、64ビット
- long long int は、64ビット
コンパイラは、gcc (GCC) 11.3.0 で、特に言語バージョンは指定していません。ずっと long は、32ビットだと考えていましたが、知らない間に64ビットになっていたようです。一方、int は32ビットのままでした。
このブログの方針
このブログで紹介する C プログラムは整数に関して、次の方針で記述します。
- 言語バージョンは、C99 を採用するが、long long int を使う以外は、可能な限り C90 から提供している機能でプログラムを実装する。
- short int は原則使わない。
- int を32ビットと仮定して、可能な限り int を使って書く。
規格に従えば、このような場合は、int ではなく int_least32_t を使うべきですが、普及している型を使うことを優先しました。 - 著者の環境では、long int が64ビットであったが、32ビットと仮定してプログラムを書く。つまり32ビットを超える整数を使うときは、long long int を使う。またブログでの記事も long int を32ビットと仮定して記述する。
- このため、long int は使われなくなる。これは、32ビットの範囲の整数は int で書き、それを超える整数は、long long int を使うことになるため。
- stdint.h で定義している型は使わない。
将来は、int が64ビットになるなど、この方針を変更をする時代が来るかもしれませんが、しばらくはこれでプログラミングを楽しみます。
最後に
Project Euler は、32ビットを超える整数を扱う場合があります。そのため、C の整数に関する規格と実際の処理系について紹介しました。このブログでは、読みやすさを優先してプログラムを記述しているため、採用した方針についても紹介しました。
C の整数を使いこなしていきましょう!