C言語のきほん|移植性を高める整数型の選び方(C99対応)

環境が変わっても困らない、C99時代の整数型の選び方を身につけよう。

C言語で整数を扱うとき、これまでに学んできた int、short、long といった型はとても基本的で、日常的によく使います。
ただ、ここでひとつ意識しておきたいのが、これらの型は環境によってサイズが変わることがある、という点です。

たとえば、ある環境では int が 4バイトでも、別の環境では long のサイズが違っていたり、扱える範囲が思ったものとずれていたりすることがあります。
こうした違いは、ひとつの環境だけで動かすプログラムではあまり気にならないかもしれません。けれど、別のOSに移したり、組込み機器で動かしたり、ハードウェアに近い制御を行ったりすると、急に重要な問題として表に出てきます。

このような「環境が変わると型のサイズや範囲も変わるかもしれない」という不安を減らすために、C99では stdint.h が導入されました。
このヘッダでは、ビット幅を明確に意識できる整数型が用意されていて、プログラムの移植性を高めやすくなっています。

たとえば、必ず 8ビットで扱いたい値には int8_t、必ず 64ビットで扱いたい値には int64_t といった型を使えます。
こうすることで、「この変数はどれだけの大きさを持つのか」がコードを読んだだけでわかりやすくなり、環境差によるトラブルも減らしやすくなります。

特に、バイナリデータの処理、ファイル形式の読み書き、通信プロトコル、組込み開発、レジスタ操作のような場面では、整数型の幅がとても大切です。
ここでは、C99で追加された整数型の種類と役割を整理しながら、移植性を高める整数型の選び方をじっくり見ていきましょう。

なぜ標準的な整数型だけでは不十分なのか

まず最初に押さえておきたいのは、int、short、long などの標準的な整数型は、とても便利ではあるものの、ビット幅が完全に固定されているわけではない、ということです。

たとえば、次のような違いが起こることがあります。

環境Aの例環境Bの例
short2バイト2バイト
int4バイト4バイト
long4バイト8バイト
long long8バイト8バイト

この表を見ると、long が環境によって変わる可能性があることがわかります。
つまり、long を使って「この変数は必ず 32ビットだろう」と思い込んでしまうと、別の環境で期待通りに動かないことがあるのです。

こうした問題は、特に次のような場面で困りやすくなります。

  • 通信データの形式がビット単位で決まっているとき
  • ファイル形式のデータ長が厳密に決まっているとき
  • ハードウェアレジスタをそのまま扱うとき
  • メモリ使用量を細かく管理したいとき
  • 複数の環境で同じ動作を保証したいとき

このような場面では、「だいたいこのくらいの大きさ」ではなく、確実に何ビットか が重要になります。
そこで役立つのが stdint.h の整数型です。

C99で追加された整数型の考え方

C99では、移植性を高めるために、整数型をより明確に選べる仕組みが追加されました。
これらは大きく分けると、次の4種類に整理できます。

種類役割
固定幅整数型ビット幅を厳密に指定したいときに使う
最小幅整数型少なくとも指定した幅が必要なときに使う
最大幅整数型扱える最大級の整数が必要なときに使う
高速整数型指定幅以上で、できるだけ高速に扱いたいときに使う

この4つは似ているようで、それぞれ使いどころが違います。
ここを整理して理解すると、型選びがとてもスムーズになります。

固定幅整数型とは何か

固定幅整数型は、名前の通り、ビット幅が明確に決まっている整数型です。
たとえば int8_t なら 8ビット、int64_t なら 64ビットです。

主な固定幅整数型の例

型名意味
int8_t8ビットの符号付き整数型
uint8_t8ビットの符号なし整数型
int16_t16ビットの符号付き整数型
uint16_t16ビットの符号なし整数型
int32_t32ビットの符号付き整数型
uint32_t32ビットの符号なし整数型
int64_t64ビットの符号付き整数型
uint64_t64ビットの符号なし整数型

この型のよいところは、コードを見ただけで「この変数は何ビットなのか」がすぐわかることです。

たとえば、次のように書けば意図がとても明確です。

  • 1バイトのセンサ値を扱いたい → uint8_t
  • 32ビット幅のデータを読み書きしたい → uint32_t
  • 64ビットの大きな値を扱いたい → int64_t

固定幅整数型が向いている場面

固定幅整数型は、次のような場面で特に役立ちます。

向いている場面理由
通信プロトコルデータ長が厳密に決まっているため
バイナリファイル処理フォーマットの幅を正確に合わせたいから
組込み開発レジスタやメモリマップを厳密に扱うため
ハードウェア制御ビット幅のずれが不具合につながりやすいため

このように、「正確な幅」が必要な場面では、固定幅整数型がとても心強い存在です。

最小幅整数型とは何か

最小幅整数型は、「少なくともこのビット幅は必要」という条件を満たす型です。
固定幅整数型と違って、ぴったりその幅である必要はありません。

たとえば、int_least16_t は「少なくとも16ビットある符号付き整数型」です。
実際の環境では 16ビットかもしれませんし、それ以上かもしれません。

主な最小幅整数型の例

型名意味
int_least8_t少なくとも8ビットの符号付き整数型
uint_least8_t少なくとも8ビットの符号なし整数型
int_least16_t少なくとも16ビットの符号付き整数型
uint_least16_t少なくとも16ビットの符号なし整数型
int_least32_t少なくとも32ビットの符号付き整数型
uint_least32_t少なくとも32ビットの符号なし整数型

最小幅整数型の考え方

最小幅整数型は、「このくらいの幅は必要だけれど、ぴったりでなくてもよい」というときに向いています。
移植性を保ちながら、必要最低限の表現力を確保したい場面で使いやすい型です。

たとえば、16ビット以上の範囲が必要で、環境ごとに最適な型を使ってほしい場合には、int_least16_t を選ぶことができます。

最大幅整数型とは何か

最大幅整数型は、その環境で表現できる最大級の整数型です。
符号付きは intmax_t、符号なしは uintmax_t です。

最大幅整数型の例

型名意味
intmax_tその環境で使える最大幅の符号付き整数型
uintmax_tその環境で使える最大幅の符号なし整数型

この型は、できるだけ大きな整数を扱いたいときに使います。
また、さまざまな整数型をまとめて受け止めたいような場面でも役立ちます。

最大幅整数型が向いている場面

向いている場面理由
非常に大きな値を扱いたいときその環境で最大級の範囲を使えるため
幅広い型の値を安全に受けたいときより広い表現範囲を持ちやすいため
汎用的なライブラリ設計環境差を吸収しやすいため

ただし、常にこれを使えばよいというわけではありません。
必要以上に大きな型を使うと、意図がぼやけたり、メモリや処理効率の面で不利になることもあります。
そのため、「本当に最大級の大きさが必要か」を考えて使うことが大切です。

高速整数型とは何か

高速整数型は、「少なくともこのビット幅は必要」という条件を満たしつつ、その環境でできるだけ高速に扱える型です。

たとえば int_fast32_t は、「少なくとも32ビットあること」を満たしながら、通常はその環境で速く扱える整数型になります。

主な高速整数型の例

型名意味
int_fast8_t少なくとも8ビットで高速な符号付き整数型
uint_fast8_t少なくとも8ビットで高速な符号なし整数型
int_fast16_t少なくとも16ビットで高速な符号付き整数型
uint_fast16_t少なくとも16ビットで高速な符号なし整数型
int_fast32_t少なくとも32ビットで高速な符号付き整数型
uint_fast32_t少なくとも32ビットで高速な符号なし整数型

高速整数型の考え方

高速整数型は、必ずしも「ぴったりその幅」ではありません。
たとえば、少なくとも32ビット必要なら、その環境で最も効率よく扱える 32ビット以上の型が選ばれます。

そのため、処理速度を重視しつつ、必要なビット幅の下限も確保したいときに便利です。

4種類の整数型を比べて整理する

ここまでの内容を、違いが見やすいように表で整理してみましょう。

種類代表例保証内容向いている用途
固定幅整数型int32_tちょうど32ビットデータ形式、通信、ハードウェア制御
最小幅整数型int_least16_t少なくとも16ビット必要最小限の幅を確保したいとき
最大幅整数型intmax_t最大級の幅非常に大きな値を扱いたいとき
高速整数型int_fast32_t少なくとも32ビットかつ高速速度を重視したいとき

この表を見ると、それぞれが別々の目的を持っていることがよくわかります。
「何ビット必要か」「速度を優先するか」「最大の範囲が必要か」といった視点で選ぶことが大切です。

標準的な整数型とC99の整数型をどう使い分けるか

ここで気になるのは、「じゃあ int や long はもう使わないのか」という点かもしれません。
もちろん、そんなことはありません。

標準的な整数型は、今でもとても大切です。
特に普通の整数計算であれば、int はとても自然で扱いやすい型です。
一方で、ビット幅や移植性を強く意識する場面では、stdint.h の型が頼りになります。

使い分けの目安

場面向いている型
一般的な整数計算int
幅を厳密に決めたいint32_t、uint8_t など
最低限の幅を保証したいint_least16_t など
できるだけ速く扱いたいint_fast32_t など
最大級の整数が必要intmax_t、uintmax_t

つまり、「普通の整数なら int、幅を意識するなら stdint.h 系」という考え方を持っておくと、とても使いやすいです。

サンプルプログラム

C99の整数型に実際に値を入れて、サイズと値を確認するシンプルなプログラム例です。

ファイル名:16_3_1.c

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void)
{
    /* C99の整数型に値を入れて確認する */
    int8_t temp_diff = -12;
    uint8_t sensor_level = 200;
    int_least16_t year_offset = 300;
    int_fast32_t total_score = 120000;
    intmax_t big_total = 1234567890123;

    printf("C99の整数型を確認してみましょう。\n\n");

    printf("int8_t のサイズは %zuバイト、値は %" PRId8 " です。\n",
           sizeof(temp_diff), temp_diff);

    printf("uint8_t のサイズは %zuバイト、値は %" PRIu8 " です。\n",
           sizeof(sensor_level), sensor_level);

    printf("int_least16_t のサイズは %zuバイト、値は %" PRIdLEAST16 " です。\n",
           sizeof(year_offset), year_offset);

    printf("int_fast32_t のサイズは %zuバイト、値は %" PRIdFAST32 " です。\n",
           sizeof(total_score), total_score);

    printf("intmax_t のサイズは %zuバイト、値は %" PRIdMAX " です。\n",
           sizeof(big_total), big_total);

    return 0;
}

このサンプルプログラムでわかること

このプログラムでは、固定幅整数型、最小幅整数型、高速整数型、最大幅整数型をひとつずつ使っています。
そのため、それぞれの型が「どういう目的で存在しているのか」を実感しやすくなっています。

特に注目したいのは次の点です。

注目ポイント内容
int8_t必ず8ビットであることを意識できる
uint8_t1バイトの符号なしデータを明確に扱える
int_least16_t少なくとも16ビット必要な値に使える
int_fast32_t速度を意識した選択肢になる
intmax_tとても大きな整数を扱える

また、この例では inttypes.h を使って表示用のマクロも利用しています。
これは、C99の整数型を printf で安全に表示するためにとても大切です。

printf でC99の整数型を表示するときの考え方

固定幅整数型などを printf で表示するときは、環境差を意識して inttypes.h のマクロを使うのが安心です。

たとえば、次のようなマクロがあります。

表示用マクロの例
int8_tPRId8
uint8_tPRIu8
int_least16_tPRIdLEAST16
int_fast32_tPRIdFAST32
intmax_tPRIdMAX

これを printf の書式文字列に組み込んで使います。

例としては、次のような形です。

printf("%" PRId8 "\n", value);

こうしておくと、環境が変わっても型に合った表示をしやすくなります。
移植性を高めたいなら、この点もぜひ意識しておきたいところです。

この図は、C99で追加された整数型を全体的に整理して眺めるための図です。
最初は型名がたくさん出てきて少し複雑に感じますが、4つのグループに分けて見ると役割がかなりはっきりします。

固定幅、最小幅、最大幅、高速型という分類が頭の中で整理されると、型名を見たときにも意味を読み取りやすくなります。

この図は、「結局どの型を選べばよいのか」を感覚的に整理するための図です。
型の種類を覚えるだけではなく、どんな目的に向いているのかを結び付けて理解することで、実際のコードの中でも使い分けやすくなります。

特に大事なのは、すべての場面で固定幅整数型を使うのではなく、目的に合わせて使い分けることです。
一般的な整数計算なら int が自然ですし、厳密な幅が必要なら int32_t や uint8_t のような型が力を発揮します。

移植性を高めるために意識したいこと

移植性を高める整数型の選び方では、「どの型が便利か」だけでなく、「なぜその型を選ぶのか」を意識することがとても大切です。

たとえば、次のように考えると選びやすくなります。

考えること向いている選択
ぴったりのビット幅が必要か固定幅整数型
最低限の幅だけ保証できればよいか最小幅整数型
速度を優先したいか高速整数型
最大級の整数が必要か最大幅整数型
普通の整数計算かint

このように、型選びにはちゃんと理由があります。
C99の整数型は、その理由をコードに表しやすくしてくれる仕組みとも言えます。

整数型を何となく選ぶのではなく、「この変数にはこの幅が必要」「ここは移植性を優先したい」「ここは速度を重視したい」と考えながら選べるようになると、C言語のコードはぐっと堅実で読みやすいものになっていきます。