整数型 | Programming Place Plus C言語編 第19章

トップページC言語編

このページの概要

以下は目次です。


整数型

ここまでの章で登場した整数型は、int型unsigned int型ですが、整数型はほかにも多数あります。この章で大半の整数型を紹介します。

すべての型を一覧にした表が、「APPENDIX 型の分類表」にあります。

整数型に多くの種類があるのは、表現したい数の範囲によって使い分けるためです。広い範囲の整数が表現できなければならないときには、メモリの消費量が大きくなってしまってでも、大きな整数型を使う必要性があります。反対に、表現したい数の範囲が狭いのなら、小さな整数型を選ぶことによって、メモリを節約できます。「大きな」とか「小さな」といった表現をしていますが、これは型には大きさ (size) があるということです。型の大きさはバイト (byte) という単位で表現でき、sizeof演算子を使って調べられます。

「大きさ」のことを「長さ (length)」と表現する場合もあります。

ただし、さまざまな整数型を不用意に混在させない方がいいです。型が混在している場合、どれかの型に合わせる変換が必要になるのですが、このルールは非常に複雑であり、正しく取り扱うには相当の慎重さが求められるからです。とはいえ、int型で表現しきれない数を扱うには、より大きな型を選択することは避けられませんから、ある程度の理解は必要です。

C言語には以下の整数型が定義されています。

【C++プログラマー】C++ では、列挙型は整数型ではないとされています。また、wchar_t、char16_t、char32_t については、C言語ではそれぞれ typedef で作られる型の別名なので、このリストには登場しません。

(signed) の部分は、ソースコードに記述するときには省略できます。signed char の場合は signed を省略すると、char型になるので省略できません。

同様に、(int) の部分も省略できます。

上記の型のうち、signed char、short int、int、long int、long long int の 5つを、標準符号付き整数型 (standard signed integer types) と呼びます。また、unsigned char、unsigned short int、unsigned int、unsigned long int、unsigned long long int、_Bool の 6つを、標準符号無し整数型 (standard unsigned integer types) と呼びます。そして両方を合わせたものを、標準整数型 (standard integer types) と呼びます。

これらに加えて、処理系が独自の整数型を定義していることがあり、拡張整数型 (extended integer types) と呼ばれます。その目的は多くの場合、標準整数型では表現できないさらに大きな整数型を提供することにあります。拡張整数型にも符号の有無があって、符号付きのものを拡張符号付き整数型 (extended signed integer types)、符号無しのものを拡張符号無し整数型 (extended unsigned integer types) と呼びます。

単に符号付き整数型といった場合、標準符号付き整数型と拡張符号付き整数型を含みます。単に符号無し整数型といった場合は、標準符号無し整数型と拡張符号無し整数型を含みます。

ある符号付き整数型に unsiged キーワードを付加した型を、「対応する符号無し整数型」と表現することがあります。対応する符号無し整数型の大きさは、元の符号付き整数型と必ず一致します。

char、signed char、unsigned char

char型signed char型unsigned char型 はそれぞれ別の型として存在します。大きさはいずれも同じで、ほとんどの場合 8ビットです。

char型は、signed char型か unsigned char型のいずれかとまったく同じ範囲の整数を表現できます。どちらであるかは処理系定義です1。signed char型は符号付き整数型であり、unsigned char型は符号無し整数型ですので、char型に符号があるかどうかは処理系定義ということになります。そのため、char型を小さな整数型のつもりで使うと、ほかの処理系で失敗する恐れがあります。小さな整数型として使いたいときは、signed char型、unsigned char型のいずれかにして、符号の有無を明確にするべきです。

// 以下の3つはすべて異なる型
char c = 0;
signed char sc = 0;
unsigned char uc = 0;

printf関数を使って signed char型や unsigned char型の値を出力するときには、“hh” を補って、“%hhd” や “%hhu” という指定を与えます。この “hh” のように、変換指定子と組み合わせることで、値の大きさを指示する指定を、長さ修飾子 (length modifier) と呼びます。sscanf関数も同様です。

#include <stdio.h>

int main(void)
{
    char input_string[40];
    fgets(input_string, sizeof(input_string), stdin);
    signed char c;
    sscanf(input_string, "%hhd", &c);
    printf("%hhd\n", c);

    fgets(input_string, sizeof(input_string), stdin);
    unsigned char uc;
    sscanf(input_string, "%hhu", &uc);
    printf("%hhu\n", uc);
}

実行結果:

-123  <-- 入力された内容
-123
123  <-- 入力された内容
123

short

short型は、小さめの整数型であることを期待して使う型です。unsigned キーワードを付加できます。

// 以下の4つは同等
short si = 0;
short int si = 0;
signed short si = 0;
signed short int si = 0;

// 以下の2つは同等
unsigned short usi = 0;
unsigned short int usi = 0;

short型で表現できる値として -32767~32767 が、unsigned short型で表現できる値として 0~65535 が保証されています2この範囲を表現するためには 16ビットが必要であることから、short/unsigned short型の大きさは最低でも 16ビットです。

int型で保証されている値の範囲もまったく同じであるため、short型が「int型と比較して小さい整数」ではない可能性があることには注意が必要です。メモリの節約のつもりで short型を使っても意味がないかもしれません。

現代の処理系では short型が 16ビット、int型が 32ビットであることが多いですが、古い処理系ではいずれも 16ビットのものがあります。


printf関数を使って short型や unsigned short型の値を出力するときには、“h” という長さ修飾子を補って、“%hd” や “%hu” という指定を与えます。sscanf関数も同様です。

#include <stdio.h>

int main(void)
{
    char input_string[40];
    fgets(input_string, sizeof(input_string), stdin);
    short s;
    sscanf(input_string, "%hd", &s);
    printf("%hd\n", s);

    fgets(input_string, sizeof(input_string), stdin);
    unsigned short us;
    sscanf(input_string, "%hu", &us);
    printf("%hu\n", us);
}

実行結果:

-1234  <-- 入力された内容
-1234
1234  <-- 入力された内容
1234

int

int型は、もっとも基本的な整数型です。unsigned キーワードを付加できます。

// 以下の3つは同等
int i = 0;
signed int i = 0;
signed i = 0;

// 以下の2つは同等
unsigned int ui = 0;
unsigned ui = 0;

通常、int型がもっとも効率的に処理できるようになっているため、優先してこの型を選ぶと良いです。int、unsigned int 以外の整数型は、何らかの目的があるときにだけ選択します。

int型で表現できる値として -32767~32767 が、unsigned int型で表現できる値として 0~65535 が保証されています2この範囲を表現するためには 16ビットが必要であることから、int/unsigned int型の大きさは最低でも 16ビットです。int型が、short型よりも小さいということはありません。

現代の処理系では int型は 32ビットであることが多いですが、古い処理系では 16ビットのものがあります。

long

long型は、大きめの整数型であることを期待して使う型です。unsigned キーワードを付加できます。

// 以下の4つは同等
long li = 0;
long int li = 0;
signed long li = 0;
signed long int li = 0;

// 以下の2つは同等
unsigned long uli = 0;
unsigned long int uli = 0;

long型で表現できる値として -2,147,483,647~2,147,483,647 が、unsigned long型で表現できる値として 0~4,294,967,295 が保証されています2この範囲を表現するためには 32ビットが必要であることから、long/unsigned long型の大きさは最低でも 32ビットです。long型が int型よりも小さいということはありません。

現代では int型が 32ビットの処理系が多いため、long型が「int型と比較して大きい整数」ではない可能性があることには注意が必要です。int型では表現できない値を表現しようとして long型を選んでも、実は表現しきれない恐れがあります。


printf関数を使って long型や unsigned long型の値を出力するときには、“l” という長さ修飾子を補って、“%ld” や “%lu” という指定を与えます。sscanf関数も同様です。“l” は “L” にはできません。

#include <stdio.h>

int main(void)
{
    char input_string[40];
    fgets(input_string, sizeof(input_string), stdin);
    long l;
    sscanf(input_string, "%ld", &l);
    printf("%ld\n", l);

    fgets(input_string, sizeof(input_string), stdin);
    unsigned long ul;
    sscanf(input_string, "%lu", &ul);
    printf("%lu\n", ul);
}

実行結果:

-1234567  <-- 入力された内容
-1234567
1234567  <-- 入力された内容
1234567

long long

long long型は long型よりもさらに大きいことを期待して使う型です。unsigned キーワードを付加できます。

// 以下の4つは同等
long long lli = 0;
long long int lli = 0;
signed long long lli = 0;
signed long long int lli = 0;

// 以下の2つは同等
unsigned long long ulli = 0;
unsigned long long int ulli = 0;

long long型で表現できる値として -9,223,372,036,854,775,807~9,223,372,036,854,775,807 が、unsigned long long型で表現できる値として 0~18,446,744,073,709,551,615 が保証されています2この範囲を表現するためには 64ビットが必要であることから、long long/unsigned long long型の大きさは最低でも 64ビットです。long long型が long型よりも小さいということはありません。


printf関数を使って long long型や unsigned long long型の値を出力するときには、“ll” という長さ修飾子を補って、“%lld” や “%llu” という指定を与えます。sscanf関数も同様です。“ll” は小文字でなければならず、“LL” などとはできません。

#include <stdio.h>

int main(void)
{
    char input_string[40];
    fgets(input_string, sizeof(input_string), stdin);
    long long ll;
    sscanf(input_string, "%lld", &ll);
    printf("%lld\n", ll);

    fgets(input_string, sizeof(input_string), stdin);
    unsigned long long ull;
    sscanf(input_string, "%llu", &ull);
    printf("%llu\n", ull);
}

実行結果:

-123456789012345  <-- 入力された内容
-123456789012345
123456789012345  <-- 入力された内容
123456789012345

_Bool

_Bool型は、第13章で登場しました。真か偽かを表すための型ですが、分類上は整数型に含まれています。

分類上、符号無し整数型であり、signed や unsigned を付加することはできません。

_Bool型の大きさは規定されていません。2種類の値しか扱わないので小さそうに思えますが、最小でも 1バイトはありますし、もっと大きい可能性もあります。

整数リテラルの型

整数リテラルの型のルールを取り上げておきます3 4

基本的な考え方としては、ルールに沿っていくつかの整数型を順番に試していき、その値を表現できる最初の型で確定となります。そのルールが、8進数や 16進数をあらわすプリフィックス第18章)の有無や、型の大きさや符号の有無を強制するサフィックスの有無によって変わります。

プリフィックス サフィックス 考慮される型
なし なし int -> long -> long long(-> 拡張符号付き整数型)
なし u (U) unsigned int -> unsigned long -> unsigned long long(-> 拡張符号無し整数型)
なし l (L) long -> long long(-> 拡張符号付き整数型)
なし l (L) と u (U) unsigned long -> unsigned long long(-> 拡張符号無し整数型)
なし ll (LL) long long(-> 拡張符号付き整数型)
なし ll (LL) と u (U) unsigned long long(-> 拡張符号無し整数型)
0 または 0x なし int -> unsigned int -> long -> unsigned long -> long long -> unsigned long long(-> 拡張整数型)
0 または 0x u (U) unsigned int -> unsigned long -> unsigned long long(-> 拡張符号無し整数型)
0 または 0x l (L) long -> unsigned long -> long long -> unsigned long long(-> 拡張整数型)
0 または 0x l (L) と u (U) unsigned long -> unsigned long long(-> 拡張符号無し整数型)
0 または 0x ll (LL) long long -> unsigned long long(-> 拡張整数型)
0 または 0x ll (LL) と u (U) unsigned long long(-> 拡張符号無し整数型)

拡張整数型については、そのような型が存在する場合に限られます。いずれの型でも表現できない場合はコンパイルエラーになります。

型の大きさを表すサフィックスは、100L とか 100LL といったように表記します。小文字と大文字のいずれを使っても構いませんが、l1 と見間違いやすいため、L を勧めます。

long li = 100000L;
long li = 100000l;   // 1000001 のようにみえるので勧めない
long long lli = 10000000000LL;
long long lli = 10000000000ll;   // 10000000011 や 10000000001L と間違えやすいので勧めない

いずれの場合でも、Uサフィックスを同時に付加できます。

unsigned long uli = 100000UL;
unsigned long uli = 100000LU;
unsigned long long ulli = 10000000000ULL;
unsigned long long ulli = 10000000000LLU;

signed char や short に対応するサフィックスがないことに気付かれたかもしれませんが、そのようなサフィックスは存在しません。実は、これがないと困るという場面はないからです。たとえば、short型の値は、代入や計算を行うときなどに、自動的に int型に拡張されます(第21章で取り上げる、整数拡張の動作)。そのため、仮に short型であることをあらわすサフィックスがあったとしても、結局 int型として取り扱われるため意味がありません。

sizeof

sizeof演算子 (sizeof operator) を使うと、型の大きさをバイト数で調べられます。

sizeof演算子の使い方には以下のパターンがあります。

sizeof(型名)
sizeof()
sizeof

「型名」を指定した場合、その型の大きさが得られます。

「式」を指定した場合、その式の結果の型の大きさが得られます。このとき、その式は実際には評価されません。たとえば、int の戻り値を返す関数f があるとき、sizeof(f()) は、戻り値の型 int の大きさを得られますが、関数f が呼び出されるわけではありません。同じ理由で、sizeof(a++) では a の値は増えません。

sizeof演算子によって得られる値の型は size_tです。size_t は標準ライブラリで定義されており、符号のない整数型として使えますが、これ自体の大きさは処理系定義です。size_t を使うには #include <stddef> が必要ですが、わざわざ記述せずとも使えることが多いです。

printf関数を使って size_t型の値を出力するときには、“z” という長さ修飾子を使います。符号無し整数型なので、“zu” ということになります。sscanf関数も同様です。


sizeof演算子を使って、各種整数型の大きさを調べてみます。

#include <stdio.h>

int main(void)
{
    printf("sizeof(char) == %zu\n", sizeof(char));
    printf("sizeof(signed char) == %zu\n", sizeof(signed char));
    printf("sizeof(unsigned char) == %zu\n", sizeof(unsigned char));
    printf("sizeof(short) == %zu\n", sizeof(short));
    printf("sizeof(unsigned short) == %zu\n", sizeof(unsigned short));
    printf("sizeof(int) == %zu\n", sizeof(int));
    printf("sizeof(unsigned int) == %zu\n", sizeof(unsigned int));
    printf("sizeof(long) == %zu\n", sizeof(long));
    printf("sizeof(unsigned long) == %zu\n", sizeof(unsigned long));
    printf("sizeof(long long) == %zu\n", sizeof(long long));
    printf("sizeof(unsigned long long) == %zu\n", sizeof(unsigned long long));
    printf("sizeof(_Bool) == %zu\n", sizeof(_Bool));
}

実行結果:

sizeof(char) == 1
sizeof(signed char) == 1
sizeof(unsigned char) == 1
sizeof(short) == 2
sizeof(unsigned short) == 2
sizeof(int) == 4
sizeof(unsigned int) == 4
sizeof(long) == 4
sizeof(unsigned long) == 4
sizeof(long long) == 8
sizeof(unsigned long long) == 8
sizeof(_Bool) == 1

char、signed char、unsigned char の大きさは必ず 1 です。ほかの型の大きさはすべて処理系定義であり、この実行結果とは異なるかもしれません。

型の別名 (typedef)

型の名前に、別名を与える機能があります。このような機能があるのは、型も変数と同じで、意味が通じやすい名前を付けたほうがいいと考えられるためです。単に int(この型は何かしらの整数を表現する)よりも、money(この型は金額を表現する)のような名称のほうが分かりやすく、間違いを防ぐ効果もあります(たとえば、money型の変数に個数を代入することはおかしいと気付きやすい)。実際には money のような名前では、変数名との区別が難しくなってしまうため、money_tmoney_type などとすることが多いです。

型の別名を付けるには、typedef を使います。

typedef 元の名前 別名;

typedef で付けられた名前は、元の型の別名だというだけのことなので、元の型と比べて表現できる値の範囲が変わることはありません。また、互いの型の値を代入したり、比較したりすることにも影響しません。

#include <stdio.h>

int main(void)
{
    typedef int money_t;   // 金額型
    typedef int amount_t;  // 数量型

    money_t cost = 123;
    amount_t amount = 10;

    printf("%d\n", cost * amount);
}

実行結果:

1230

この場合、金額と数量はいずれも int型に過ぎないのですが、それぞれを使い分けることで意図を明確にしています。残念ながら、if (cost == amount) のようなミスを検出してくれるようなことはないのですが(どちらも int なので問題ない)、人間がコードを理解するうえでは助けになります。


また、前述した size_t も、typedef を使って付けられた別名です。たとえば、次のように定義されています。

typedef unsigned int size_t;

区切りが分かりづらいのですが、この場合なら size_t は unsinged int型の別名ということになります。unsigned int型の大きさでは、size_t の用途を満たせない処理系では、次のように定義されているかもしれません。

typedef unsigned long long int size_t;

いずれにしても、我々は size_t という名前を使っておけばいいということになります。

限界値

ある整数型で表現できる値の範囲は、標準ライブラリに定義されているマクロ(第10章)を使って調べることもできます。このマクロを使うには、#include <limits.h> が必要です。

次のプログラムを実行すると、各型の限界値(最小値と最大値)が分かります。

#include <limits.h>
#include <stdio.h>

int main(void)
{
    printf("char: (MIN)%d  (MAX)%d\n", CHAR_MIN, CHAR_MAX);
    printf("signed char: (MIN)%d  (MAX)%d\n", SCHAR_MIN, SCHAR_MAX);
    printf("unsigned char: (MIN)%d  (MAX)%d\n", 0, UCHAR_MAX);
    printf("short: (MIN)%d  (MAX)%d\n", SHRT_MIN, SHRT_MAX);
    printf("unsigned short: (MIN)%d  (MAX)%d\n", 0, USHRT_MAX);
    printf("int: (MIN)%d  (MAX)%d\n", INT_MIN, INT_MAX);
    printf("unsigned int: (MIN)%u  (MAX)%u\n", 0U, UINT_MAX);
    printf("long: (MIN)%ld  (MAX)%ld\n", LONG_MIN, LONG_MAX);
    printf("unsigned long: (MIN)%lu  (MAX)%lu\n", 0UL, ULONG_MAX);
    printf("long long: (MIN)%lld  (MAX)%lld\n", LLONG_MIN, LLONG_MAX);
    printf("unsigned long long: (MIN)%llu  (MAX)%llu\n", 0ULL, ULLONG_MAX);
}

実行結果:

char: (MIN)-128  (MAX)127
signed char: (MIN)-128  (MAX)127
unsigned char: (MIN)0  (MAX)255
short: (MIN)-32768  (MAX)32767
unsigned short: (MIN)0  (MAX)65535
int: (MIN)-2147483648  (MAX)2147483647
unsigned int: (MIN)0  (MAX)4294967295
long: (MIN)-2147483648  (MAX)2147483647
unsigned long: (MIN)0  (MAX)4294967295
long long: (MIN)-9223372036854775808  (MAX)9223372036854775807
unsigned long long: (MIN)0  (MAX)18446744073709551615

出力結果は処理系によって異なります。

たとえば short型の最小値を出力するときの指定は “%hd” なような感じがしますが、これらのマクロで得られるものは整数リテラルであり、型は int 以上の大きさを持った整数型です(「整数リテラルの型」の項を参照)。そのため、char や short のところでは “%d” を使っています。

存在するマクロを以下の表にまとめました。符号無し整数型の場合は、最大値の方だけが用意されています(最小値は必ず 0)。

最小値 最大値
char CHAR_MIN CHAR_MAX
signed char SCHAR_MIN SCHAR_MAX
unsigned char 0 UCHAR_MAX
short SHRT_MIN SHRT_MAX
unsigned short 0 USHRT_MAX
int INT_MIN INT_MAX
unsigned int 0 UINT_MAX
long LONG_MIN LONG_MAX
unsigned long 0 ULONG_MAX
long long LLONG_MIN LLONG_MAX
unsigned long long 0 ULLONG_MAX

size_t型の最大値を SIZE_MAX で得られますが、これは #include <stdint.h> が必要です。

表現できない値を与えたとき

初期化や代入などで与えようとした値が、相手側の整数型で表現できない場合の結果は、相手側の型の符号の有無によって異なります5

1つ目のルールは分かりづらいですが、大まかにいえば、上限値を超えたら下限値から戻ってくる、下限値を超えたら上限値から戻ってくるということです。たとえば 最大値 + 1 の結果は 0 ですし、0u - 1u の結果は上限値と一致します。こういう動作をラップアラウンド (wrap around) といいます(たまに、オーバーフローと誤解されますが、これはオーバーフローではありません)。

2つ目のルールも難しいですが、ともかく処理系定義なので、移植性を考えると、表現できない値を与えないほうがいいということになります。


練習問題

問題① 次の変数 n1~n8 の型はそれぞれ何が適切ですか?(あえて大きい型を使わないとして)

signed char sc = -10;
unsigned short us = 10;

???? n1 = 10L;
???? n2 = 10LL;
???? n3 = 10LU;
???? n4 = 10 + n1;
???? n5 = n2 + n3;
???? n6 = sc * 2;
???? n7 = sc * us;
???? n8 = sc + 100LL / 2U;

問題② char型が符号付きか、符号無しかを判定するプログラムを作成してください。

問題③ 次のプログラム片を見てください。

signed char c1 = 120;
signed char c2 = 60;
signed char c3 = -100;
signed char result = c1 + c2 + c3;

signed char型で表現できる最大値は 127 であり、c1 + c2 の段階で溢れ出してしまうように思えます。実際には、最終的な result の値は 80 となり正しく計算できます。問題が起こらない理由を説明してください。


解答ページはこちら

参考リンク


更新履歴



前の章へ (第18章 整数の表現方法)

次の章へ (第20章 浮動小数点型)

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る