Prompt

コンパイル言語はOS毎で個別の対応をしなければならないが、
その対応方法はOS毎でどんな感じ?

やっほー!情報工学を学んでいる君なら、このあたりの低レイヤーな話はワクワクするよね!

コンパイル言語がOSごとに「個別の対応」を迫られる理由は、主に 「バイナリ形式の壁」「システムコールの壁」 の2つに集約されるんだ。Javaのような中間コード形式(JVM)とは違って、ネイティブコンパイルはOSという舞台に直接飛び込むから、その劇場のルールに合わせなきゃいけないんだよね。

具体的にどんな対応が必要なのか、OSごとの特徴を整理してみたよ!


1. 実行ファイル形式(コンテナ)の合わせ込み

プログラムを動かすには、OSが「どこにコードがあって、どこにデータがあるか」を理解できる形式でパッケージングしなきゃいけないんだ。

OSフォーマット名特徴
Windows 11PE (Portable Executable)MS-DOS時代からの名残(MZヘッダ)を含みつつ、動的リンク(DLL)の構造が独特だよ。
Ubuntu (Linux)ELF (Executable and Linkable Format)セグメント管理が柔軟で、共有ライブラリ(.so)のロード方法がWindowsとは根本的に異なるんだ。
macOSMach-O複数のアーキテクチャ(IntelとApple Siliconなど)を1つにまとめる「Universal Binary」をサポートしているのが面白いところ!

2. システムコール(OSへの頼み事)の橋渡し

ここが一番エキサイティングな部分!ファイルを書き出したり、メモリを確保したりするのはOSの仕事だけど、その「頼み方(API)」がOSごとに全然違うんだ。

  • Windowsの場合:

    主に Win32 API を叩くことになるね。例えば、ファイルを開くなら CreateFile() を呼ぶ。

  • Linux (Ubuntu) の場合:

    POSIX 規格に近いシステムコールを使うよ。ファイルを開くなら open() システムコールだね。

コンパイラや標準ライブラリは、ソースコード上の printf()std::cout を、ターゲットOSに合わせた適切なシステムコールに内部で書き換える(マッピングする)作業をしてくれているんだよ。


3. ABI(Application Binary Interface)の遵守

関数を呼ぶときに「どのレジスタに引数を入れるか」「スタックを誰が掃除するか」という約束事が ABI だよ。

例えば、x64アーキテクチャでもWindowsとLinuxでは引数を渡すレジスタの順番が違うんだ!

  • Microsoft x64: RCX, RDX, R8, R9 の順で使用。

  • System V AMD64 (Linux/macOS): RDI, RSI, RDX, RCX, R8, R9 の順で使用。

コンパイラはこの「作法」をOSごとに使い分けて、レジスタ操作のコードを生成しているんだ。職人技だよね!


💡 豆知識:クロスコンパイルの魔法

君はUbuntuも使っているよね?Windows上でLinux用のバイナリを生成したり、その逆を行ったりすることをクロスコンパイルと呼ぶよ。

最近のRustやGo言語はこのあたりの対応がめちゃくちゃ優秀で、ターゲットを指定するだけでこれら全ての「壁」を自動で飛び越えてくれるんだ!