- バックアップ一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- 授業/C言語基礎/前処理 へ行く。
- 1 (2016-01-10 (日) 15:38:57)
- 2 (2016-01-10 (日) 15:55:27)
- 3 (2016-01-12 (火) 16:40:42)
- 4 (2016-01-15 (金) 03:26:31)
Cプログラムをコンパイルすると、まず前処理が行われ、その後、機械語への翻訳が行われます。 前処理を行うための命令をプリプロセッサーと言います。
前処理では、主に、ヘッダー・ファイルの読み込みと、マクロ定義の置き換えが行われます。
ヘッダー・ファイル †
#include †
printf関数は他のファイルで定義された関数であるのに、externを付けたプロトタイプ宣言なしで使っていました。
実は、printf関数のプロタイプ宣言は、stdio.h というヘッダー・ファイルに、以下のように書いてあります。
extern int printf(const char* ...);
(printf関数は、第1引数の文字列に含まれる変換指定子の数によって引数の数が変わるので、定義やプロトタイプ宣言が特殊な形をしています。)
これを #include プリプロセッサーでCプログラムに読み込むことによって、printf関数が使えるようになります。
/* * Hello World! */ #include <stdio.h> int main(void) { printf("Hello World!\n"); return 0; }
< >と" " †
C言語には標準でいくつかのヘッダー・ファイルが用意してあります。
標準で用意されているヘッダー・ファイルは、ヘッダー・ファイルの名前を不等号の括弧 < > で囲んで指定します。
ヘッダー・ファイルは、自分で用意することもできます。
自分で用意したヘッダー・ファイルは、ヘッダー・ファイルの名前をダブル・クォーテーション " " で囲んで指定します。
例えば、次のようなプログラムとヘッダー・ファイルを用意します(プログラム1a, 1b)。
/* * add.c: 和を求める */ double add(double x, double y) { return x + y; }
/* * add.h */ extern double add(double, double);
このヘッダー・ファイルを読み込むには、ファイル名をダブル・クォーテーション " " で囲んで、次のように指定します(プログラム1c)。
/* * main.c: 和を求めて出力する */ #include <stdio.h> #include "add.h" int main(void) { double x = 1.0, y = 2.0; double z = add(x, y); printf("%f\n", z); return 0; }
main.c をコンパイルすると、プリプロセッサーによって add.h が読み込まれてから翻訳が行われます。
演習1 †
プログラム1a, 1b, 1cを作成し、コンパイルせよ。
マクロ定義 †
#define †
#defineプリプロセッサーはマクロ定義を置換します。
#define マクロ名 文字列
プログラムをコンパイルすると、まずマクロ定義に記述されたマクロ名が文字列に置き換えられ、それからプログラムが翻訳されます。
引数を持ったマクロを定義することもできます。
マクロ定義の使用例 1: 配列の要素数 †
/* * 二次元配列を宣言し、すべての要素に0を代入する */ #include <stdio.h> #define N 4 int main(void) { int a[N][N]; int i, j; for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { a[i][j] = 0; } } return 0; }
初期のC言語 (K&R, ANSI C, C89, C90) では配列の要素数を変数にして宣言することができなかったので、このような使い方がよく行われていました。
マクロ定義の使用例 2: 列挙型の代わり †
/* * キーボードから0-3を入力すると、(0, 0)から対応する方向へ1マスずつ進む */ #include <stdio.h> #define NORTH 0 #define EAST 1 #define WEST 2 #define SOUTH 3 int main(void) { int x = 0, y = 0, f = 0; while (1) { printf("0-3のいずれかを入力してください:\n"); scanf("%d", &dir); switch (dir) { case NORTH: y++; break; case EAST: x++ break; case WEST: x--; break; case SOUTH: y--; break; default: f = 1; } if (f) { break; } printf("(%d, %d)へ移動しました\n"); } return 0; }
最初のC言語 (K&R) には列挙型がなかったので、このような使い方がよく行われていました。
モダンなC言語では列挙型を使います。
enum { NORTH, EAST, WEST, SOUTH };
マクロ定義の使用例 3: 定数の代わり †
/* * 円の面積と円周を求める */ #include <stdio.h> #define PI 3.1415 int main(void) { double r; printff("半径を入力してください:\n"); scanf("%d", &r); double s = PI * r * r; double c = 2 * PI * r; printf("面積は%f、円周は%fです\n", s, c); return 0; }
ただし、定数 PI が定義されるわけではありません。
マクロ定義の使用例 4: 関数定義の代わり †
/* * エラー・メッセージを出力する */ #include <stdio.h> #define error(x) printf("Error %d\n", x) int main(void) { error(1); return 0; }
ただし、error関数が定義されるわけではありません。
マクロ定義の使用例 5: 関数として定義できないもの †
/* * 配列の要素をすべて書き出す */ #include <stdio.h> #define length(a) sizeof(a) / sizeof(a[0]) int main(void) { int a1[] = { 0, 1, 2, 3, 4 }; double a2[] = { 0.0, 1.1, 2.2, 3.3 }; int i; for (i = 0; i < length(a1); i++) { printf("%d: %d\n", i, a1[i]); } for (i = 0; i < length(a2); i++) { printf("%d: %f\n", i, a2[i]); } return 0; }
関数では引数として渡された配列について全体のサイズを調べることができないため、配列のサイズを求めて返す関数は作れません。
また、関数では引数の型を指定しなければならないため、異なる型を引数として受け取る関数を作ることはできません。
マクロ定義の注意点 †
マクロ定義は、単に置換されるだけなので、置換後にどうなるかに注意する必要があります。
たとえば、次のプログラムについて考えてみましょう。
/* * macro.h */ #define X 1 + 1
/* * macro.c: Xの2倍の値を出力する */ #include <stdio.h> #include "macro.h" int main(void) { int y = X * 2; printf("%d\n", y); return 0; }
main関数だけを見ると、X の2倍を求めようとしているように見えます。
このプログラムをコンパイルすると、まず、マクロ X が 1 + 1 に置き換えられます。 したがって、前処理が行われ、実際に翻訳されるプログラムは次のようになります。
#include <stdio.h> int main(void) { int y = 1 + 1 * 2; printf("%d\n", y); return 0; }
X が 1 + 1 に置き換えられ、式が 1 + 1 * 2 になりました。 すると、加算演算子 + よりも乗算演算子 * の方が優先順位が高いので、計算結果は X の2倍の 4 ではなく、3 になってしまいます。
これを防ぐためには、優先順位が変更されないように ( ) をつけて定義します。
/* * macro.h */ #define X (1 + 1)
まとめ †
前処理(プリプロセッサー)には、主に、ヘッダー・ファイルの読み込み (#include) と、マクロ定義の置換 (#define) があります。
Cプログラムをコンパイルすると、前処理が行われてから、翻訳が行われます。