C言語拡張の移植性と代替コンパイラの活用術
C extensions, portability, and alternative compilers
C extensions, portability, and alternative compilers
C言語拡張における移植性の確保と、代替コンパイラの利用に関する議論について。異なる環境下でのビルドや互換性問題への対策が焦点となっています。
C言語のプロジェクトで個人的に一番イラッとするのが、Linuxユーザーが書くと大抵「自分の環境では動く」程度のレベルで止まっちゃうことなんだよね(WindowsやFreeBSDユーザーだと、その両方のプラットフォームでハマることになるし)。記事でも典型的な例が挙げられてる。
#if !(defined __GNUC__ || defined __clang__ || defined __TINYC__)
# define __attribute__(xyz) /* Ignore */
#endif
この !defined チェックの中に、attribute がすでに定義されているかどうかのチェックを入れない理由なんてないはずなんだけど(自作コンパイラの作者なら、デフォルトで内部の mycompiler__attribute に変換されるような attribute の define を強制できるだろうし)。
それに、FreeBSDでコンパイルしようとすると、systemdの依存関係とかPOSIX準拠じゃない挙動によくぶち当たる(Windowsの話はするまでもないけど、ここで論争したくないから控えておくよ)。
コンパイラやOSの行列(マトリックス)に対して、現実的にコードをテストするいい方法ってあるの?
あるよ。ImportC(D言語のコンパイラに組み込まれたCコンパイラ)を実装したとき、様々な .h ファイルに入り混じるめちゃくちゃな独自仕様の対応に追われてかなりの時間を費やしたね。
https://github.com/dlang/dmd/blob/master/druntime/src/import... (https://github.com/dlang/dmd/blob/master/druntime/src/importc.h)
https://github.com/dlang/dmd/blob/master/druntime/src/__impo... (https://github.com/dlang/dmd/blob/master/druntime/src/__importc_builtins.di)
GCCやClangのふりをするつもりはないけれど、実用的なプロジェクトをコンパイルしたいというインディー系Cコンパイラ開発者なら、slimccのテストスクリプト[1]やプラットフォームヘッダーのハック[2]が役に立つかもね。
[1] https://github.com/fuhsnn/slimcc/blob/main/scripts/linux_thi... (https://github.com/fuhsnn/slimcc/blob/main/scripts/linux_thirdparty.bash)
[2] https://github.com/fuhsnn/slimcc/blob/main/slimcc_headers/pl... (https://github.com/fuhsnn/slimcc/blob/main/slimcc_headers/platform_fix/)
他にも面白い話があるよ:
if が最適化されないのが原因だったなぜこんな風にしないんだろう?
+ #if !(defined __GLIBC_COMPILER_SUPPORTS_ATTRIBUTES__)
- #if !(defined __GNUC__ || defined __clang__ || defined __TINYC__)
# define __attribute__(xyz) /* Ignore */
#endif
(あるいは、使おうとする属性ごとに細かく分けるとかね)
コンパイラに基づくダウンストリームのC++ライブラリでは、OSやコンパイラのプラットフォームをチェックするのはかなり一般的なことだし(例えば Boost.Config)。モダンC++だと、ある程度標準化も進んでるよ https://en.cppreference.com/cpp/utility/feature_test
tccに対抗するために高速な小型コンパイラ rcc を実装したけど、glibcヘッダーの奇妙な挙動はClangと同じ方法で簡単に直せたよ。GCCのプリデファインをすべて定義して、GCCの拡張機能をすべて実装するだけ。1日でできた。それに、うちのヘッダーはglibcと比べてクリーンだよ。このカテゴリの他のコンパイラには slimcc、kefir、cproc がある。glibcのヘッダーをパースできるコンパイラは他にないよ。tccはその点、特別に例外対応してるんだ。
Cを書いたことがある人なら誰でも、ISO C標準に完全に準拠したコードなんて非現実的でレアだってことを知っている。
それなりの量のCを書いてきたけど、それがレアだとは思わないな。特にアプリケーションよりもライブラリの場合、そしてプロプライエタリなコードよりもFOSSの場合ならなおさら。
世の中の実用的なCコードのほとんどは、多かれ少なかれ非標準の挙動や言語拡張に依存している
どの分野で働いているかにもよるんじゃないかな。PCをターゲットにしていない企業だと、独自の挙動や拡張に依存するのは難しいんだ。ターゲットデバイスのコンパイラがGCCなどの便利な機能をサポートしていない可能性があるから、安全策をとってC99(ひどい時にはC89)にとどまることになる。それでも標準にはあるけど……まあ、それが十分に堅牢にサポートされていると信頼するのは警戒するかな。例えば可変長配列(VLA)みたいにね。
もちろん、コードが単一のマシンだけをターゲットにしないとなると、移植性や標準準拠なんかを心配せざるを得なくなるんだけどね。
コンパイラのレベルでオープンソースの移植性について文句を言うなら、オープンソースの精神を吸い込んで口を閉じてろ。そして息を吐いて、自分でその魔法のニッチなコンパイラに移植すればいいだろ。
Common Lispのエコシステムは、多数の実装がどうやって一緒に前進していくかという良い模範になっていると思う。実装が実験的に拡張を取り入れて、本当に便利なものは複数の実装で採用されて、十分に良ければ移植性ライブラリが事実上の標準として現れる。その成果は https://portability.cl/ で見られるよ。標準化委員会が40年近く前に解散したのに、言語は今まで以上に進化してるんだ!
例えばClangでLinuxカーネルをコンパイルできるようにするには、両方のプロジェクトにパッチが必要で、かなりの労力を要した。
よくわかるよ。:-)