Cプリプロセッサとは,C言語のコンパイラが機械語への翻訳を始める前に,指示され た内容の前処理(プリプロセス)を行うプログラムのことをいう.Cプリプロセッ サへの指示は次のようなプリプロセッサ指令として,C言語のソースプログラ ム中に記述しておく.
#プリプロセッサ指令の種類
行の最初に空白があってはいけない点に注意すること.プリプロセッサ指令 の種類には,主なものとして次が用意されている.
#include <ファイル名> //標準ライブラリ用のヘッダファイルを読み込む #include "ファイル名" //自作・ローカルなヘッダファイルを読み込む #define 定義したい名前 置換内容 //定数などの定義 #pragma プラグマ名 プラグマ用の記述 //そのコンパイラ専用に特別に用意されたプリプロセッサ指令
このプリプロセッサ指令は,C言語の文法とは関係ない別の 文法なので注意すること.特に,文末に「;」は必要なく,逆に 「;」を書くとエラーの原因となる.
#include <ファイル名> #include "ファイル名"
ヘッダファイルとは,自分のプログラムに取り込むことができるように用意 された,あらかじめプログラムを書いておいたファイルのことをいう.
ヘッダファイルの中身はC言語のプログラムそのもので,通常は定数の定義や,標準ライ ブラリ関数のプロトタイプ宣言などが書かれ ている.ヘッダファイルは,例えば「stdio.h」のように,拡張子が「.h」とす るようなファイル名で保存されている.
ヘッダファイルには,あらかじめC言語に標準で組み込まれている標準ライブ ラリ関数を呼び出すための準備部分が書かれた標準ライブラリ用のヘッダファイ ルと,自作のヘッダファイルやシステム固有の特殊なヘッダファイルなどが存在 する.
標準ライブラリ用のヘッダファイルは,最初から決まったフォルダに保存さ れている.この決まったフォルダに存在するヘッダファイルを読み込むには一番 目の方法を利用する.
#include <標準ヘッダファイル名>
一方自作のヘッダファイルや,特殊なヘッダファイルはC言語プログラムの ソー スファイルと同じフォルダに保存しておき,二番目の方法で指定する.
#include "自作ヘッダファイル名";
例えば「hoge.h」として次のようなヘッダファイルがあったとする.
/* hoge.h */ #define ABC 123 #define EFG 456 int funcA(float); int funcB(double);
そしてこのhoge.hを利用する次のソースプログラムを書いたとする.hoge.h とこのソースプログラムは同じフォルダに保存しておく.
#include "hoge.h" int main(void) { なにかプログラム }
このプログラムをビルドすると,まずプリプロセッサが起動して先のソース プログラムは次のように変換され,まるまるhoge.hの中身を読み込んだ状態にな る.
/* hoge.h */ #define ABC 123 #define EFG 456 int funcA(float); int funcB(double); int main(void) { なにかプログラム }
プリプロセッサ指令がすべて置き換えられて,C言語の文法だけになったこの 状態で機械語への翻訳が実行されることになる.
#define 定義したい名前 置換内容
プログラム中に,定数を書いておくことも多いが,気をつける点が
そこで,定数に名前を付けることにする.例えば,次のように円周率を定義 することができる.
#define PI 3.14
これでプログラム中では「PI」という名前を「3.1415」という定数の代わり に使用することができる.
#define PI 3.1415 //円周率 int main(void) { double length; //円周 double area; //面積 double radius; //半径 radius = 10.0; //半径を設定 length = radius * 2.0 * PI; //円周の長さ=直径×円周率 area = radius * radius * PI; //円の面積=半径×半径×円周率 ...以下略... }
このプログラムはCプリプロセッサが処理すると次のように置換されて処理さ れる.
int main(void) { double length; double area; double radius; radius = 10.0; length = radius * 2.0 * 3.14; area = radius * radius * 3.14; ...以下略... }
定数を定義して使用することにより,先の欠点を解消できる.
定数の定義の他にも,変数名を置換したりするのにも使用される.この#defineは, 使用しなくてもプログラムを書くことができるものの,わかりやすく修正しやす いプログラムを書くためには積極的に利用した方がよい.
#pragma プラグマ名 プラグマ用の記述
プリプロセッサ指令の中には,コンパイラごとに特有な機能を追加すること もある.例えば,microsoft社のもの,intel社のものなどそれぞれに異なる機能 を提供するため,#pragmaが用意されている.
実験で使用するNC30には,M16CのメモリマップドI/OやSFRにアクセスするた めにアドレスを指示するためのプラグマ「#pragma ADDRESS」が用意されている.
#pragma ADDRESS 名前 番地
例えば,次の例ではda0という名前を16進数のアドレス03d8番地として定義す る.
#pragma ADDRESS da0 03d8H
この上で,定義された名前da0のデータサイズを次の変数宣言で行う
unsigned char da0;
これで03d8番地にunsigned(符号なし)char型(8bit)のデータがあることを示し,それに「da0」の 名前でアクセスすることができるようになる.C言語のプログラム中では,da0を 普通の変数と同じように使用できる.ただその変数da0のアドレスが03d8番地に 固定されている点だけが,普通の変数と異なる点である.
また,通常のC言語では記述できない,割り込みルーチン(関数)を指示するためのプ ラグマが,「#pragma INTERRUPT」である.
#pragma INTERRUPT 関数名
例えば,次のようにifuncを指定すると,関数ifuncが割り込みルーチンとし て使用できる.割り込みルーチンは,通常の関数呼び出しではなく,割り込みに よって突然呼び出されるため,通常のように仮引数 を持つことができず,また返値を持つことも許されない.このため,データを受 け渡さなくてもよいvoid型の関数でなければならない.
#pragma INTERRUPT ifunc void ifunc(void) { 割り込み処理のプログラム内容 }
割り込み処理については別ページで解説する.
厳密に実行時間をスケジュールしたプログラムを書いたり,特殊なハードウェ アを制御したりするような場合など,C言語で記述することができないようなプ ログラムも存在するかもしれない.その場合には,結局アセンブリ言語を使った プログラムを書くことになる.しかし,そうした特殊な部分がごく一部だとした ならば,わざわざアセンブリ言語のソースプログラムファイルを用意するのでは なく,それをC言語のプログラムの一部に埋め込めたら便利である.
そこで,次のようにC言語のプログラムの一部に,アセンブリ言語のプログラ ムを埋め込むことができるように,「#pragma ASM」が用意されている.
void wait(void) { #pragma ASM //ここからアセンブリ言語 MOV.W #0FFFH,A0 LOOP: NOP NOP NOP DEC.W A0 JNZ LOOP #pragma ENDASM //ここまでアセンブリ言語 }