1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【C】初めてのC言語(22. マクロ処理)

Last updated at Posted at 2022-10-17

はじめに

  • これまでの学習に続いて、今回はdefineを用いたマクロ処理について学んだ結果をまとめてみました。

学習環境

  • 今回はpaiza.ioのC言語のエディタを使いました。

プリプロセッサ

  • プリプロセッサとは、コンパイラが動作する前にソースコードに対して前処理を行うソフトウェアのことです。
    • 例えばソースコード中のコメントは、プリプロセッサによる前処理で削除されます。
  • また、ソースコードの先頭に書いてきた#includeは、プリプロセッサに対して前処理を要求する代表的な命令です。
    • プリプロセッサが#includeで指定されたファイルを探しだし、#includeが書かれた部分をヘッダファイルの内容に置き換えます。
  • このような#defineによる文字列の置換は「マクロ定数」と呼ばれ、C言語では広く使われていたそうです。

defineのおさらい

ここは前回の記事からの転載です。

  • #define#includeと同様に、プリプロセッサの代表的な命令で、「ソースコード内のある文字列を別の文字列に置換する」という機能を持っています。
    • この置換処理のことを「展開」と呼ぶそうです。
  • 以下のコードでは、プリプロセッサによって「PIが3.1415に置き換えられる」という処理が行われます。
Main.c
#include <stdio.h>

#define PI 3.1415

int main(void){
    int r = 2;
    printf("半径%dの円の面積:%f\n", r, r * r * PI);
    return 0;
}
実行結果
半径2の円の面積:12.566000

マクロの副作用

  • #defineを使ったマクロ定数には以下の副作用があるため、利用する際には注意が必要になるそうです。

副作用1:型や構文のチェックが効かない

  • #defineは単純に文字列の置換だけを行うため、型や構文のチェックが行われません。
  • 上記のコードでは#define PI 3.1415と記述していますが、以下のように数値として扱われない誤った形式で記述してしまうと、マクロとして定義した箇所では無く、展開される箇所でエラーとなります。
Main.c(誤ったマクロの定義)
#include <stdio.h>

#define PI "3.1415"  // ★数値として扱われない形式で記述

int main(void){
    int r = 2;
    printf("半径%dの円の面積:%f\n", r, r * r * PI);
}
コンパイルエラーの内容
Main.c:7:55: error: invalid operands to binary expression ('int' and 'char [7]')
    printf("半径%dの円の面積:%f\n", r, r * r * PI);
                                        ~~~~~ ^ ~~
1 error generated.

副作用2:危険な定数展開をしてしまうリスクがある

  • #defineは単純な文字列の置換のため、intなどの予約語を別のものに置換するなど、危険な展開をしてしまうことも可能だそうです。

マクロの副作用を防ぐには?

  • #defineでなければ実現できない特殊事例を除いて、定数として用いる場合はconstenumを使う方が良いそうです。
Main.c(改善版のコード)
#include <stdio.h>

const double PI = 3.1415;
int main(void){
    int r = 2;
    printf("半径%dの円の面積:%f\n", r, r * r * PI);
}

定義済みマクロ定数

  • 以下は、C言語で自動的に定義される(元から定義されている?)マクロ定数で、デバッグ時などに用いられるそうです。
    • __func__については、厳密にはマクロではないそうです...
__FILE__  // このマクロが書かれているソースファイル名
__LINE__  // このマクロが記述された位置(行番号)
__DATE__  // プリプロセッサが起動された日付
__TIME__  // プリプロセッサが起動された時刻
__func__  // このマクロが書かれている関数名
  • 以下は__LINE__を使った例ですが、このように行番号を標準出力できるとデバッグ時に役立ちそうだと感じました。
Main.c
#include <stdio.h>

int main(void){
    printf("ここは%d行目です。\n", __LINE__);
}
実行結果
ここは4行目です。

マクロ関数

  • #defineでは以下のように「引数を伴った文字列置換」も出来るため、「関数のようなマクロ」を定義することもできます。
  • マクロ関数はマクロ定数と同様の副作用もありますが、さらに「関数展開による予期しない動作のリスク」もあるそうです。
    • 処理をマクロ関数として定義するのは、あまり行儀の良い書き方では無いのかもしれませんね...
Main.c
#include <stdio.h>

// #define 置換前の文字列(引数1, 引数2,...) 置換後の文字列
#define ADD(X,Y) X+Y

int main(void){
    printf("%d\n", ADD(2,3));
}
実行結果
5

マクロを使うべきか?

  • 上記のようにマクロには様々なリスクがある上、通常の定数や関数で安全に置き換えられるシーンが多いと思われます。
  • それにも関わらずマクロが使われていたのは、CPUやメモリを節約できるというメリットがあったからだそうです。
  • マシンの性能が向上し、さらにコンパイラの最適化が備わっている現代では、マクロをあえて使うシーンは減っていると思われますが...
    • レガシーなコードにはマクロが使われていることもあるとおもうので、マクロ定数やマクロ関数については押さえておく必要がありそうです。

補足:これまでの学習の歩み

参考URL

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?