Visual Studio 2008では同名のソースファイルを扱うことが出来ない件

Visual Studio 2008では同名のソースファイルが同一のプロジェクト内にあってはいけません。
僕は、たとえ無知だったとはいえ、その過ちを犯してしまい、全く想定外の挙動によって深い悲しみに包まれました。。。
あの頃の自分に教えてあげたい・・・ファイル名は変えろ・・・と。

サンプルプロジェクトはこちらです。
Google ドライブ - 1 か所であらゆるファイルを保管


さて、もともと僕が同名のソースファイルを使うことになった動機は、
似たような、でもちょっと違う挙動のソースをコンパイルスイッチによって、
どれをコンパイルするかコントロールしようというものでした。


サンプルプロジェクトでは、common.hにfoo, barの関数宣言があり、
実装はimplディレクトリ内の各impl[n]ディレクトリにある、detail.cppに書こうという魂胆です。

それぞれのdetail.cppでは、IMPLという識別子にdefineされている数字によって自分をコンパイルするかどうかの
インクルードガード的なものでソースが囲まれています。

プロジェクトのプロパティでIMPLをグローバルにdefineして、コンパイルするファイルを決定しようというのです。俺つえー!

たとえば、impl3にあるやり方でfoo, barを一番最初に実装できたとしましょう。impl3からとか恣意的ですがお気になさらず。
impl1とimpl2はまだ未定義なので、自分がコンパイルされそうになったときはエラー出しておきます。

このままビルドしてみましょう。
(ちなみに、もしかしたらこの時点であなたの環境と僕の環境では状況が違うかも知れません。)
(まあ、おそらく同じでしょう。)

こんぱいるなう・・・

1>------ ビルド開始: プロジェクト: SameNameProj, 構成: Debug Win32 ------
1>コンパイルしています...
1>detail.cpp
1>c:\vc\test\samenameproj\samenameproj\impl\impl1\detail.cpp(3) : fatal error C1189: #error : impl1 has not been implemented yet...
1>main.cpp
1>コードを生成中...
1>ビルドログは "file://c:\vc\test\SameNameProj\SameNameProj\Debug\BuildLog.htm" に保存されました。
1>SameNameProj - エラー 1、警告 0
========== ビルド: 0 正常終了、1 失敗、0 更新不要、0 スキップ ==========

おっと、そうです。普通に考えてimpl1, impl2, impl3があったなら普通はimpl1から作りそうなものですから、IMPL=1と定義していたのでした。いけないいけない。

プロジェクトのプロパティからプリプロセッサの定義をIMPL=1からIMPL=3に変更してもう一度ビルドしてみましょう。

1>------ ビルド開始: プロジェクト: SameNameProj, 構成: Debug Win32 ------
1>コンパイルしています...
1>detail.cpp
1>main.cpp
1>コードを生成中...
1>リンクしています...
1>LINK : 前回のインクリメンタル リンクで C:\vc\test\SameNameProj\Debug\SameNameProj.exe が見つからなかったか、ビルドされませんでした。フル リンクを行います。
1>main.obj : error LNK2019: 未解決の外部シンボル "void __cdecl bar(void)" (?bar@@YAXXZ) が関数 _main で参照されました。
1>main.obj : error LNK2019: 未解決の外部シンボル "void __cdecl foo(void)" (?foo@@YAXXZ) が関数 _main で参照されました。
1>C:\vc\test\SameNameProj\Debug\SameNameProj.exe : fatal error LNK1120: 外部参照 2 が未解決です。
1>ビルドログは "file://c:\vc\test\SameNameProj\SameNameProj\Debug\BuildLog.htm" に保存されました。
1>SameNameProj - エラー 3、警告 0
========== ビルド: 0 正常終了、1 失敗、0 更新不要、0 スキップ ==========

ん!!??未解決の外部シンボルだとおおお!!何が起きたし!!
あー、あれかな、objファイルとか残ってるのかなー、リビルドしましょうね。

1>------ ビルド開始: プロジェクト: SameNameProj, 構成: Debug Win32 ------
1>コンパイルしています...
1>detail.cpp
1>main.cpp
1>コードを生成中...
1>リンクしています...
1>LINK : 前回のインクリメンタル リンクで C:\vc\test\SameNameProj\Debug\SameNameProj.exe が見つからなかったか、ビルドされませんでした。フル リンクを行います。
1>main.obj : error LNK2019: 未解決の外部シンボル "void __cdecl bar(void)" (?bar@@YAXXZ) が関数 _main で参照されました。
1>main.obj : error LNK2019: 未解決の外部シンボル "void __cdecl foo(void)" (?foo@@YAXXZ) が関数 _main で参照されました。
1>C:\vc\test\SameNameProj\Debug\SameNameProj.exe : fatal error LNK1120: 外部参照 2 が未解決です。
1>ビルドログは "file://c:\vc\test\SameNameProj\SameNameProj\Debug\BuildLog.htm" に保存されました。
1>SameNameProj - エラー 3、警告 0
========== ビルド: 0 正常終了、1 失敗、0 更新不要、0 スキップ ==========

・・・え。

コンパイルは・・・ちゃんと出来ているのだろうか・・・。
プロジェクトをクリーンしたあとでimpl3/detail.cppをVisual Studio内に開いてCtrl+F7でもってコンパイルしましょう。

1>------ ビルド開始: プロジェクト: SameNameProj, 構成: Debug Win32 ------
1>コンパイルしています...
1>detail.cpp
1>ビルドログは "file://c:\vc\test\SameNameProj\SameNameProj\Debug\BuildLog.htm" に保存されました。
1>SameNameProj - エラー 0、警告 0
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========

ふむ。
detail.cppがちゃんとコンパイルされて、1 正常終了になっているじゃないか。

出来てるっぽいのでプロジェクトをビルドしてみます。


1>------ ビルド開始: プロジェクト: SameNameProj, 構成: Debug Win32 ------
1>コンパイルしています...
1>main.cpp
1>リンクしています...
1>main.obj : error LNK2019: 未解決の外部シンボル "void __cdecl bar(void)" (?bar@@YAXXZ) が関数 _main で参照されました。
1>main.obj : error LNK2019: 未解決の外部シンボル "void __cdecl foo(void)" (?foo@@YAXXZ) が関数 _main で参照されました。
1>C:\vc\test\SameNameProj\Debug\SameNameProj.exe : fatal error LNK1120: 外部参照 2 が未解決です。
1>ビルドログは "file://c:\vc\test\SameNameProj\SameNameProj\Debug\BuildLog.htm" に保存されました。
1>SameNameProj - エラー 3、警告 0
========== ビルド: 0 正常終了、1 失敗、0 更新不要、0 スキップ ==========

何がイケナイっていうのさ!!!


そうだ!生成されたobjファイルを覗いてみましょう。今日RSSリーダーを見てたらobjdumpというものが目に止まったので早速使ってみるの術。
mingwとかcygwinに入ってた気がします。

objファイルが生成されているSameNameProjのDebugディレクトリに移動して

objdump -S detail.obj

とすると、objファイルの中身の逆アセンブルとソースとを混ぜて出力してくれるらしいですよ!すごい!

C:\vc\test\SameNameProj\SameNameProj\Debug>
detail.obj: ファイル形式 pe-i386

・・・え。何も出ない。。。

期待される出力

C:\vc\test\SameNameProj\SameNameProj\Debug>
detail.obj: ファイル形式 pe-i386


Disassembly of section .text:

00000000 :
0: 55 push %ebp
1: 8b ec mov %esp,%ebp
3: 81 ec c0 00 00 00 sub $0xc0,%esp
9: 53 push %ebx
a: 56 push %esi
b: 57 push %edi
c: 8d bd 40 ff ff ff lea -0xc0(%ebp),%edi
12: b9 30 00 00 00 mov $0x30,%ecx
17: b8 cc cc cc cc mov $0xcccccccc,%eax
1c: f3 ab rep stos %eax,%es:(%edi)
1e: 8b f4 mov %esp,%esi
20: a1 00 00 00 00 mov 0x0,%eax
25: 50 push %eax
26: 68 00 00 00 00 push $0x0
2b: 8b 0d 00 00 00 00 mov 0x0,%ecx
31: 51 push %ecx
32: e8 00 00 00 00 call 37
37: 83 c4 08 add $0x8,%esp
3a: 8b c8 mov %eax,%ecx
3c: ff 15 00 00 00 00 call *0x0


・・・(生成されたこのobjはオカシイ!!ていうかエクスプローラでみたら2kbって小さすぎじゃね?)*1


(この後僕は一生懸命原因を探すのですが、結局よく分かりません。)


特に同名にしておく理由はなかったので、試しにimpl3/detail.cppをimpl3/detail3.cppにリネームしてみましょう。(Visual Studioのソリューションエクスプローラ上で行うと、元ファイル名をプロジェクトから削除したり、新しいファイル名でプロジェクトに追加したりするのを自動でやってくれるので楽です。)

おお、何事もなくリンクが通ってしまいました・・・!!
解決した!



僕より200倍位聡明な方なら、Debugディレクトリにobjファイルが生成されるのだから、
同名のソースがあればobjファイルも競合してしまうじゃないかということにお気づきだと思います。
(逆に、気づいたなら僕より200倍位聡明だというのも多分真です。)



なるほど。同名のソースファイルがあるとobjファイルの生成に問題があるからダメなんだね。と。これで解決です!



以下は駄文です。同名のソースファイルがダメだと分かった今では読まなくても問題ないです。


さて、先ほどのファイル名を変える前はなぜおかしなobjが生成されたのでしょうか。聡明な意見を取り入れて考えれば、別の同名のソースファイルがコンパイルされて競合してたりするんじゃないか、という予想が立ちます。

あれ、でも、さっきCtrl+F7でimpl3/detail.cppだけをコンパイルしようとしても、期待するobjは生成されなかったっぽいですね・・・1ソースだけをコンパイルするなら競合なんてしないはず・・・???


結論から先に書きますと、Visual Studio 2008では同名のソースがプロジェクトに含まれていると、どれかコンパイルされるかはファイルの更新日時に依存するようです。


impl3/detail.cppのファイル名を変更する前に話を戻します。


いま、この3つdetail.cppのうち、impl3/detail.cppよりも、impl1/detail.cpp impl2/detail.cppのファイルの更新日時の方が新しくなるようにします。
(ソリューションエクスプローラ上でファイル名のcppをcpppにしてまたcppに戻すとかすればファイルの更新日時が更新されるでしょう。僕の環境だと、ソースいじって保存しただけではファイルの更新日時は変わらなかったのですが、これで更新できました)

プロジェクトのプロパティから、IMPL=3にすることをお忘れなく。
ビルドしてみましょう。


見事にビルドが通ると思います。
一番古いファイルがビルドの対象となるようです。


ほほう。
なるほど。そうなのですねぇ。
ということはです。



いまimpl1/detail.cppが一番古くなるようにして、プリプロセッサの定義もIMPL=1に変更して、
そして先程からやっているようにimpl3/detail.cppをCtrl+F7でコンパイルしてみましょう。


さて・・・

1>------ ビルド開始: プロジェクト: SameNameProj, 構成: Debug Win32 ------
1>コンパイルしています...
1>detail.cpp
1>c:\vc\test\samenameproj\samenameproj\impl\impl1\detail.cpp(3) : fatal error C1189: #error : impl1 has not been implemented yet...
1>ビルドログは "file://c:\vc\test\SameNameProj\SameNameProj\Debug\BuildLog.htm" に保存されました。
1>SameNameProj - エラー 1、警告 0
========== ビルド: 0 正常終了、1 失敗、0 更新不要、0 スキップ ==========


なるほど、予想通りです。
(なんだそれ!!!!!!!!!!)(こんなのぜったいおかしいよ!!!!!!!!!!)(わけがわからないよ!!!!!!!!!!!!!!!!!!)



ちなみに、それぞれのソースのプロパティから、objファイルの出力先が競合しないようにすると、
ファイルの更新日時とか関係なくちゃんとそれぞれのソースがコンパイルされるようになります。


Visual Studio 2005の時は、objファイルが競合しそうなときはファイル名に数字をインクリメントしていって、
自動で解決してくれたみたいですが、2008はそうではないようです。2010は知りません。

同名.cppファイルの自動解決は出来ないのか
(こちらを教えてくださった[twitter:@codeanalyze]さんありがとうございます。)

*1:僕の環境ではこの時点で生成されたdetail.objは1767byteでした。