undefined reference to `_exit' の原因
組み込みでソフトウェアを開発していると身に覚えがない関数が無くてリンクできないことがあります。 その関数はいったいどこで何のために必要とされているのでしょうか?
undefined reference to `_exit'
試しにarm-none-eabi-gccでmainだけのプログラムをコンパイルしてみると _exitが未定義でリンクできないという理由でエラーになります。
int main() { return 0; }
$ arm-none-eabi-gcc main.c e:/msys64/mingw64/bin/../lib/gcc/arm-none-eabi/8.4.0/../../../../arm-none-eabi/bin/ld.exe: e:/msys64/mingw64/bin/../lib/gcc/arm-none-eabi/8.4.0/../../../../arm-none-eabi/lib\libc.a(lib_a-exit.o): in function `exit': exit.c:(.text+0x2c): undefined reference to `_exit' collect2.exe: error: ld returned 1 exit status
_exitを必要とするのは誰なのか
C言語のプログラムはmainから実行されると習ったと思いますが、 実際にはmainの手前にスタートアップと呼ばれるプログラムがリンクしていて、 プログラムの実行はこのスタートアップから始まります。
スタートアップではまずシステムやプログラム全体の初期化を行いその後でmainを実行します。 またmainから戻った後プログラム全体の終了処理もするのですが、 ここで_exitが実行されます。
何故_exitが残っているのか
スタートアップがmainを実行する前の初期化をしてくれるのであれば、ついでに実行後の終了処理も全部やってくれればいいのにと思いたくなるのが正直なところです。 しかしながらもともとLinux等のリッチなOSが存在することを前提に開発されたスタートアップを組み込み用にポーティングする際に、 _exitは組み込みでどうすべきか一概に決められないというやむにやまれぬ理由があり、ここの実装はユーザー任せになっています。
_exitを実装する
それではとりあえずプログラム全体をリンクするために自分で _exitを実装することにします。 そこで_exitで何をすべきかを考えるのですが、そもそも組み込みでmainから戻ることがあるのでしょうか? 仮にプログラムを終了することがあったとしても、そのために必要な処理はmainから戻ることなく実行されるのではないでしょうか?
というわけで今回の_exitは次のように空っぽのプログラムとして実装することにします。
#include <stdnoreturn.h> _Noreturn void _exit(int code) { while(1); }
gccでは_exitは_Noreturnで宣言されているようなので、コンパイル時に警告されることを防ぐため同様に定義しています。
これで無事リンクできるようになりました。
これで実行可能なのか
ではこのプログラムが実際にターゲットボードで実行可能かというとそういうことは普通ありません。 組み込みではスタートアップは製品仕様に合わせて自分で実装するのが原則です。しかしそれはあまりに難しいため一般的にはCPU、ボード、コンパイラ、RTOS等何かしらのベンダーから提供されたものをノーチェックで使っているのが現状です。これを本当にそれで正しいと保障できるようになるためにはマイコンやプログラミング言語についてもっとディープに勉強する必要があると思う今日この頃です。