Youtube登録者5000人突破!!

【C/C++】Makeflieの読み方・つくりかた

Makefileを作ることで、C/C++のプログラムの実行が簡単になります。

Makefileがないと、いちいちコマンドラインからコンパイルとリンクを行わないといけません。

毎回コマンドラインに入力するための命令を覚えておくのは大変なので、Makefileがよく利用されます。

ここではMakefileの基本構成から、具体的な作成例も紹介します。ぜひ最後までご覧ください。

Makefileの基本構成

Makefileの最小構成は非常に短いです。下記のようになります。

<ターゲット名(名前)> : <材料>
                       <実行されるコード>

ターゲット名は、make <ターゲット名> として命令を指定する際に使用します。

<材料>は、コードを実行する上で使用するファイルを記載します。

<実行されるコード>は、通常のコマンドラインで使用するコードが記載されます。

簡単に言うと、Makefileとは、コマンドラインでいちいちオプションを付けて命令するのが面倒なので、簡単にできるように一つのファイルにまとめたものであることがわかります。

コマンドライン上でのコンパイルとリンク

コンパイルとリンク

先にコンパイルとリンクについて説明しておきましょう。

VisualStudioなどのIDE(統合開発環境)を利用したことのある人は、「ビルド」という表現のほうが馴染み深いかもしれません。

ビルドはコンパイルとリンクをまとめて行うことで、プログラムから一気に実行ファイルを作成します。

コンパイルとは「翻訳」に当たる作業であり、プログラム言語で書かれたものをコンピュータが読める機械語(01だけの文字列)に直します。

そして、リンクとは翻訳したファイルを連結させて、最終的に一つの実行ファイルを作成するという作業です。

IDEではこれらが裏で行われているため簡単に見えますが、実際に自分でコンパイルとリンクをやろうとすると少し手間が出てきます。

そこでMakefileを使用するわけです。

その前に、まずはコマンドライン上での操作から見ていきましょう。

コマンドライン上コンパイルとリンク

2つのcppファイルで構成されるプログラムのコンパイルとリンクを考えます。

最も短くコマンドライン上で命令する場合は下記のように書けます。

g++ main.cpp sub.cpp -o exfile

ここで行っているのは、main.cppとsub.cppというプログラムを用意し、g++コンパイラ(C++用コンパイラ)によってexfileという名前の実行ファイルを作成しています。

-oはオプション名で、出力ファイルの名前を指定できます。-oをつけない場合は勝手にa.outという名前で出力されます。

ここではC++を対象として説明しますが、C言語の場合はgccコンパイラに変更すれば大体そのまま使用できます。

オブジェクトファイルの作成

g++ main.cpp sub.cpp -o exfile

先述したこの書き方だとmain.cppとsub.cppがまとめてコンパイルされます。

まとめてコンパイルされると、修正時にいちいち全てのコンパイルがやり直しとなり手間なので、各オブジェクトファイルを出力し、最後にリンクさせます。

g++ -c main.cpp
g++ -c sub.cpp
g++ main.o sub.o -o exfile

今後は三行になりました。

上の2行では-cオプションにより、コンパイル作業を行っています。それぞれの行で、オブジェクトファイルmain.oとsub.oが出力されます。

最後の行で2つのオブジェクトファイルmain.oとsub.oをリンクさせて、実行ファイルを作成します。

このような構成にすることで、1つのプログラムを変更しても他のプログラムをコンパイルする必要がなくなり、コンパイルが高速になります。

2つのプログラムだと影響は小さいですが、プログラムが増えてくるとコンパイル時間が気になってくるので、分割コンパイルは意識しましょう。

Makefileによるコンパイルとリンク

ここからは上記で行ったコンパイルとリンクをMakefileを使用して行います。

target : main.o sub.o
         g++ main.o sub.o -o exfile
main.o : main.cpp
         g++ -c main.cpp
sub.o : sub.cpp
         g++ -c sub.cpp

上記のMakefileは、main.cppとsub.cppを分割コンパイルして、exfileという実行ファイルを作成します。

コマンドライン上では make target を行うことでコンパイルとリンクが実行されます。

Makefileはターゲット名を省略すると最も上にある命令が実行されるため、make時のtargetは省略できます

上記のMakefileの実行の流れは下記の通りです。

  1. targetが実行される
  2. <材料>であるmain.oとsub.oのために、Makefile内が検索される
  3. Makefile内のmain.oとsub.o命令が実行されるため、
    main.cppとsub.cppのコンパイルによりmain.oとsub.oが生成される
  4. オブジェクトファイルmain.oとsub.oが用意されたため、targetも実行できる

つまり、一番上のtargetが呼び出されるけど、間接的に下2つのmain.oとsub.o命令が呼ばれることによって、コンパイル→リンクという流れができることになります。

少し複雑ですが、コマンドラインで行った操作と同じなので、照らし合わせて考えると理解しやすいでしょう。

一般的なMakefile

ここまではMakefileの構造について話してきたため、ここからは実際に使われるMakefileについて説明していきます。

実際に使用されるMakefileは下記のような構造となっています。

<変数定義1>
<変数定義2>
all : <ターゲット名1>
<ターゲット名1> : <材料>
                <実行されるコード>
<ターゲット名2> : <材料>
                <実行されるコード>
clean :
        rm -f <実行ファイル> <オブジェクトファイル>

まず変数定義についてですが、プログラムを追加した際にいちいち関連箇所を全て変更するのは面倒なので、変数を使用します。

特に、プログラムが増えてくると一括管理したくなるため、一つの変数にまとめてしまうのが一般的です。

次に、allとcleanについてです。

一般的にmake allとmake cleanはよく使用されるため、命令として追加しておくとなれていない人でも使いやすいMakefileになります。

make allは全てのプログラムのコンパイルとリンクを行います。

ターゲット名1を参照する材料として指定することで、ターゲット名1に実行を移しています。ターゲット名1に全てのプログラムのコンパイルとリンクを行うコマンドを入れてください。

一方でmake cleanはコンパイルとリンクによって生成される全てのファイルを削除します。これは、一旦白紙の状態に戻すのと同じです。

よって、make cleanではrmコマンドによりファイルが削除されています。画像ファイルや.bmpファイルも生成している場合はここで削除対象として加えてやります。

以上でMakefileの一般構成について理解できました。

具体的なMakefile例

ここからは具体的なMakefileについて見ていきましょう。

CXX = g++
CFLAGS = -O0 -Wall
PROG = exfile.out
OBJS = main.o sub.o
all : ${PROG}
${PROG} : ${OBJS}
          ${CXX} -o $@ ${OBLS} ${CFLAGS}
%.o : %.cpp
      ${CXX} ${CFLAGS} -c $<
clean : 
        rm -f ${PROG} ${OBLS}

少し長いのと新しい情報が多いため、分割して説明していきます。

変数宣言

CXX = g++
CFLAGS = -O0 -Wall
PROG = exfile.out
OBJS = main.o sub.o

Makefileではまず変数宣言を行います。変数はどんな名前でも構いませんが、今回の用語を使うのが一般的です。

変数は${定義名}で使用できます。

CXXではコンパイラを指定します。C++だとg++、C言語だとgccです。

CFLAGSはコマンドライン時のオプションをまとめて入れています。詳細は後で後述します。

PROGは、出力ファイル名を入れます。

OBJSは、中間的に生成するオブジェクトファイル名を入れます。作成した.cppファイルの拡張子を.oにしたものを記入してください。

オプション

ここからはCFLAGSで使用するオプションについて説明します。

-O0は最適化オプションです。0~3で指定でき、0は最適化なし、3は最も強い最適化を与えます。

-Wallは警告表示の有無です。Wallを与えることで、全ての警告を表示できます。

他にもよく使われるものは下記の通りです。

-lmで標準ライブラリをリンクできます。(lはLの小文字です)

-I/usr/local/include でインクルードディレクトリをパスとして追加できます。(Iはiの大文字です)

C++11,14などの機能を使うなら、-std=c++11もしくは14と書きます。

ターゲット

all : ${PROG}
${PROG} : ${OBJS}
          ${CXX} -o $@ ${OBLS} ${CFLAGS}
%.o : %.cpp
      ${CXX} ${CFLAGS} -c $<
clean : 
        rm -f ${PROG} ${OBLS}

戦術の通り、allは全てのファイルのコンパイルとリンクを行います。cleanについては生成ファイルの削除です。

all内で${PROG}を呼び出しており、${PROG}は生成ファイルのため存在しません。よって、ターゲット${PROG}が実行されます。

ターゲット${PROG}内では、${OBJS}によりオブジェクトファイルを参照して下段のコマンドが実行されます。

オブジェクトファイル.oは更にその下にターゲット命令として存在するため、%.oが実行されることになります。

%.oの%は、入力した文字を保持します。よって、%.cppとは、${OBJS}で与えたmain.oとsub.oの拡張子以前であるmainとsubが入ります。

つまり、%.oの行はmain.cppとsub.cppを使用して下段のコマンドが実行されることになります。

流れとしては、ターゲットallの部分で${PROG}を探して、ターゲット${PROG}で${OBJS}を探して、${OBJS}の中身を%.oに渡します。

そして逆流するように、ターゲット%.oでコンパイルを行い、ターゲット${PROG}でリンクを行い、allに返すことで操作が完了します。

コマンドの説明を省略しましたが、$@はターゲット名です。${PROG}の名前で出力ファイルが生成されます。

$<は参照材料となったファイルを指定します。上記だと%.cpp、つまりmain.cppとsub.cppが入ります。

おわりに

Makefileを使うとコンパイル作業が楽になります。C++でもC言語でも少し変更するだけで使用できるので、Linuxなどでコンパイルが必要な場合は是非利用してみてください。

makeで呼び出すこと、ターゲットの書き方だけ覚えておくだけでも他人のMakefileを読む上では十分です。

もし書く機会があれば、他の人の作ったファイルを参考にして作成してみてください。

youtubeもやってます

C++のプログラミング講座などもやってます。結構色々な機能について説明しているので、よければどうぞ。