オブジェクトファイルのリンク順

OCaml のオブジェクトファイル(.cmo, .cmx)を並べる順番には意味がある。 順番を間違えると:

Reference to undefined global Hogehoge


などと言われるので注意。


a.ml:

let x = 1


b.ml:

let y = A.x


c.ml:

let z = B.y


というソースがあったとする。 a.ml, b.ml, c.ml の順に分割コンパイルする。これは問題ない:

$ ocamlc -c a.ml
$ ocamlc -c b.ml
$ ocamlc -c c.ml


さて、これをリンクする場合、依存関係の順にリンクしなければいけない:

$ ocamlc -o a.out a.cmo b.cmo c.cmo    # a.out 実行ファイルへとリンク


これを間違えると Reference to undefined global Hogehoge というエラーが出る:

$ ocamlc -o a.out b.cmo a.cmo c.cmo
File "_none_", line 1:
Error: Error while linking b.cmo:
Reference to undefined global `A'


OCaml でのモジュール毎の分割コンパイルと、そのリンクは、モジュール群のソースが、 連結されて一つの巨大な OCaml プログラムソースになったものをコンパイルする 作業を分割したもの、と考えると判りやすい。 b.cmo, a.cmo, c.cmo の順番での リンクは、 b.ml, a.ml, c.ml をこの順番でつなぎあわせたものをコンパイルするのと 同じで、b.ml の部分では a.ml のモジュール A は未定義。だからエラーになる:

module B = struct

    let y = A.x

end

module A = struct

    let x = 1

end

module C = struct

    let z = B.y

end


この上記のプログラムがコンパイルエラーになるのと同じである。


これは cma ライブラリを作る際の落とし穴にもなる ocamlc -o lib.cma b.cmo a.cmo とした場合、 lib.cma はエラーもなく作成される。その後、この lib.cma を使って例えば c.cmo とリンクし、実行ファイルを作ろうとすると、そこで初めてエラーとしてレポートされる:

$ ocamlc -a -o lib.cma b.cmo a.cmo       # lib.cma アーカイブ作成。エラー無し
$ ocamlc -o a.out lib.cma c.cmo          # a.out 実行ファイルへとリンク(失敗する)
File "_none_", line 1:
Error: Error while linking lib.cma(B):
Reference to undefined global `A'


上記の lib.cma は A というモジュールに依存した B モジュールと、それと独立した A モジュールを含むアーカイブになっている。大変に気持ち悪いがこのようなことができる:

$ ocamlc -o a.out a.cmo lib.cma c.cmo


この実行ファイルには A というモジュールが二回リンクされている。lib.cma 内部の B が使う A は lib.cma 内の A ではなく、 lib.cma の前に並べた a.cmo になる。同名モジュールが二回出てくることは OCaml のソース上ではあまりにわけが分からないので禁止されているが、リンカ上では OCaml の普通の値が同名の変数に束縛された場合 shadowing されるように先出のモジュールは後出のものに shadowing される。気持ち悪いがそういう挙動である。


このような問題を避けるにはモジュールを依存順に並べてリンクすればよいのだが、Makefile などの場合は手でモジュールリストの順番を調整する必要がある。 OMake などではこの依存関係を自動解析してくれるのでほとんど気にする必要はない…ただしモジュール間に渡る副作用の依存関係が存在していない限りにおいて、である。