ジャンル雑多なゲーム・ゲーム制作関連の色々な情報を取り扱っているブログ。最近はBlenderについてが中心。
[C, C++学習]C, C++言語再学習ノート-3日目- –コンパイルとコンパイラ、C言語のプリプロセッサ指令、マクロ置換

[C, C++学習]C, C++言語再学習ノート-3日目- –コンパイルとコンパイラ、C言語のプリプロセッサ指令、マクロ置換

C言語をコンパイルして生成されるexeファイルと、#include <stdio.h>の意味~プリプロセッサ指令とマクロの話 —勉強しなおしの記録というか学習ノート-3日目-

Unreal Engin4すごい!(語彙力)ってなったので、少しいじりつつ、むかーしかじった事あるC,C++の学びなおしの学習ノート。この連載記事の詳しい趣旨と注意事項は1日目をご覧ください。

※2019年5月25日 加筆・修正、画像の追加を行いました。
※2019年6月16日 訂正を行いました。

UE4に関して。今日は、ブループリントのチュートリアルやってみようと(実際少し触ったんですが)あまり進まなかった上、よくわからなかったので、今日の進捗は省略させていただきますm(_ _;)m(その分明日はUE4やりたい)

目次

C, C++学習ノート

昨日掲載したこの記事複数ファイルで構成してるプログラムの部分、あまり説明せずにとりあえず「複数のファイルのプログラムをコンパイルして、一つのexeファイルを生成!」っていうのやったんですが、あまりにも説明足りなかったなと思ったので、そこらへんの補足やら、解説を書いていきたいと思います。

そもそもコンパイルってなんぞ?

人間とコンパイラとパソコンのイメージ画像

今まで、C言語で記述したファイルをコンパイルしてきましたが、「これって何してるの?」っていうのを、ごくかるーく説明してみます。(厳密に言えば違うので、お叱りを本職(言い方)の方から頂くことになるかと恐々としております((((;゚Д゚))))ガクガクブルブル)

そもそも、C言語は、機械とお話しするための言語(この言い方だと完全にヤバいやつ)で、『コンパイル』というのが、
「人間の言葉」→「機械の言葉」
っていう翻訳作業を意味しています。上の画像で、真ん中の、眼鏡書けてる賢そうな方が、コンパイル(翻訳作業)をしてくれているプログラムの『コンパイラ』さんです。

ここで言う「人間の言葉」がC言語で書いたソースコードで、「機械の言葉」が『バイナリコード』って言って「01」って感じの2進法で書かれたコードです。exeファイルに書かれてるのがそれですね(厳密に言えばテキストも他の画像やら音楽やらのファイルも「01」で表現されてるんですがここでその説明は割愛)。

参考にさせていただいたページ:
コンパイル(compile)とは
バイナリとは
バイナリ

そういえば#include <stdio.h>ってなに?

今までさんざん使ってきた、#include <stdio.h>ですが、ここの部分って何してるのかというと、 ざっくりいうと、『コンパイラ』さんに、
「プログラムをコンパイルする前に、stdio.hというヘッダーファイルを#include(インルード(取り込み))して!(>人<)」
っていう指令を出しているわけです。

昨日の記事で複数ファイルのプログラムの時にもmain.cに関連付けるcファイルの数だけヘッダファイル(.hのファイル)作りましたが、このstdio.hは、C言語にもともと備わっているファイル(ライブラリ)のヘッダファイルですね。いつもここを通じてC言語のもろもろの標準で備わっている関数(printf()やらなんやら)を呼び出して使わせていただいているわけです。(お世話になってます。(*- -)(*_ _)ペコリ)

ちなみに#includeでファイル取り込みする時の<〇〇.h>と、”□□.h”の違いについて

昨日オリジナルで作ったヘッダファイル取り込みの時に、「”」で囲ったのと、「<」で囲ったのの違いは、インクルードするファイルの違いで、 C標準のライブラリであれば、「<」。標準外(オリジナルのものなど)であれば「”」だそうです。…と、多くのサイトやページでは書いてあったのですが(実際そうとも言えるかとも思うのですが)、実は、「”stdio.h”」でも通るようです

どうやら、ヘッダファイルの探し方が違うようです。

<>(山括弧)→探査パスにカレントディレクトリ(現在居るフォルダなどの場所)を含めない
“”(ダブルクォート)→探査パスにカレントディレクトリを含める

引用元: C言語、#includeを書く際の囲み文字「<>(山括弧)」と「””(ダブルクォート)」の違いについて。

だそうですので、使えたとしても、コンパイラに手間をかけさせてしまうわけですから、結局のところはもともとの「<」を使った方が良いとは思います。豆知識的に頭の片隅に置いといてください。(無駄知識じゃんとか言われると悲しい(;ω;`))
※詳しい解説は引用元のページに丸投げさせていただきます

自分でもコード書いて試してみたら、ちゃんとコンパイルされて、実行結果に「a = 100」と表示されました!やっぱり少しでも疑問に思ったら、調べてみるものですね (-ω- )フムフム 

#include "stdio.h"

//グローバル変数
int a = 100;

void main() {
	printf("a = %d\n", a);
}

もっと突っ込んで#includeの部分について –プリプロセッサ指令(プリプロセッサディレクティブ)と、マクロの話

そんでもって、C言語では、このコンパイル前の段階にコンパイラさんに、「こうして欲しい」っていう指令を出すことが出来たりします。 プリプロセッサ指令(プリプロセッサディレクティブ)と言ったりします。

「コンパイルの前処理(プリプロセス)をするプログラム(プリプロセッサ)に出す指令。」という意味です。(見慣れない横文字続いて目がチカチカするかもですね(^_^;))
この言い方だと、ちょっと誤解が生じるかもしれませんが、決して「コンパイラ = プリプロセッサ」ではありません。コンパイラさんが担う役割の1つみたいな感じですね。(ほとんどありませんが、コンパイラ以外がプリプロセッサを担当していたりもするようです。でも通常は「コンパイラ=プリプロセッサの役目の1つ」という認識で良いかと思います。)

後日追記:少し調べなおしたところ、コンパイラとプリプロセッサは別物みたいです。C言語で記述したファイルからexeファイル生成するのに、段階が3つあるようです。

  • プリプロセッサ・・・ヘッダ(.h)ファイルとソース(.cpp)ファイルを読み込み、プリプロセッサ指令を実行
  • コンパイラ・・・機械語に翻訳してオブジェクト(.obj)ファイルを生成
  • リンカ・・・「標準ライブラリ」+「オブジェクトファイル」をまとめたものを結合させて、実行可能(.exe)ファイルを生成

という感じにみたいです。

参考にさせていただいたサイト・ページ一覧:
一週間で身につくC言語の基本|第7日目:ファイル分割
それC++なら#defineじゃなくてもできるよ

(自分で描いた絵となので伝わるか怪しいんですが)コンパイラさんに、機械の言葉に翻訳する前に、翻訳以外の事も依頼しているイメージです。セリフつけるとしたら、↓みたいな感じ。

~※個人のイメージです~~~
人間:「こういうファイル(stdio.hとか)を一緒に資料として持ってきて、そのファイルと一緒に翻訳していただけませんか?
あと、こういう単語あったら、こういう言葉に置き換えてから翻訳して欲しいんです!
あ、こういう付箋張ってあるところは、これがこういう時は、こうして、そうじゃなければ、ここ飛ばしちゃってください!」

コンパイラさん プリプロセッサさん:「了解しました!翻訳前にやりますね!」

※ほろほろり個人が持っているイメージです。

で、#includeというのはその「プリプロセッサ指令」の内の1つです。プリプロセッサ指令の種類はいくつかあって、昨日の記事のここのコードの中でも出てきた、#defineや、#ifndef#endifなんかもそれです。ここまで来たらお気づきかと思いますが、プリプロセッサ指令は先頭に「#」が付きます。

既出の4つと、#define関連で#undefや、#ifndef関連で#ifdef#if、#else、#elifに関して書いてみます。

#include

先ほど書いたように、後に続けて指定されてファイルを取り込む際に使用します。

書式「#include “ファイル名”」または「#include <ファイル名>」

#define

マクロ置換』と言って、ある特定の文字列をある特定の文字列や数値に定義付けする際に使います。いわゆる『定数』として扱える『オブジェクト形式マクロ』と、引数を取り関数のように扱うことが出来る、『関数形式マクロ』の2パターンの使い方があります。

注意点として、マクロは基本的に1行完結です。それから、マクロ名は全て大文字で記述してください。(小文字でも定義付けできるのですが、命名規則的なものや可読性の観点から、大文字で記述することをお勧めします)

  • オブジェクト形式マクロ

書式「#define マクロ名 値」

#include <stdio.h>
#define REIWA "時に、初春の令月にして、気淑く風和ぎ、梅は鏡前の粉を披き、蘭は珮後の香を薫す。"
#define REIWA_GANNEN 2019

void main() {
	printf("%s\n%d",REIWA,REIWA_GANNEN);
}

実行結果は↓のようになります。

時に、初春の令月にして、気淑く風和ぎ、梅は鏡前の粉を披き、蘭は珮後の香を薫す。
2019

複数行に渡って記述したい場合は下のように「\」を区切りたい行の後ろにつけます。

#include <stdio.h>
#define REIWA "時に、初春の令月にして、\
気淑く風和ぎ、梅は鏡前の粉を披き、蘭は珮後の香を薫す。"
#define REIWA_GANNEN 20\
19

void main() {
	printf("%s\n%d",REIWA,REIWA_GANNEN);
}

※実は#defineで定義付けする以外にも、const修飾子を使って定数化することが出来たりしますが、constはまた別の機会に。

  • 関数形式マクロ

この関数形式マクロを記述する時に、マクロ名と引数を記述する()の間にスペースを入れるとエラーになるので注意です!

書式「#define マクロ名(引数1, 引数2, …) 置換したい処理」

#include <stdio.h>
#define MUL(a, b) ((a) * (b))

void main() {
	printf("%d", MUL(30 + 2, 50 + 2));
}

実行結果: 1664

掛け算の関数形式マクロを定義してみたのですが、なぜこんなに()を多めにつけているのかというと、マクロの置換の性質に関係してきます。

マクロ置換は、その性質上、字面通りですがその定義付けしたマクロをそのマクロが記述されているところへ、コンパイル前に置き換えるんです。つまり、上で例に挙げたのものは、コンパイルする時↓のようになります。

#include <stdio.h>

void main() {
	printf("%d", ((30 + 2) * (50 + 2)));
}

なので、もし先ほどの例で

#define MUL(a, b) a * b

としていた場合、

#include <stdio.h>

void main() {
	printf("%d", 30 + 2 * 50 + 2);
}

実行結果: 132

という意図しない演算結果が出力されてしまいます。なので、関数形式マクロを記述する時は、しつこいくらいに()をつけときましょう

実はC言語ではデフォルトで定義済のマクロが用意されていますので、ついでにいくつかご紹介。

マクロ置き換えられる値
__FILE__コンパイルされているファイル名
__LINE__現在の行番号
__DATA__コンパイルした日付
__TIME__コンパイルした時間

他にもありますが、恐らく基本的につかうのはこれくらいかなと思うので、もっと詳しく知りたい方は、↓のページをご覧ください。

外部ページ:定義済みマクロ

#undef

前回の記事では使いませんでしたが、#undefという#defineとは対になるものがあります。これを使うことによって、#defineで定義してあるマクロを局所(ローカル)的なマクロとして使うことが出来ます。そのマクロが定義済みであった場合、そのマクロを未定義にします。

書式「#undef マクロ名」

#include <stdio.h>
#define MUL(a, b) ((a) * (b))

void main() {
	printf("%d", MUL(30 + 2, 50 + 2));

#undef MUL // マクロ MUL を、未定義に戻す
	printf("%d", MUL(30 + 2, 50 + 2)); // 未定義に戻したのでエラーする
}

#if、#else、#elif、#endif

if文と大体同じようなものです。ただし、コンパイルされる前の段階なので、変数が判断されません。変数生成前ですので、条件式に書いても思ったようには動作しませんのでご注意ください。あとは、JavaScriptでは「else if」と記述するのに対し、#elifと記述するのと、終わりに#endifを記述するに少し違和感感じるかもしれませんね。

最低限#ifと#endif部分があれば動くというのと、「0」が「false」と判断されたり、「1」が「true」と判断されたりするのは、JavaScriptを使っている方なら想像に難しくないかと思います。

条件式がtrueの部分の間のコードだけコンパイルされます。

書式「#if 条件式」

書式「#else」

書式「#elif 条件式」

書式「#endif」

#include <stdio.h>
#define MISTAKE 2 < 1

void main() {
#if MISTAKE
	printf("マクロMISTAKEは正しいです");
#elif 0
	printf("0はTRUEと判断されます。");
#else
	printf("どいつもこいつも!有罪だ!");
#endif
}

実行結果: どいつもこいつも!有罪だ!

※元ネタ知ってる方は、私と握手。

#ifdef、#endif

前回の記事で使った#ifndefと似ているこの、#ifdefですが、これは、指定したマクロが、定義されている場合に、#endifまでの間にあるコードをコンパイルするというもの。

#include <stdio.h>
#define _SAMPLE_C_

void main() {
#ifdef	_SAMPLE_C_
	printf("マクロ_SAMPLE_C_は定義済み");
#endif
}

実行結果:マクロ_SAMPLE_C_は定義済み

#ifndef、#endif

前回の記事で使ったこの#ifndefは、上で紹介した#ifdefの反対で、指定したマクロが定義されていない場合のみ、#endifまでの間にあるコードをコンパイルします。つまり、前回の記事でやっているのは、二重にインクルードされるのを防いでいるということですね。『インクルードガード』と言うよく知られた使い方です。

前回の記事のここから再掲載

#ifndef _SHOW_ANSWER_H_
#define _SHOW_ANSWER_H_

// プロトタイプ宣言
void showAnswer();

#endif // _SHOW_ANSWER_H_

他のプリプロセッサ指令や、マクロに関しては下のページが参考になるかと思いますので、丸投げさせていただきますので、詳しく知りたい方はどうぞ。

参考にさせていただいた&おすすめ外部ページ:
プログラミング覚書 – Programing Memo/C
C言語のマクロの基本について
C言語 プリプロセッサ
プリプロセッサ ディレクティブ
プリプロセッサ
デバッグ
プリプロセッサの基礎
インクルードガード
定数とマクロ(C言語) – 超初心者向けプログラミング入門
一週間で身につくC言語|第7日目

あとがき

今日は補足で終わってしまったんですが、何とか3日続けられました!でもここからですね…三日坊主嫌なので頑張ります!そして今日は全くと言っていいほどUE4進められなかったので、明日はそちらの方優先で進めたいと思います。

ご意見・ご感想・ご質問、また、ここ間違ってるよとか、もっといい方法あるよといったご指摘などございましたら、お手数ですがコメント欄やtwitterアカウントほろほろり(@_horo_horori)へお願いしますm(_ _)m

Pocket