見出し画像

C言語教室 第11回 - プリプロセッサとマクロ

C言語のプログラムを書く時におまじないのように最初に書くことが多い #include <stdio.h> ですが、そろそろ、このおまじないの說明をしておきましょう。またC言語には定数に名前を付ける機能が無いので、何かの制限値であるとか、いつもの文字列を生で毎回、書いているかもしれません。

#で始まる行は、プリプロセッサと呼ばれるコンパイルに先立つ「前処理」に使われる行です。それなりに多くのコマンドがあるのですが、今回は代表的なものだけ説明します。

  • #include <ファイル名>

この命令がある行を指定したファイル名の中身と置き換えます。ここで読み込むファイルの中には利用するライブラリ関数の宣言が書いてあります。これでCコンパイラはライブラリ関数を使う時に正しい引き数の数と型、戻り値の型を確認することができるんですね。C言語の場合はヘッダファイルの拡張子は”.h”です。<>で囲まれているファイル名は環境で決められているシステムのディレクトリにあるのですが、これを見つけるのは少し面倒かもしれません。環境変数includeを見たり、それらしいフォルダから”*.h”を検索するなどして場所を見つけてください。ただ開いてみても、決してわかりやすく書いてある訳では無いので、使いたい関数の名前があることだけわかれば充分です。

  • #define 名前 値

ここで定義された「名前」を見つけると、それ以降の行で名前を見つけるたびに「値」と置き換えます。例えば

#define MAX_LINE 50

という行があるとすると、

int max = MAX_LINE;

という行は

int max = 50;

に置き換えられます。#define はC言語の命令ではなくプリプロセッサという「文字列」を扱う処理なので注意してください。定義の行末にセミコロンは不要です。

#define MAX_LINE 50;

としてしまうと先の例だと

int max = MAX_LINE;;

と展開されます。余分なセミコロンがあってもエラーにはなりませんが、

int max = MAX_LINE + 1;

としたときに、

int max = 50; + 1;

となったら意図しない結果になりますよね。また同じ名前の定義は出来ません。既に定義されている名前に値を定義しようとするとエラーになります(どうしても値を変えたいときには #undef してから #define をやり直せば可能ではあります)。

もちろん #define は文字列の値を定義することもできます。

#define HELLO_MESSAGE “hello.\n”

と定義しておけば

printf(HELLO_MESSAGE);

printf(“hello.\n”);

と展開されます。なお文字列リテラルに定義されていた名前が含まれても展開はされません。

#include <stdio.h>

#define MAXLINE 50

void main() {
  printf("MAXLINE\n");
}

を実行すると、ちゃんと50とならずに文字列が表示されます。

MAXLINE

いずれにせよプリプロセッサはC言語の言語上の型などと無関係に文字列を文字列で置き換えるだけなので、注意して使わないと意図しない展開が行われることがあります。

#include <stdio.h>

#define PARAM 5+4

void main() {
  printf("%d\n", PARAM*3);
}

は、printfの中の式が

printf(“%d\n”, 5+4*3);

と展開されるので、答えは17になります(なぜかブラウザ環境では27になるのですが)。これを避けるためには定義の時点で括弧をつけておくと安心です。

#define PARAM (5+4)

これで

printf(“%d\n”, (5+4)*3);

と展開されることになるので、意図した通りの結果が出るはずです。

ところで #define で定義する名前は普通は大文字のみを使います。C言語の変数や関数の名前は小文字のみで書くのが普通なので、これらと区別するのが目的です。コードを読んでいる時に大文字で書かれていれば「ああ、これはきっとマクロなんだ」と理解できるというだけで、小文字を使ってもエラーになるわけではありません。実際に関数やグローバル変数を置き換える目的で小文字の名前で定義されているマクロもあります。いずれにせよ大文字で書かれているマクロが展開されれば、C言語的には小文字ばかりになるのでひと安心です。なおC言語の変数や関数の名前に大文字が使えるかどうかは、C言語というよりもリンカのお仕事で言語自身では何も決めていませんが、区別はされます。

ところで #define には関数のように引き数を定義することもできます。

#define MAX(x,y) ((x) > (y) ? (x) : (y))

という定義があれば、

i = MAX(5, 8);

i = ((5) > (8) ? (5) : (8));

と展開され、i には 8 が代入されます。ここで 5 や 8 といったリテラルを使いましたが、もちろん変数であるとか式を書いても問題ありません。括弧がしつこく書いてあるのは、x や y に式が書かれた時に、演算子の優先度の関係で式の意味が変わってしまうのを防ぐためです。

プリプロセッサ命令には他にもありますし、マクロの置き換え演算子もあるのですが、この辺りは複数ファイルを扱うようなり自分でヘッダを書くようになってからあらためて説明したいと思います。

プリプロセッサ命令については、もう少し知りたい人は以下を読んでみてください。

プリプロセッサ

次回は、今までのまとめと振り返りを予定しています。


今回はコードを書くのではなく、說明をする文章題です。

課題

說明で使った MAX(x,y) というマクロで、引き数にインクリメント演算子を含んだ変数を書くと何が起こるか考察しなさい。

演習

プリプロセッサが自分の書いたコードを、どのように展開しているのかを確認する方法を調べなさい。


公開して気がついたのですが、#で始まる部分がハッシュタグと解釈されているところがあるのですが、これを回避する書き方がわかりません。\とか.ではダメみたい。noteのエディタはどのフレームワークで作られているかわかれば見つけられるかもしれないけど、独自実装だったりするのかなぁ。と悩んでいたら、ハッシュタグに登録したものを本文中で使うのがダメだったようです。#defineをタグからはずしたら大丈夫になりました。なるほどね。

ヘッダ画像は "stdio.h" の一部。

この記事が気に入ったらサポートをしてみませんか?