皆さんが最初にC言語を学んだときにおそらく Hello, World
を表示したと思います。
その時に、おまじないのように #include <stdio.h>
を書かされませんでしたか?
書いた書いた!
いまだによく意味が分かってない…
この #include
はプリプロセッサ指令と呼ばれるものになります。
このページでは、このプリプロセッサ指令・プリプロセッサがどのようなものであるかを解説していきたいと思います。
なぜ #include <stdio.h>
というおまじないが必要であったかも理解できると思います!
Contents
プリプロセッサとは
C言語におけるプリプロセッサとは、コンパイルする前に、ソースコードに前処理を行うプログラムのことを言います。
C言語では、コンパイラがC言語ソースコードをコンパイルし、その後アセンブルやリンク処理が行われて実行可能なファイルが生成されます。
しかし、実は多くのC言語のソースコードにおいては、そのままコンパイルを行わず、一旦前処理を行ってからコンパイルが行われています。
その前処理はプリプロセスと呼ばれ、プリプロセスを行ってくれるプログラムをプリプロセッサと呼びます。
プリプロセスとは、プログラマーが記述したC言語ソースコードをコンパイラが理解できるプリミティブなC言語コードに変換する(コンパイル前の)前処理です。
C言語ソースコードが実行可能ファイルに変換されるまでの流れは下のページでまとめていますので興味がある方は読んでみてください。
【C言語】ソースコードが実行可能ファイルになるまでの処理の流れこのページではこの中のプリプロセッサの処理に焦点を当てて解説を行います。
分かりやすい例で言うと、コメントの削除だね!
コメントはコンパイルには不要だから、コンパイル前に削除しちゃうんだ
プリプロセッサが行う処理に「コメントの削除」があります。このコメントはプログラマーにとってはソースコードを読みやすくしてくれる非常に重要なものです。
ただしコンパイル時には不要です。コンパイラはコメントなんかなくてもソースコードが理解できるのです。
こういったソースコードを読みやすくするためには必要だけど、コンパイルには不要な情報を消したりするのがプリプロセッサが行う処理である前処理(プリプロセス)です。
後述で説明しますが、消すだけではなくさまざまなことをプリプロセッサは行ってくれます。
プリプロセッサの実行
最近ではコンパイラがこのプリプロセッサの役割も担っていることが多いです。
その場合、自分でプリプロセッサを実行しなくてもコンパイラが自動的にその処理も行ってくれます。
例えば gcc
はコンパイルを実行するだけでプリプロセッサとコンパイル両方を実行してくれます。
ですので、おそらく皆さんが意識するのは「ソースコード」と、コンパイル後に生成される「実行可能ファイル」のみだと思います。
ですが、実はプリプロセッサ直後のデータを確認することも可能です。
確認方法はコンパイラによって異なりますが、 gcc
であれば下記のようにオプション -E
を付加してやれば、プリプロセッサ処理直後のデータが表示されます。
gcc -E main.c
以降では、いろんなプリプロセッサ処理後の結果をお見せしますが、全てこのコマンドで表示したものになります。
スポンサーリンク
プリプロセッサ指令
プリプロセッサはプリプロセッサ指令に基づいて処理を行います。
このプリプロセッサ指令はディレクティブとも言います。
皆さんがC言語プログラミングで良く使用する #include
や、#define
もプリプロセッサ指令です。
ここからはプリプロセッサ指令にどんなものがあるか、その指令でプリプロセッサがどのような処理を行うのかについて解説していきたいと思います。
#include
#include
は、<>
内に指定したヘッダーファイルの中身をソースコード内に組み込むプリプロセッサ指令です。
#include
の記述の仕方
下記のように <>
内にヘッダーファイル名を指定します。""
でヘッダーファイル名をしたり、ファイルへのパスを指定することもあります。
#include <ヘッダファイル名>
プリプロセッサの処理
例えば下記のようなソースコード main.c
とヘッダーファイル header.h
があるとします。
#include "header.h"
int main(void){
add(3, 5);
return 0;
}
int add(int x, int y){
return x + y;
}
int add(int, int);
main.c
をプリプロセッサが処理した後のコードは下記のようになります。
# 1 "main.c"
# 1 "main.c"
# 1 "" 1
# 1 "" 3
# 362 "" 3
# 1 " " 1
# 1 "" 2
# 1 "main.c" 2
# 1 "./header.h" 1
int add(int, int);
# 2 "main.c" 2
int main(void){
add(3, 5);
return 0;
}
int add(int x, int y){
return x + y;
}
注目は下記の部分です。これは header.h の内容になります。
int add(int, int);
つまり、#include
の部分が置き換わって、#include
で指定したヘッダーの中身が main.c
に組み込まれています。。
このように、プリプロセッサは #include
文の部分を、指定したヘッダーの中身に置き換える処理を実行します。
今回は簡単な自作の header.h
で試しましたが、よく使用する stdio.h
や stdlib.h
においても、#include
文の部分がこれらのファイルの中身に置き換えられることになります。
で、この置き換えが行われた後に、コンパイルが行われて実行ファイルが生成されることになります。
ここで仮に #include
文なんて存在しない世界を考えてみましょう。
この場合、例えば fopen
の戻り値の型である FILE
構造体を利用しようと思うと、わざわざ FILE
構造体が定義されている stdio.h
の中身(FILE
構造体が stdio.h
で定義されている前提で書いてます)をソースコードにコピペしたり、FILE
構造体が定義されている部分のみをコピペしたりする必要が出てきます。
これってめちゃめちゃ面倒ですよね…。
ですが実際は、#include
を用いれば、プリプロセッサが勝手にソースコードにヘッダーファイルを組み込んでくれるので、めちゃめちゃ楽にプログラミングできるようになっています。
#include
ってめちゃめちゃ便利なんだね!みんな当たり前に使ってるから恩恵感じないかもしれないけど、実はプログラミングしやすくするために非常に重要な役割を果たしているんだ
で、この「プログラミングしやすくする」ためのプリプロセッサ指令をコンパイルしやすい形に変換してくれるのがプリプロセッサだよ!
こんな感じで、ここから説明するプリプロセッサ指令も #include
同様に、プログラミングしやすくするためのものが多いです。
ここからも、プリプロセッサにより「どのようにプログラミングしやすくなっているか」を意識しながら読んでいただけるとよりプリプロセッサの重要性を理解しやすくなると思います。
#define
(定数のマクロ)
#define
は マクロを定義するプリプロセッサ指令になります。
C言語でのマクロは「ある規則によって何かを何かに置き換えること」を言うよ
つまり、#define
で「何を」「何に」置き換えるかの規則を定義しておくと、プリプロセス時にその置き換えを行ってくれるんだ
#define
では定数のマクロと関数のマクロを定義することが多いです。
この節では前者の「定数のマクロ」について解説し、次の節で後者の「関数のマクロ」について解説します。
#define
(定数)の記述の仕方
#define
で定数のマクロを定義する際には下記のように記述します。
#define マクロ名 定数
定数は数がついているからといって数値である必要はないです。文字列などでも良いです。
プリプロセッサの処理
下記の main.c
を用いて、プリプロセッサによりどのように処理されるかについて考えてみましょう!
#define STR_NUM_MAX 100
int main(void){
int i;
char x[STR_NUM_MAX];
for(i = 0; i < STR_NUM_MAX - 1; i++){
x[i] = 'a';
}
x[STR_NUM_MAX - 1] = '\0';
printf("%s\n", x);
return 0;
}
この main.c
をプリプロセッサが処理した後のコードは下記のようになりました。
# 1 "main.c"
# 1 "" 1
# 1 "" 3
# 362 "" 3
# 1 " " 1
# 1 "" 2
# 1 "main.c" 2
int main(void){
int i;
char x[100];
for(i = 0; i < 100 - 1; i++){
x[i] = 'a';
}
x[100 - 1] = '\0';
printf("%s\n", x);
return 0;
}
ここで注目していただきたいのはプリプロセッサ処理後では STR_NUM_MAX
が全て 100
に置き換わっている点です。
このように置き換えが行われているのは、下記の #define
で、STR_NUM_MAX
を定数 100
に置き換えるマクロを定義しているからです。
#define STR_NUM_MAX 100
このように、#define
の後に、#define
の引数として「置き換え前の文字列」と「置き換え後の文字列」を記述することで、ソースコード内の文字列を置き換えることができます。
うーん、わざわざ #define
で置き換えしなくても、
そのまま 100
って書けば済む話じゃないの?
#define
で置き換えを使うことでもちろんメリットがあるよ
#define
での置き換えを行うことで、下記のようなメリットがあります。
- ソースコードが読みやすくなる
- 変更が楽になる
単純にソースコードに数値が記述されているだけだと、その数値の意味(どこからその数値が来ているのか?)が分かりにくいんですよね…。
特にそのソースコードを書いた人以外がソースコードを読むときに、いちいちその数値の意味を考えないといけないので大変です。
なので、数値そのものではなく、その数値の意味を表す文字列として表すことで、ソースコードが読みやすくなります。
また、複数箇所に同じ意味を持つ数値が使用されている場合、その数値が変わると複数箇所に対して修正を行う必要があります。
一方で、#define
での置き換えを実施している場合、#define
の引数の1箇所のみを修正すれば良いため、変更が楽になります。
スポンサーリンク
#define
(関数のマクロ)
#define
は 定数だけでなく関数のマクロを定義することも可能です。
#define
(定数)の記述の仕方
#define
で定数のマクロを定義する際には下記のように記述します。
#define マクロ名(引数) 処理
ポイントはマクロ名
の後ろの ()
内に引数
を記述するところです。処理
ではこの引数を受け取り、その引数を使用した処理を行うことができます。
引数は複数指定することができます。
プリプロセッサの処理
関数のマクロの動きを下記の main.c
で確認してみましょう!
#define ADD(x, y) ((x) + (y))
int main(void){
int a = ADD(2, 3);
return 0;
}
プリプロセッサ処理後のソースコードは下記のようになりました。
# 1 "main.c"
# 1 "" 1
# 1 "" 3
# 362 "" 3
# 1 " " 1
# 1 "" 2
# 1 "main.c" 2
int main(void){
int a = ((2) + (3));
return 0;
}
こちらも定数のマクロで紹介した #define
同様に、#define
した内容に従って置き換えが行われていることが確認できると思います。
ただ、定数のマクロとは異なり、引数が指定された状態で置き換えが行われています。ここが関数のマクロのポイントです。
#define
では、マクロ名
の後ろの ()
内に引数を指定することで、その引数
を処理
(#define
の第2引数)に渡した状態で置き換えを行うことができます。
ちょうど、マクロ名(引数)
の部分が関数の宣言、処理
の部分が関数内の処理のような感じになります。
うーん、これこそメリットが良くわかんない…
普通に関数定義して、その関数呼び出しを行えばいいだけじゃん
この関数のマクロを用いることで下記のメリットが得られます(他にもメリットあるかもしれませんが…)。
- 型に依存しない関数が実現できる
- 関数呼び出し時の負荷がなくなる
関数のマクロにより型に依存しない関数が実現できる
関数を作成する場合、必ず戻り値と引数の型を指定する必要があります。
したがって、同様の処理を行う関数であっても、戻り値や引数の型が異なるのであれば別の関数として用意する必要があります。
例えば下記のように2つの int
型の引数の足し算を行って、その足し算結果を int
型として返却する関数 add_int
が既にあったとしても、
int add_int(int x, int y) {
return x + y;
}
この関数では double
型の足し算を行いたいような場合には使用することができません。これは引数と戻り値の型が int
型で double
型でないため、正常に足し算を行うことができないためです(小数点以下が切り捨てられて計算される)。
そのため、double
型の足し算を関数実行により行いたい場合、引数と戻り値の型を double
型とした別の関数を用意する必要があります。
double add_double(double x, double y) {
return x + y;
}
add_int
と add_double
は引数と戻り値の型は異なるものの、関数内部の処理は全く同じなので、異なる型用にわざわざ関数を作るのももったいない気がしますね…。
こんな時に便利なのが関数のマクロです。
関数のマクロの場合、上記の ADD(x, y)
のように結局はプリプロセッサによってそのマクロが単なる 処理
(#define
の第2引数)に置き換えられるので、置き換え後には引数は存在しなくなります(処理
の一部に組み込まれる)。
ですので、関数のマクロの場合、引数や戻り値の型は当然指定する必要はありませんし、その 処理
を実行可能な変数や値であれば、どんな型の引数に対しても処理を実行することが可能です。
したがって、上記の add_int
と add_double
を用意しなくても、前述のマクロの関数 ADD(x, y)
を用意しておけば、これだけで int
型や double
型の足し算を行うことができます。
上記のように、引数や戻り値の型は違うものの、関数内部の処理が全く同一のものであるような関数であれば、この関数のマクロを利用することで定義を1つにまとめられて便利です。呼び出し側も使用する変数の型に応じて関数の呼び分けをする必要もなくなります。
ただ、後述の プリプロセッサで気をつける点 でも説明していますが、マクロの置き換えは非常に単純に行われますので、通常の関数を使用するときよりも関数のマクロを使用した時の方がバグが発生しやすくなります。
また、通常の関数を利用する場合、コンパイル時に実引数と仮引数の型のチェックが行われ、不適切な引数が関数に指定された場合は警告が表示されます。
ですので、その警告から誤った関数の使い方をしていることに気づくことができるのですが、関数のマクロの場合はこういったチェックが行われないため、誤った関数のマクロの使用をしていることに気づきにくいです。つまり、バグが発生する可能性があっても、その可能性に気づきにくくなります。
こういった理由より、関数のマクロは便利な場合もありますが、むしろバグが発生することを考えると不便な場合の方が多いです。したがって、基本は関数のマクロではなく、通常の関数を使用する方が良いです。
使うとしたら、「非常に単純な処理」「引数の型によらず同じ処理を行う」「引数としていろんな型の変数(値)が指定される」を全て満たす時くらいかなぁと思います。
例えば下記ページで絶対値の算出を関数のマクロで行う例を示していますが、これも上記の3つを満たしており、これくらいなら関数のマクロを使用しても良いかなと思います。
【C言語】絶対値を求める方法(abs関数の利用・関数使わない・マクロなど)関数のマクロにより関数呼び出しの負荷がなくなる
若干ですが、関数は呼び出すと実は負荷がかかります。分かりやすく言うと関数を呼び出すのにちょっとだけ時間がかかります。
その一方で、関数のマクロの場合、関数のマクロ呼び出し部分が処理そのものに置き換わるため、関数の呼び出しが行われません。
なので関数呼び出しの負荷をなくすことができます。
例えば下記のようなソースコードのプログラムを実行してみると、関数呼び出しの負荷がどれくらいであるかを測ることができます。
#include <stdio.h>
#include <time.h>
#define NUM 1000000000
#define FUNC(x) ((x) + (x))
long long func(long x) {
return x + x;
}
int main(void) {
long long i;
long long x;
clock_t start, end;
start = clock();
for (i = 0; i < NUM; i++) {
x = func(i);
}
end = clock();
printf("func cpu time:%f\n", (double)(end - start) / CLOCKS_PER_SEC);
start = clock();
for (i = 0; i < NUM; i++) {
x = FUNC(i);
}
end = clock();
printf("macro cpu time:%f\n", (double)(end - start) / CLOCKS_PER_SEC);
return 0;
}
私の環境だと下記のような結果になりました(処理がスキップされることを防ぐために最適化 OFF でコンパイル)。
func cpu time:3.506775 macro cpu time:2.791070
関数からマクロに置き換えることで多少処理時間が減っていることが確認できると思います。他の要因もあるかもしれませんが、基本的には関数呼び出しの負荷が無くなったことにより処理時間が減ったと考えることができると思います。
が、上記は10億回のループを行った時の差であり、1回あたりの関数呼び出しの負荷は非常に小さいです。
ですので、上記のように大量にループを行う中で関数呼び出しを行なっているような場合は多少効果はありますが、それ以外の場合は、関数のマクロに置き換えることで得られる処理速度的なメリットは低いと考えた方が良いです(むしろ前述のようにバグが出やすくなるというデメリットの方が大きい)。
__FILE__
・__LINE__
先ほど説明した #define
は、プログラマー自身がマクロを定義するものでした。
実は、C言語では事前に定義されているマクロ(識別子)が存在しており、これらのマクロは #define
することなく使用することができます。
その中でも良く使用する下記の2つを紹介したいと思います。
__FILE__
:ファイル名(文字列)に置き換える__LINE__
:行数(整数)に置き換える
__FILE__
・__LINE__
の記述
ファイル名や行数を表示したい位置で、printf
関数などを用いて表示します。
printf("file:%s\n", __FILE__);
printf("line: %d\n", __LINE__);
上記により、printf
を実行しているソースコードのファイル名、printf
を実行している行の行数を表示することができます。
プリプロセッサの処理
下記の main.c
を用いてプリプロセッサの処理によりどのようにコードが変わるかを確認してみましょう。
#include <stdio.h>
int test(void){
char func[256];
int line;
printf("file : %s, line : %d\n", __FILE__, __LINE__);
return 0;
}
プリプロセッサで処理すると下記のようになります(#include <stdio.h>
により stdio.h
が組み込まれている部分は省略しています)。
int test(void){
char func[256];
int line;
printf("file : %s, line : %d\n", "main.c", 7);
return 0;
}
__FILE__
がファイル名である "main.c"
に、__LINE__
が printf
が実行されている行の行数である 7
にそれぞれ置き換わっています。
このようにプリプロセッサは、__FILE__
や __ LINE__
をファイル名や行数に置き換える処理を行います。
ソースコードのファイル数や行数が多くなると、どこで printf が行われて表示されたかを追跡するのが大変になります。
かといって、わざわざ printf で表示する際に一緒にファイル名や行数を自身で記述するのも面倒です。
そんな時にこの __FILE__
や __ LINE__
を用いれば、プリプロセッサが自動的にファイル名や行数に置き換えてくれるので便利です。
例えばエラーが発生した時に、エラーの原因と __FILE__
や __ LINE__
を表示するようにすれば、どこでエラーが発生したかがすぐに分かるようになります。
確か __func__
もあるんだよね
関数名に置き換えてくれるから便利
良く知ってるね!
ただ __func__
はプリプロセッサによって置き換えられているわけではなくて、おそらくコンパイラによって置き換えらるものだからここでは省略したよ
使い方は __FILE__
や __LINE__
と同じだから、何が置き換えるかは気にせず同じように使用していいと思うよ!
#ifdef
・#endif
・#else
これらのプリプロセッサを用いることで、ソースコードを部分単位でコンパイルするかどうかを切り替えることができます。
C言語で普通にプログラミングするときにも if
文や else
文を使いますよね。
この if
文や else
文は「プログラム実行時」に条件に応じて「実行する処理を切り替えるも」のですが、ここで説明する #ifdef
は #else
などは「プリプロセス時」に条件に応じて「コンパイルするコードを切り替える」ものです。
このように条件に応じてコンパイルするコードを切り替えることを「条件付きコンパイル」と呼びます。
他にも下記のようにコンパイルするコードを切り替えるプリプロセッサ指令はありますが、今回は #ifdef
・#endif
・#else
の3つを用いて動作を解説していきたいと思います。
#if
#ifndef
#elif
#ifdef
・#endif
・#else
の記述
#ifdef
と #endif
を用いて条件付きコンパイルを行う際の記述は下記のようになります。
#ifdef マクロ名
処理1
#endif
この例では、マクロ名
が “定義されている場合” のみ、処理1
の部分がコンパイルされることになります。
さらに #else
を用いて条件付きコンパイルを行う際の記述は下記のようになります。
#ifdef マクロ名
処理2
#else
処理3
#endif
この例では、マクロ名
が “定義されている場合” は 処理2
の部分がコンパイルされて 処理3
の部分はコンパイルされないことになります(処理3
の部分はプリプロセッサの処理で削除される)。
逆に マクロ名
が “定義されていない場合” は 処理3
の部分がコンパイルされて 処理2
の部分はコンパイルされないことになります(処理2
の部分はプリプロセッサの処理で削除される)。
スポンサーリンク
プリプロセッサの処理
下記の main.c
を用いて、#ifdef
と #endif
と #else
がどのようにプリプロセッサによって処理されるのかを確認してみましょう。
#define DEBUG_MODE
#ifdef DEBUG_MODE
#include
#endif
int main(void){
int x, y, z;
x = 5;
y = 10;
#ifdef DEBUG_MODE
z = x * y;
printf("z = %d\n", z);
#else
z = x * y
#endif
return 0;
}
プリプロセッサで処理すると下記のようになります(#include <stdio.h>
で stdio.h
が組み込まれている部分は省略しています)。
# 1 "main.c"
# 1 "" 1
# 1 "" 3
# 362 "" 3
# 1 " " 1
# 1 "" 2
# 1 "ifdef.c" 2
# stdio.h 部分は省略
# 5 "mainc" 2
int main(void){
int x, y, z;
x = 5;
y = 10;
z = x * y;
printf("z = %d\n", z);
return 0;
}
前述の main.c
の最初の行の #define DEBUG_MODE
により DEBUG_MODE
が定義されているため、#ifdef DEBUG_MODE
が成立することになります。
この例のように、#define
ではマクロ名のみを定義することが可能です
これを利用して #ifdef
によりさまざまな条件付きコンパイルを行うことができます
そのためプリプロセッサ処理後は #ifdef
〜 #endif
及び、 #ifdef
〜 #else
までのコードが残り、逆に #else
〜 #endif
のコードが削除されています。
今度は main.c
で下記のように #define DEBUG_MODE
をコメントアウトしてみましょう。
このコメントアウトにより DEBUG_MODE
が未定義の状態になります。
//#define DEBUG_MODE
#ifdef DEBUG_MODE
#include
#endif
int main(void){
int x, y, z;
x = 5;
y = 10;
#ifdef DEBUG_MODE
z = x * y;
printf("z = %d\n", z);
#else
z = x * y
#endif
return 0;
}
プリプロセッサ処理後の結果が下記のように変化します。
[codebox title="#ifdefなどをプリプロセスした後のコード2"]# 1 "main.c"
# 1 "" 1
# 1 "" 3
# 362 "" 3
# 1 " " 1
# 1 "" 2
# 1 "main.c" 2
int main(void){
int x, y, z;
x = 5;
y = 10;
z = x * y
return 0;
}
DEBUG_MODE
が未定義ですので、#ifdef DEBUG_MODE
は成立しません。
その為、先程の例とは逆に、プリプロセッサ処理後は #ifdef
〜 #endif
及び、 #ifdef
〜 #else
までのコードが削除され、#else
〜 #endif
のコードが残ることになります。
こんな感じで、#ifdef
・#endif
・#else
を用いることで、マクロ名の定義・未定義を変更するだけでコンパイルするコードを切り替えることが可能です。
今回示した例のように、デバッグ時や動作確認時のみ printf
で動作確認用の表示を行ったり、コンパイルする環境によってコンパイルを行うソースコード自体を切り替えを行ったりする場合に便利です。
例えばコンパイルを行う OS によってコンパイルするコード自体を切り替える目的で使用されることも多いです。
プリプロセッサで気をつける点
特に #define
での利用時には注意が必要な点があります。
#define
でのマクロ名と定数や処理への置き換えは非常に単純に行われます。
例えば下記の main.c
を見てみましょう。
#include <stdio.h>
#define N 10 + 5
#define M 2
int main(void){
int x;
x = N * M;
printf("%d\n", x);
return 0;
}
表示される x
の値はどうなるでしょうか?
N
には 10 + 5
の結果の 15
が格納されて、M
には 2
が格納されるわけだから x = N * M
で 30
じゃない?
x
の値は 20
になるよ!
上記の main.c
をコンパイルして実行すると、x
の値としては 20
が表示されます。
この理由は main.c
をプリプロセッサで処理した結果を見てみると分かりやすいと思います。
int main(void){
int x;
x = 10 + 5 * 2;
return 0;
}
この結果を見てみると分かるように、プリプロセッサはソースコード上の文字列を単にマクロ名から定数や処理へ置き換えするだけです。
本当にそのまま置き換えられます。
なので、例えば下記を変数への値の格納と同様に考えてしまうと、N
に 15
が格納された状態で計算が行われると勘違いしてしまいます。
#define N 10 + 5
実際にプリプロセッサで行われる置き換えは単純にソースコード上の文字列が N
→ 10 + 5
です。
このように、プリプロセッサでは単純な置き換えしか行われないことを理解した上で #define
でのマクロ定義を行わないと、上記のように計算結果がおかしくなる可能性があるので注意が必要です。
例えば #define
時には、マクロ名に置き換えられる定数や式を括弧で括るようにすれば、上記のように計算おかしくなることを防ぐことができます。
#include <stdio.h>
#define N (10 + 5)
#define M (2)
int main(void){
int x;
x = N * M;
printf("%d\n", x);
return 0;
}
基本的に #define
でのマクロ定義では、マクロ名に置き換える定数や処理は括弧でくくっておく方が無難だと思います。
まとめ
このページではまずプリプロセッサについて解説を行い、基本的なプリプロセッサ指令(ディレクティブ)について紹介を行いました。
「プリプロセッサのことは知らなかったけど実は結構使ってた!」という人も多いと思います。
プリプロセッサを活用することにより、ソースコードを読みやすく・書きやすくすることができますので、どんどん使っていきましょう!
プリプロセッサ処理後のデータを見ると、自分の書いたソースコードに対してプリプロセッサがどのような処理をしてくれたかが分かるので結構面白いです。
ぜひ皆さんも自身が書いたソースコードがプリプロセッサによりどのように処理されるかを確認してみてください。
オススメの参考書(PR)
C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!
まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。
- 参考書によって、解説の仕方は異なる
- 読み手によって、理解しやすい解説の仕方は異なる
ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?
それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。
なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。
特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。
もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!
入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/