GCCとかmakeを使ってC/C++のプロジェクトをビルドするときの各種オプションや変数をいつも忘れてしまうのでまとめました。
基本
GCC製の実行ファイルが実行されるまでの過程を大きく分けるとコンパイル→リンク→実行の3段階があります。
まず、コンパイル時に必要になるのがヘッダです。#include <stdio.h>のstdio.hみたいなやつです。
ヘッダの探索にはデフォルトで/usr/includeなどが使われます。一覧は、例えばCのコンパイラ(cc)であれば
echo '#include <stdio.h>' | cc -v -x c -E - > /dev/null
などと打つと色々と出てきますがその中の「#include <...> search starts here:」のところで確認できます。手元のLinux Mintではこうなりました。
/usr/lib/gcc/x86_64-linux-gnu/11/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
コンパイル時に***.hとか***.hppが見つからないと言われたときは、lib***-devみたいな名前のパッケージを入れると大抵解決します。
次に、リンク時に必要になるのがライブラリです。リンクの中にも実行ファイル作成時の静的リンクと実行時の動的リンクの2種類がありますが、今は実行ファイル作成時なので前者に焦点を当てます。
静的リンクライブラリはLinuxだと***.a、Windowsだと***.lib(***.dllの場合もある?)です。動的リンクライブラリはLinuxだと***.so、Windowsだと***.dllです。
静的リンクライブラリの探索にはデフォルトで/usr/libなどが使われます。一覧はさっきと同じコマンドの出力のLIBRARY_PATH=というところで確認できます。手元ではこうなりました。
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../:/lib/:/usr/lib/
ちなみに、 cc -print-search-dirsというコマンドの出力にもlibraries: =という項目がありますが、ここから重複や存在しないフォルダを取り除いたものが上記のLIBRARY_PATHになるらしい(出典)のでLIBRARY_PATHのほうが便利そうです。
とはいえ上でも結果的に重複はしていて、それを除くと結局以下のようになります。
/usr/lib/gcc/x86_64-linux-gnu/11/
/usr/lib/gcc/x86_64-linux-gnu/
/usr/lib/x86_64-linux-gnu/
/usr/lib/
/lib/x86_64-linux-gnu/
/lib/
さらに手元では/libは/usr/libへのシンボリックリンクなので、下の2つも要らないことになります。
コンパイル時や実行時に***.aとか***.soが見つからないと言われたときは、lib***みたいな名前のパッケージを入れるか、上記フォルダを探してそれっぽいのがあればlib***.so -> lib***.so.2.1みたいな感じでシンボリックリンクを作るかすると大抵解決します。
最後に実行時です。このときは動的リンクのライブラリの探索が必要となります。ここまでの説明はGCCについてであるため、例えばClangなど別のコンパイラには当てはまりませんが、以下の内容はコンパイラの種類には無関係です。そのかわり、glibc環境のみに当てはまります。
実行時のライブラリの探索パスは、/etc/ld.so.confで設定されています。ただ実際には/etc/ld.so.conf.d/*.confをincludeしているだけのことが多いので、cat /etc/ld.so.conf.d/*.confで見るのが早いです。
さらにこれら以外に/lib、/usr/lib、/lib64、/usr/lib64などが参照されるようにハードコードされています。
これらの一覧は
LD_DEBUG=libs /lib64/ld-linux-x86-64.so.2 --library-path "" --inhibit-cache /bin/true 2>&1 | grep "search path"
のようなコマンドで見ることができます。結構見慣れない(特殊な状況向けに含まれているだけで、フォルダさえ存在しない)ものが含まれていて面白いです。
/lib64/ld-linux-x86-64.so.2の部分はCPUアーキテクチャごとにほぼ固定ですが、厳密には
readelf -l /bin/true | grep interpreter
などでわかります。
また、実際の実行時には毎回これらのフォルダを全て見ているわけではなく、キャッシュを利用しています。キャッシュされたライブラリの一覧はldconfig -pで調べられます。これは良く使うコマンドです。
GCC用のオプション・変数
ヘッダ探索・ライブラリ探索の時に、上記のデフォルトに加えて、自分で指定したフォルダも見てほしいという場合があります。
まずヘッダ探索時は、-I/my/includeのように-Iオプションでフォルダを追加できます。複数追加したければ複数回-Iオプションを書きます。さらに、以下の環境変数も読み込まれます。
- CPATH…CおよびC++のヘッダ探索が行われるパスの一覧
- C_INCLUDE_PATH…Cのヘッダ探索が行われるパスの一覧
- CPLUS_INCLUDE_PATH…C++のヘッダ探索が行われるパスの一覧
実際にはCPATHはあまり使われず、あとの2つが使われていることが多いような気がします。優先順位(探索の順番)としては、-I>環境変数>デフォルト値となります。
次はリンク・実行時です。まず、静的リンク時に、コマンドラインオプションとしては、-L/my/libのように-Lでフォルダを追加できます。さらに、以下の環境変数が使われます。
- LIBRARY_PATH…静的リンク時のライブラリ探索が行われるパスの一覧
- LD_LIBRARY_PATH…静的リンク時(LIBRARY_PATHと同様)、及び実行時に動的ライブラリの探索が行われるパスの一覧
LD_LIBRARY_PATHは、実行時にも影響するので、.profileなんかに下手に書いておくとlsでさえ実行できなくなったりします。注意してください。また、ビルド時に使うことはありませんが、LD_LIBRARY_PATHよりさらに優先度の高い(早く読み込まれる)LD_PRELOADという変数もあり、こちらはフォルダではなく共有ライブラリファイルのパスを直接指定します。既存の実行ファイルの動作を無理やり変更したいときなどに使われます。
これらのオプション・変数の起源はGCC(コマンドはgccやg++)だと思いますが、後発のClang(コマンドはclangやclang++)も概ねGCCと互換性があるので同様に使うことができます。ただしLIBRARY_PATHが-lcryptoのような-lXXX系の引数に効かないという仕様(バグ?)があるようなので、-Lで指定する必要がありそうです。
また、gccには無関係ですが重要な変数として以下を挙げておきます。
- PKG_CONFIG_PATH…pkg-configによる.pcファイルの探索時に使われるパス
pkg-configは、ライブラリ探索を抽象化したインターフェースみたいなもので、/usr/libとかに直接ライブラリファイルを置かなくても、ライブラリファイルの場所を書いた.pcファイルを/usr/lib/pkgconfigとかにおいておけば、それに従ってライブラリを探してくれるというものです。configureスクリプトを使うようなプロジェクトとかでよく使われていると思います。
これの事実上のデフォルト値は以下で確認できます。
pkg-config --variable pc_path pkg-config
make用の変数
これらはGNU makeで使われる変数です。○○FLAGSという名前で、これがgccなどの各コマンドに対してオプションとして渡されます。
- CFLAGS…Cコンパイラに渡すオプション。
- CXXFLAGS…C++コンパイラに渡すオプション。
- CPPFLAGS…C/C++のプリプロセッサ(PreProcessor)に渡すオプション。名前が紛らわしいが、「C++に関するオプション」ではない。
- LDFLAGS…リンカ(ld)に渡すオプション。
#includeもプリプロセッサ命令の一種ですから、-IはCPPFLAGSに追加します。-LはLDFLAGSに追加します。CFLAGS/CXXFLAGSは、よく使うところだと最適化オプション(-O*)、警告(-W*)、デバッグ(-g*)あたりかと思います。
ちなみに、オプションではなく実行ファイル名を指定する変数としては、Cコンパイラを指定するCC、C++コンパイラを指定するCXX、Cプリプロセッサを指定するCPP(←これはほぼ使わなさそう)などがあるのでついでに覚えてしまいましょう。
これらの変数は、①Makefile内②makeのコマンドラインオプション③環境変数という3つの方法で指定できますが、コマンドラインオプションの優先度が最も高く、Makefile内に何を書いたとしてもコマンドラインオプションでの指定内容に上書きされます。環境変数は最も優先度が低く、Makefile内での各変数の初期値として設定されるので、Makefile内で書き換えることができます。
Rustなどでのリンカオプションの指定
Rust経由でldを使う場合は、LD_LIBRARY_PATHなどを設定してもダメ(Rust側で設定されてしまうから?)で、
export RUSTFLAGS="-Clink-arg=-L/usr/lib/.."
などとRust用の変数を使う必要があるようです。
他の言語でも似たようなことがあるかもしれません。
応用例1: root権限がないところで色々なソフトウェアをビルドしたい
共用のsshサーバーなど、root権限を持っていないところでもC/C++のソフトウェアをコンパイルしたい場合があります。
この場合、~/.myrootのようなフォルダを作っておいて、環境変数は以下のように設定します。
export C_INCLUDE_PATH="$HOME/.myroot/include:$HOME/.myroot/usr/include:$C_INCLUDE_PATH"
export CPLUS_INCLUDE_PATH="$HOME/.myroot/include:$HOME/.myroot/usr/include:$CPLUS_INCLUDE_PATH"
export LIBRARY_PATH="$HOME/.myroot/lib:$HOME/.myroot/usr/lib:$LIBRARY_PATH"
export PKG_CONFIG_PATH="$HOME/.myroot/lib/pkgconfig:$HOME/.myroot/usr/lib/pkgconfig:$PKG_CONFIG_PATH"
これはあくまで最低限の例です。実際にはx86_64-linux-gnuとかも指定する必要があるかもしれません。
その上で、aptとかrpmみたいなパッケージをインストール(rootが必要)ではなくダウンロードだけしてきて、~/.myrootの中に(~/.myrootを/と見立てて)展開します。すると上記の変数で指定されたフォルダに必要なファイルが入るのでビルドできるようになります。
また、aptやrpmでなく、configureスクリプトを用いてビルドするタイプのライブラリであれば、./configure --prefix=$HOME/.myrootのようにprefixに.myrootを指定すると、そこをルートのようにしてインストールが行われます。
応用例2: BSD系などでclangを使う
BSD系(Mac含む?)では、GNU界隈に由来する(?)ライブラリを入れると/usr/local以下に入ることがありますが、clangがここを見る設定になっていないことがあります。そこで以下のようにしておくと解決します。
export CPLUS_INCLUDE_PATH=/usr/local/include:$CPLUS_INCLUDE_PATH
export C_INCLUDE_PATH=/usr/local/include:$C_INCLUDE_PATH
export LIBRARY_PATH=/usr/local/lib:$LIBRARY_PATH
余談: ユーザー環境変数の設定方法
環境変数を常時設定するには、~/.profileに書きます。~/.bash_profileや~/.bashrcを使うと書いてある記事もありますが、これらはあくまでbashだけが使う設定ファイルなので、~/.profileに書くのが最も正当です。~/.bash_profileでは~/.profileと~/.bashrcの読み込みのみを行います。
詳しくは以下の記事をご覧ください。
Bash: .bashrcと.bash_profileの違いを今度こそ理解する|TechRacho by BPS株式会社
bash の初期化ファイル .profile, .bashrc, .bash_profile の使い分けと管理方針 - A Memorandum