C言語

【注意】32ビットのシステムで64ビットの演算

はじめに

同じ型どうしの計算でも、その型で扱える値域を超えることが分かっている場合は、
大きな型にキャストして演算する必要があります。

皆さんも1度は経験したことがあるのではないでしょうか。
意図した通りの計算結果を得られず、不具合を埋め込んでしまった経験が(笑)

CPUのビット数を超える型を扱う場合は、特に注意が必要です
例えば、32ビットのシステムで、64ビットの演算を行う場合などです。

本記事では、なぜ意図した結果が得られない場合があるのか、
型の値域を超えるとどうなるのかを説明いたします。

 

コンパイラの気持ち

C言語では、特に指定のない定数などは INT 型として扱われます。
演算においても、極力 INT 型で済ませようとします。

SANACHAN
SANACHAN
INT型は、32ビットのシステムであれば32ビット、16ビットのマイコンであれば16ビットの場合もあります。ただし、64ビットのシステムでも INT 型は32ビットです。

 

演算を実行するプロセッサは、表面上は8ビットや16ビットの演算をサポートしている場合もありますが、
内部的にはプロセッサ自身のビット数、例えば32ビットで演算を行ったうえで値を切り詰めています。
そのため、INT 型で演算を行った方が、処理性能が良くなる場合があります。

SANACHAN
SANACHAN
そのため、コンパイラは極力 INT 型で演算するアセンブラを出力します。

 

オーバーフロー

INT 型で表現できる数値は「-2,147,483,648」から「2,147,483,647」です。
演算を行った結果、この値域を超える場合はオーバーフローが発生します。

SANACHAN
SANACHAN
一周回って、意図した計算結果になりません。

 

UNIX 系の OS でオーバーフローといえば、最も有名なのが「2038年問題」です。
実時間を1970年からの経過秒の INT 型で管理しており、2038年でオーバーフローする問題です。

この問題は、「Linux/Windowsにおける2038年問題の対策」で詳しく解説しています。

 

実際にどのようなことが起きるか見てみましょう。

SANACHAN
SANACHAN
コンパイル時の -m32 は、64ビットのGCCで32ビットとしてコンパイルする際に必要となるものです。詳しくは、「64ビットのGCCで32ビットのバイナリを生成する方法」をご覧ください。

 command
$ gcc -m32 -o test32 -O2 main.c
$ ./test32
RESULT: 100000000

演算結果は「100000000」ですので、INT 型で表現できます。
では、次の演算はどうでしょうか。

 command
$ gcc -m32 -o test32 -O2 main.c
$ ./test32
RESULT: -727379968

結果の期待値は「1000000000000」ですが、負の値になりました。
INT 型で表現できる地域を超えたため、オーバーフローを起こして正しい結果になりませんね。

 

間違ったオーバーフローの対応

冒頭でも少し触れましたが、同じ型どうしの計算でも、その型で扱える値域を超える場合は、
大きな型にキャストして演算する必要があります。

先ほどのプログラムを以下のように変更して試してみましょう。

 command
$ gcc -m32 -o test32 -O2 main.c
$ ./test32
RESULT: -727379968

関数の戻り値、呼び出し元で受け取る変数を Long Long 型(64ビット)に変更しましたが、
問題は解決されませんでした。

SANACHAN
SANACHAN
この現象が、本記事で伝えたい内容です。

 

先ほどの「コンパイラの気持ち」を思い出してみてください。
定数は INT 型として扱われますので、計算部分は「INT型 × INT 型 × INT 型」となり、
INT 型で計算を行った後に、Long Long 型(64ビット)にキャストして戻るコードになります。

SANACHAN
SANACHAN
演算自体は INT 型の32ビットで行われるため、問題解決になりません

 

正しいオーバーフローの対応

今回のように、32ビットのシステムで64ビットの演算を行う場合など、
プロセッサのビット数を超える型を扱う場合は、特に注意が必要です

正しくは、以下のように記述します。

 command
$ gcc -m32 -o test32 -O2 main.c
$ ./test32
RESULT: 1000000000000

SANACHAN
SANACHAN
期待した結果が得られました。

 

GCC は、演算式の中のいずれかが Long Long 型であれば大丈夫ですが、
ANSI C で特に規定されていない内容となるため、全ての項を64ビットとしておいた方が無難です。
左辺を優先するコンパイラや、右辺を優先するコンパイラもあります。

ポイント

  • 全ての項を Long Long 型(64ビット)にキャストする
  • 定数は LL を末尾に付け、Long Long 型として扱うように指定する

 

こちらの記事もよく読まれています

  • この記事を書いた人
  • 最新記事
SANACHAN

SANACHAN

「生涯一エンジニア」を掲げ、大手グローバル企業でSE/PGとして8年勤め、キャリアアップ転職した現役のエンジニアです。世にあるメジャーな全プログラム言語(コボル除く)を自由に扱えます。一児の父。自分のため、家族のため、日々勉強してます。システムエンジニア、プログラミングに関する情報を蓄積している雑記帳です。

-C言語
-, , , ,