あるDLLプロジェクトでCソースが複数あり、それぞれのソースでグローバル変数が定義されていま
す。
そのグローバル変数は各ソースごとにのみ使用されるものですので、ヘッダーファイルにはextern等
の関連宣言も一切ない状態です。
ただ、ここから一般的ではないことかも知れませんが、グローバル変数がソースのあちこちで使用さ
れているとローカル変数なのかどうか分かりづらいという個人的な事情により、グローバル変数は一
つの構造体にまとめています。
この構造体は各ソースごとに定義しています。
例えば
typedef struct {
....
....
} GLOBAL_MATOME;
GLOBAL_MATOME matome;
という同一名称の構造体が各ソースにあり、内部の定義は各ソースで異なります。
そして、matome.hogeという記述があれば「このソースのグローバル変数なんだな」と分かるようにし
ています。
問題はここからなのですが、VC2012でデバッグしていて、あるソースでmatome部分の参照・書き込み
を行う場所にてブレークポイントを置き、その場所でウォッチで見ると、matomeの内容がそのソース
のものではなく、別ソースのmatome定義が参照されてしまいます。
試行錯誤の結果、各ソースのGLOBAL_MATOMEという同じ名前をそれぞれ別の名称に変更した結果、正し
く参照されるようになりました。
ここで自信が無くなってきたのですが、そもそも同一名称の構造体定義を各ソースに持つこと自体、
NGだったのでしょうか?
それとも、VC自体の設定ミス、もしくは何らかのファイルが壊れているのでしょうか?
よろしくお願いします。
C++ と違って C は namespace もないし ODR もないので、
ISO 9899 の文法・意味規則上は提示コードで問題ないと思う。
が、特定 C 処理系の動作とか、そもそも C ではなくてデバッガの挙動とか、
そういう話はまた別なのであって...
Visual studio のデバッガは C++ ソースのデバッグができる必然があるので、
デバッガ自体はおそらく ODR を要求しているのだと思う。
となると、提示ソースコードの書き方は C++ 仕様における ODR 違反であることになる。
typedef struct { ... } XYZ; というのが「無名構造体」であることは理解しているかい?
ファイルごとに内容の異なる無名構造体に同じ「別名」をつけたら ODR 違反。
ウチでは無名構造体は使用禁止にしている (いろいろと問題が多いので)
無名構造体を使うのをやめて、きっちり名前をつけてやるといいだろう。
struct foo_c_globals { ... }; のように ODR にならないようにすると
(typedef struct foo_c_globals { ... } GLOBAL_MATOME; とか)
デバッガも混乱しない、んぢゃ無いかな (ウチに VS2012 はないので推測)
まあもっとも、まとめなきゃならないほど大域変数を多用している設計スタイル自体が
まずいと思う。そんなに大域変数って使う必要がある?
普通に (PC 用の) コード書いたら大域変数を使うことってまず無いと思うが・・・
「正しく参照できない」とは、
1.プログラムの実行上正しく参照されない。
(プログラム自体が正しく動かない)
2.デバッグ用のウォッチ上で正しく参照できない。
(ウォッチで展開した時、別のソースのメンバが表示される)
のどちらでしょう。
文面からは2.のような気もしますが、残念ながら読み取れませんでした。
2.と仮定した場合、単にデバッグ用のプログラムデータベースでの参照不良となりますが
、
その理由はタグ名が省略されているからかもしれません。
VSは伝統的にタグなしの構造体を無名の構造体と扱います(デバッグ用上の話です)。
ある変数の型が、複数ある無名の構造体のどれであるかわからなくなっているのかもしれ
ません。
この場合、その構造体をtypedefし、かつタグを正しく書けば、同名でも区別してもらえ
るかもしれません。
(例)
typedef struct GLOBAL_MATOME // タグ名
{
}GLOBAL_MATOME; // 型名
GLOBAL_MATOME matome; // 実体
などとしてみてはどうでしょう。
だめだったらごめんなさい。
各ソースのみで使われるのであれば,オブジェクトの識別子の定義にstaticをつけた方がよいか
もしれません。
一応,標準CにもODRに近い記述はありまして,引用元がちょっと古いですが
ISO/IEC 9899:1999 6.9 External definitions ¶5
> If an identifier declared with external linkage is used in an expression (other than
as part of the operand of a sizeof operator whose result is an integer constant),
somewhere in the entire program there shall be exactly one external definition for the
identifier;
> otherwise, there shall be no more than one.
と,「exactly one external definition」を要求しています。
# JIS X 3010:1993の翻訳文が微妙で,ISO/IEC 9899-1990にexactlyがあったかどうかがわから
ない……。
皆さんレスありがとうございます。
皆さんのご意見を元に、まず、無名構造体によるODR違反の線で修正をしてみました。
(当初typedefを使ったままタグを付けたり試行錯誤しましたが、現象変わらずなのでシンプルにしてみまし
た。)
struct tagGLOBAL_MATOME {
....
....
};
struct tagGLOBAL_MATOME matome;
上記の修正で現象は変わらず、デバッグ中Aソース参照中にmatomeをウォッチすると、Bソースのmatomeが展
開されます。
(YuOさんのご意見を元にstaticを付けてみましたが、これも現象変わらずでした。)
結果、タグ名を各ソースで異なる名称にすれば回避できました。
当初の無名構造体のまま、別名で回避したときと同じ状況です。
結論としては、VC2012デバッガとしてはODRを求めているということになるのでしょうか。
恥ずかしながらODR自体知りませんでしたが、無名構造体に限らず、同一名称の構造体タグを使うのもODR違
反ということでしょうか。
心配なのは、これはデバッガだけの問題なのか、ビルド成果物も実は別ソースの構造体に対してアクセスし
ているのかという所です。
何食わぬ顔して動いているようには見えますが・・・。
>>tetrapodさん
> ウチでは無名構造体は使用禁止にしている (いろいろと問題が多いので)
よろしければ、主にどのような問題がありますでしょうか?
> まあもっとも、まとめなきゃならないほど大域変数を多用している設計スタイル自体が
ごもっともです。
>>仲澤さん
> 「正しく参照できない」とは、
分かりにくくてすみません。2.のことです。
> 2.と仮定した場合、単にデバッグ用のプログラムデータベースでの参照不良となりますが
一応、リビルドやクリーンを行いましたが、同一名称がやはりダメなんでしょうか・・・
>>YuOさん
> 一応,標準CにもODRに近い記述はありまして,引用元がちょっと古いですが
なるほどです。英語を確実に読み取る自信はありませんが、今回の件はNGなのかもですね。
>ここで自信が無くなってきたのですが、そもそも同一名称の構造体定義を各ソースに持
つこと自体、NGだったのでしょうか?
C言語の言語仕様的にOKですが、大抵の場合はリンカで弾かれます。
試しにグローバルスコープで同名の宣言があると、VS2012ではビルド時にこんなエラー
が出ます。
(コンパイルは通ってますが、リンク時にエラーになってる例です)
error LNK2005: int a (?a@@3HA) は既に hogehoge.obj で定義されています。
fatal error LNK1169: 1 つ以上の複数回定義されているシンボルが見つかりました。
ですから、他所で定義されているのであれば「他所で定義されてるよ」という、extern
は必須の宣言になります。
以下のように書けば、わりと簡潔に書けます。
・共通で読み込むヘッダファイルの1箇所に記述
typedef struct {
....
....
} GLOBAL_MATOME;
extern GLOBAL_MATOME matome;
・適当なソースファイルのどこか1箇所に記述
GLOBAL_MATOME matome;
あと、皆さんとのやり取りを見ていると構造体のインスタンスに対する理解が怪しい気
がするので、構造体をクラスに変えてコンストラクタ/デストラクタにブレークポイント
しかけて、どのタイミングで生成/破棄されるかを調べると理解が深まるんじゃないかと
思います。
・共通で読み込むヘッダファイルの1箇所に記述
class GLOBAL_MATOME{
public:
GLOBAL_MATOME(){int a=0;};//ここにブレークポイント
~GLOBAL_MATOME(){int a=0;};//ここにブレークポイント
....
....
};
extern GLOBAL_MATOME matome;
・適当なソースファイルのどこか1箇所に記述
GLOBAL_MATOME matome;
>>ARさん
構造体の定義については、各ソースで同名タグながら内容は全く違いますので、ヘッダーに
一切記述せず各ソース個々に記述しています。
よって、リンクエラーも出てきません。
問題となっているのはデバッグ時に対象構造体をウォッチすると、別ソースの同名タグ構造
体が参照されてしまうことでして、現状ではデバッガとしてはODR違反に該当していて追え
る状態では無いのではないかと解釈しました。
本件の解決策としては、同一名称の構造体タグを使わないということで解決とします。