MMGamesロゴ  MMGames
Twitterシェアボタン  Facebookシェアボタン   
 English 

しんで覚えるC言語
しんで覚えるC言語

簡易的な関数の実現

#define疑似命令の高度な機能
第1節で紹介した#define疑似命令は定数を宣言する疑似命令ですが、
実は、それ以上に高度な機能を持ち合わせています。
#define疑似命令による定数は、単なる置き換えによって実現されていますが、
これを利用すると特殊な処理を行わせることも可能です。

たとえば、変数の中身を画面に表示するにはprintf文を次のように使用します。

enumに数値を指定
printf("temp = %d\n", temp);

しかし、変数tempの値をあちこちで表示する必要がある場合に、
この文を毎回打ち込むのが面倒であれば、#define疑似命令で置き換えることができます。

ソースコード
#include <stdio.h>

#define PRINT_TEMP printf("temp = %d\n", temp)

int main(void)
{
    int temp = 100;
    PRINT_TEMP;
    return 0;
}

このプログラムの実行結果は、次の通りになります。

実行結果
temp = 100

このプログラムのトリックは、#define疑似命令の定義内容にあります。
#define疑似命令の機能は単なる置き換えで、数値でなくとも何でも置き換えできます。
ここでは、PRINT_TEMP が変数を表示するprintf文にそのまま置き換わっています。
つまり、プログラム的にはその部分にprintf文を書いているのとまったく同じです。

これは、あらゆるプログラムを強引にまとめることができる、非常に強力な機能です。
しかし、上記のテクニックを乱用すると、プログラムが省略表現だらけになってしまって、
最初にプログラムを書いた人にしか読めなくなってしまうので、十分に注意して使用する必要があります。
マクロという簡易関数
前項で見た通り#define疑似命令は非常に強力ですが、
実は、#define疑似命令にはさらに強力な機能が備わっています。
#define疑似命令で簡単な関数を作ってしまうことが可能です。

#define疑似命令では、名前の後に()で文字を指定すると、
以後の置き換える内容で同じアルファベットの部分を置き換えることができます。
たとえば、次の#define疑似命令は、指定されたint型の変数を画面に表示します。

ソースコード
#define PRINTM(X) printf("%d\n", X)

次のプログラムは、先ほどの#define疑似命令を使う例です。

ソースコード
#include <stdio.h>

#define PRINTM(X) printf("%d\n", X)

int main(void)
{
    int a1 = 100, a2 = 50;
    PRINTM(a1);
    PRINTM(a2);
    return 0;
}

このプログラムの実行結果は、次の通りになります。

実行結果
100
50

ここの#define疑似命令では、名前の後の()の中でXが指定されています。
これによって、以後の置き換え文の中のXは、
#define疑似命令を使用した時の()内の記号に置き換えられます。
先ほどの例では、Xがa1やa2に置き換わった内容になったのです。

この機能や使い方を見る限り、これはまるで関数のようです。
実際、この機能は簡易的な関数の変わりに使われており、マクロと呼ばれています。

キーワード
【マクロ】

#define疑似命令による置き換えで式などを簡単に表現すること。


マクロの使い方は普通の関数とまったく同じですが、仕組みは大きく異なります。
関数の場合、その実態は1か所にあり、必要な時に呼び出されて使われます。
しかし、マクロでは、使用している場所のプログラムそれ自体が置き換わり、
呼び出しなどの作業が必要ないため、若干高速になります。

しかし、マクロを使う場所すべてが置き換わるので、あまり巨大なマクロを作ると、
その為にプログラムのサイズが極端に大きくなることもあります。

その為、一般的には、マクロは、決まり切った数式などに利用されます。
たとえば、次のマクロは台形の面積を求めるマクロです。

ソースコード
#include <stdio.h>

#define GET_TRAPEZOID_AREA(A, B, H) (A + B) * H / 2

int main(void)
{
    int up, down, h, s;

    printf("上底、下底、高さ:");
    scanf("%d,%d,%d", &up, &down, &h);
    s = GET_TRAPEZOID_AREA(up, down, h);
    printf("面積:%d\n", s);
    
    return 0;
}

このプログラムの実行結果は次の通りになります。

実行結果
上底、下底、高さ:5,10,8
面積:60

この様にすれば、決まり切った数式を何度も入力する必要がなくなります。
副作用の恐怖
#define疑似命令によるマクロは手軽で便利なのですが、
使い方を間違えると思わぬ現象に遭遇することがあります。

たとえば、前回の台形の面積を求めるプログラムに置いて、
なんらかの事情で、高さを常に+3しなければならない場合を考えてみます。
次のプログラムは、そのように変更してみた例です。

ソースコード
#include <stdio.h>

#define GET_TRAPEZOID_AREA(A, B, H) (A + B) * H / 2

int main(void)
{
    int up, down, h, s;
    
    printf("上底、下底、高さ:");
    scanf("%d,%d,%d", &up, &down, &h);
    s = GET_TRAPEZOID_AREA(up, down, h + 3);
    printf("面積:%d\n", s);

    return 0;
}

このプログラムの実行結果は次の通りになります。

実行結果
上底、下底、高さ:5,10,5
面積:76

高さは+3されているので、結果は前回と同じになるはずなのですが、
なぜか、答えは76と表示されてしまっています。

これは、#define疑似命令は、単なる置き換え命令でしかないため、
GET_TRAPEZOID_AREA(up,down,h + 3) を (A + B) * H / 2 と置き換えると、
(up + down) * h + 3 / 2 と言う式になってしまいます。
これでは、高さに3が加わるのではなくなってしまい、計算がおかしくなります。
この様に、置き換えで予期しない計算結果になることをマクロの副作用と呼びます。

これを解決する方法は2つあります。1つは、呼び出し時にかっこをつけることです。

ソースコード
GET_TRAPEZOID_AREA(up, down, (h + 3));

と、かっこをつけておけば先に高さに3が加わるので、正常に計算できます。

もう1つの方法は、マクロの方にかっこをつけておく方法です。
マクロで使われている置き換え部分すべてにかっこをつけ、さらにマクロ全体にもつけます。

ソースコード
#define GET_TRAPEZOID_AREA(A, B, H) (((A) + (B)) * (H) / 2)

この様にすれば、すべての数値にかっこが付いているので大丈夫です。

しかし、気をつけて使用するのは面倒ですし、うっかり忘れてしまうかもしれません。
その為、マクロはあまり多用しない方が良いとされています。
#define疑似命令は定数の宣言にのみ使用して、
数式などの計算にはできる限り関数を使用する方が良いでしょう。


本サイトについて

苦しんで覚えるC言語(苦C)は
C言語入門サイトの決定版です。
C言語の基本機能を体系立てて解説しており、
市販書籍と同等以上の完成度です。

第0部:プログラム概要編
  1. プログラムとは何か?
2章:プログラムの書き方
  1. 書き方のルール
  2. 書き方の慣習
  3. 練習問題2
3章:画面への表示
  1. 文字列の表示
  2. 改行文字
  3. 練習問題3
6章:キーボードからの入力
  1. 入力用の関数
  2. 入力の恐怖
  3. 練習問題6
9章:回数が決まっている繰り返し
  1. 繰り返しを行う文
  2. ループ動作の仕組み
  3. 練習問題9
10章:回数がわからない繰り返し
  1. 回数不明ループ
  2. 入力チェック
  3. 練習問題10
13章:複数の変数を一括して扱う
  1. 複数の変数をまとめて扱う
  2. 配列の使い方
  3. 練習問題13
20章:複数のソースファイル
  1. 最小限の分割
  2. 分割の定石
  3. 練習問題20

コメント
COMMENT

💬 コメント投稿欄を開く