
Windowsでは通るのにLinuxでC++リンクエラーになる理由と解決策:ImPlot::PlotBars未定義シンボルの正体
WindowsでSDL3+Dear ImGui+ImPlotのプロジェクトが問題なくビルドできるのに、Linuxに持っていくとld.lld: undefined symbolで落ちる――この手の現象は「環境差」ではなく、ほぼ確実に“型”と“テンプレートの実体化”が原因です。とくに今回の ImPlot::PlotBars<unsigned long> が未定義になるケースは、再現性が高く、直し方もパターン化できます。この記事では、ログに出ている情報だけを手がかりに、なぜ起きるか/どう直すかを、手順としてまとめます。
- Windowsでは通るのにLinuxでC++リンクエラーになる理由と解決策:ImPlot::PlotBars未定義シンボルの正体
- ノイズ除去後の要点(何が起きているか)
- なぜWindowsでは通ってLinuxで落ちるのか:unsigned long のサイズ差がトリガー
- 仕組み:テンプレート定義が.cpp側にあると「使った型だけ」では自動生成されない
- 最短で直す:unsigned long をやめて固定幅整数にする(推奨)
- すぐ効く回避策:呼び出し側で型を変える(キャスト)
- 根本対応:ImPlot側で unsigned long の明示的インスタンス化を追加する
- Zigビルドで気をつける点:C++標準ライブラリとリンカの組み合わせ
- チェックリスト(この順で潰すと早い)
- まとめ:Linuxで落ちるのは「環境差」ではなく「型とテンプレート」の必然
ノイズ除去後の要点(何が起きているか)
エラーの核心はこれです。
-
Linuxでリンク時に
ImPlot::PlotBars<unsigned long>(...)が見つからない -
代わりに
ImPlot::PlotBars<signed char>(...)は定義されている と示される -
implot_items.oに定義があることもログに出ているが、欲しい型の実体がない
つまり「ImPlotのPlotBarsテンプレート関数を unsigned long で呼んだが、その型の“実体(インスタンス化された定義)”がリンク対象に存在しない」という状態です。
なぜWindowsでは通ってLinuxで落ちるのか:unsigned long のサイズ差がトリガー
ここが最大の落とし穴です。
-
Windows(とくにMSVC系)では
unsigned longはたいてい32bit -
Linux(x86_64のGCC/Clang系)では
unsigned longはたいてい64bit
同じソースで「unsigned long を使っている」つもりでも、実際には別サイズ・別ABIの別型として扱われます。テンプレートは「型が違えば別物」なので、Windowsで実体化されていた型(例:32bit相当)が、Linuxでは64bitになり、ImPlot側が用意している(または明示的に実体化している)型セットから外れて未定義になる、という流れが起きます。
ログに「did you mean: PlotBars<signed char>」と出るのも、ImPlot側が“いくつかの型だけ”明示的に用意していて、たまたまそこにsigned charが含まれている、という典型的な症状です。
仕組み:テンプレート定義が.cpp側にあると「使った型だけ」では自動生成されない
テンプレート関数は通常、ヘッダに定義があると呼び出し側で自動的に実体化されます。ところが、ライブラリ実装の都合で
-
宣言はヘッダ
-
定義(実装)は
implot_items.cppの中
という形になっていると、呼び出し側の翻訳単位では実体化されません。結果として、ImPlot側が“明示的に実体化した型”以外で呼ぶとリンクエラーになります。
今回まさにそれが起きていて、Linuxでunsigned long(64bit)を要求したのに、ImPlot側にその型の実体がない、ということです。
最短で直す:unsigned long をやめて固定幅整数にする(推奨)
一番堅くて移植性が高い解決はこれです。
-
棒グラフ用の配列を
uint32_t/uint64_t/int32_t/int64_tのような固定幅型に揃える -
PlotBars呼び出しもその型に合わせる(必要ならキャストではなく、データ側を変える)
例として、カウント値・インデックス値が本当に32bitで十分なら uint32_t に寄せると、Windows/Linuxで同じ型になります。
ポイント
-
unsigned long/size_tは環境依存になりやすい -
「Windowsで通る」は安全性の証明にならない(むしろ危険信号)
すぐ効く回避策:呼び出し側で型を変える(キャスト)
データ構造の変更が重い場合は、呼び出し箇所で型を“ImPlotが想定している型”に寄せます。
-
unsigned long配列をそのまま渡さず、unsigned intやImU64などに変換したバッファを作って渡す -
もしくは、表示用だけ
double/floatに変換して渡す(プロット用途なら十分なことが多い)
注意
-
キャストで無理やり型を合わせると、要素サイズがズレて壊れます(
unsigned long*をuint32_t*として渡すのはNG) -
必ず「変換した実体のバッファ」を用意するのが安全です
根本対応:ImPlot側で unsigned long の明示的インスタンス化を追加する
テンプレートが.cpp実装で、明示的インスタンス化の方式を採っている場合、ImPlot側に「この型でも作って」と指示するのが王道です。
-
implot_items.cppの末尾付近にある“明示的インスタンス化”の並びにunsigned long(あるいはuint64_t相当)を追加する -
ただしこれは ライブラリ改変 になるため、将来の更新でコンフリクトしやすい
チーム開発やCIを考えるなら、推奨は「プロジェクト側で固定幅型へ統一」です。
Zigビルドで気をつける点:C++標準ライブラリとリンカの組み合わせ
ログを見る限り、Zigのビルドで-lc++ -lcを付けてld.lldでリンクしています。ここ自体が即原因とは限りませんが、Linuxでは
-
使っているC++標準ライブラリ(libc++ / libstdc++)
-
ABI差
-
最適化やLTOの有無
で症状の見え方が変わることがあります。今回のように「特定テンプレートだけ未定義」は、まず型差・実体化不足を疑うのが最短ルートです。C++ランタイムを入れ替える前に、unsigned long問題を潰すのが得策です。
チェックリスト(この順で潰すと早い)
-
PlotBarsに渡している配列の型がunsigned long/size_tになっていないか確認 -
それを
uint32_t/uint64_tなど固定幅型に変更(推奨) -
変更が難しければ、表示用に別バッファ(
doubleなど)へ変換して渡す -
どうしても
unsigned longを使うなら、ImPlot側に明示的インスタンス化を追加(最終手段)
まとめ:Linuxで落ちるのは「環境差」ではなく「型とテンプレート」の必然
今回のリンクエラーは、ImPlotが悪いというより、C++のテンプレートと環境依存型の組み合わせが起こす定番の罠です。Windowsでの成功は「unsigned long がたまたま別の型に見えていただけ」という可能性が高く、Linuxで顕在化したのが今回の未定義シンボルです。
最も堅い解決は、unsigned long や size_t のままプロット関数に渡さず、固定幅型に統一すること。これだけで、Windows/Linux双方で再発しにくいコードになります。