GAIO CLUB

2023年05月22日

【第8回】CコンパイラとCPUの関係

静的解析/コンパイラ技術
今回から、Cコンパイラのお話をします。
と言っても、コンパイラ内部の話ではありません。コンパイラが生成するコードや、そのコードが動く時の話です。

でもその前に、CPUとコンパイラとの関係について述べておこうと思います。
以前ガイオは、組み込み向けのクロスCコンパイラを提供していました。ここに記載するコンパイラの話もクロスCコンパイラを前提とした話です。CPUとは、Cコンパイラが動作するCPUではなく、コンパイル後のプログラムが動作するCPUを指しています。

CPUとコンパイラ

Cコンパイラは、C言語プログラムをアセンブリ言語(機械語)に変換します。
固有の言語拡張をしているC言語もありますが、拡張仕様を使わないC言語プログラムであれば、Cコンパイラにより、いろいろなCPU上で動作するプログラムを作ることができます。

C言語プログラムを使うためには、新しいCPUを開発した時にCコンパイラも作る必要があります。どんなCPUに対してもCコンパイラが作れるかというと、そうでもありません。Cコンパイラを作れないCPUもあります。

Cコンパイラを作れるかどうかは、アセンブリ言語の仕様に依存します。

8ビット・16ビット・32ビット…

CPUのビット数だけの理由でCコンパイラが作れないということはありません。
一般的に機械語命令が豊富なハイエンドCPUの方がコンパイラの作成は容易ですが、8ビットマイコン向けのCコンパイラも作れます。

CPUのビット数は、そのCPUが扱いやすいデータのビットサイズと関係がありますので、int型の大きさに影響を与えます。16ビットCPUのint型は2バイト、32ビットCPUは4バイト、というような感じです。

非力なCPU向けのプログラムは動作時の性能が落ちます。CPUの基本性能もそうですが、機械語命令も非力なので、Cコンパイラはライブラリを多用するようになります。

たとえば、8ビットCPUには4バイトデータ用の演算命令がありません。しかし、C言語にはlong型があります。long型変数の加算を機械語に変換するだけでも数多くの命令が必要になりますから、演算ライブラリを用意しておき、これを呼び出すようになります。floatやdoubleも、全て演算ライブラリによる処理が行われることになります。

いくつもの演算ライブラリを用意しておき、演算のたびにライブラリ呼び出し命令を生成する8ビット向けのCコンパイラは、そういうコードを生成するコンパイラになります。

もしかしたら、アセンブリ言語を読める方がいらっしゃるかもしれないので、Cコンパイラが生成した懐かしいマイコンのアセンブリコードをお見せします(関数部分のみ)。使用したCコンパイラはGAIO製です。
※:Z80のcall命令部分は、2バイトの乗算命令が無いために演算ライブラリを呼び出しています。

ワードマシン

ワードマシンと呼ばれるCPUがあります。
多くのCPUではメモリの1アドレスが8ビットデータを指していますが、16ビットデータを指すようなCPUのことを言います。DSPなどで見られますが、ワードマシン用のCコンパイラもあり、GAIOも作ったことがあります。

ワードマシンにどう対応するかはCコンパイラによります。char型もshort型も同じ16ビットサイズになるかもしれませんし、charはそのうちの8ビットだけを使用するようになるかもしれません。

GAIO製コンパイラが生成した、あるDSPのアセンブリコードをお見せします(コメントを追加しています)。
ちなみに、このDSPの場合、sizeof演算子で得られる値は、char・shortが1、long・floatは2、doubleは4です。

FPU

FPU内蔵のCPUでは、浮動小数点演算の機械語を使います。

FPUが無ければ、8ビットCPUの説明に書いたように、ライブラリ呼び出しになります。
通常、FPUには浮動小数点演算の振る舞いを指定する制御設定があり、丸め処理等の制御が可能です。

以前、浮動小数点についてお話をしましたが、丸め処理にはいくつかの種類があり、FPU命令の丸め処理は、FPUの制御設定値によって変わります。ライブラリによる浮動小数点演算処理であればコンパイラが丸め処理を決めますが、FPUの場合は使用者が決めることになります。

たとえば、浮動小数点から整数への型変換(代入)では、小数値を切り捨てるのがC言語の仕様ですが、FPUの制御設定次第では、この仕様から外れることも起ります。

余談ですが、浮動小数点数から整数へ代入する際に、切り捨てではなく、四捨五入にしたいときは0.5を加算すると良いです。

エンディアン

CPUには、ビッグエンディアンデータを扱うものとリトルエンディアンデータを扱うもの、さらには両方扱えるものがあります。

変数をそのまま演算に使用するのであれば、コンパイラがエンディアンの配慮をする必要はありません。しかし、メモリ上の変数を小さな型にキャストして使用する場合には、コンパイラが機械語の調整を行います。
両エンディアンを扱えるCPUの場合、通常、コンパイラはビッグとリトルの2種類のライブラリを提供します。

エンディアン以外にもライブラリの区別が必要なことはあり、コンパイルオプションに合う多種のライブラリを提供するCコンパイラもあります。

アラインメント

データサイズに関するアドレス境界制限は、CPUによりさまざまです。

アラインメント制限についてもコンパイラがうまく変数を配置してくれるので、使用者が気にする必要はないのですが、異なる型の変数アドレスをポインタに入れるようなプログラムでは、コンパイラの対処ができません。

そこは、プログラム作成者が配慮しなければなりません。

アラインメントは構造体のメンバ間に境界調整のための空きを作りますから、同じ構造体でもCPUが変わると構造体全体のサイズが変化します。
CPUとコンパイラの関係については、他にもレジスタなど密接に関係するリソースがありますが、それらについては次回以降にお話したいと思います。

筆者紹介

浅野 昌尚(あさの まさなお)

ガイオ・テクノロジー株式会社

開発2部 QTXグループ

1980年代から30年以上にわたり汎用構造のCコンパイラ開発に従事し、その間に8ビットマイコンからRISC・VLIW・画像処理プロセッサまで、さまざまなCPU向けのクロスCコンパイラを開発。

人気のコラム

最新のコラム