したがって、私が知る限り、これはCで有効です。
foo.c
struct foo {
int a;
};
bar.c
struct foo {
char a;
};
しかし、関数に関する同じことは違法です:
foo.c
int foo() {
return 1;
}
bar.c
int foo() {
return 0;
}
リンクエラー(関数foo
の複数の定義)が発生します。
何故ですか? Cが一方を処理できて他方を処理できないようにする構造体名と関数名の違いは何ですか?この動作はC++にも拡張されますか?
何故ですか?
struct foo {
int a;
};
オブジェクトを作成するためのテンプレートを定義します。オブジェクトや関数は作成しません。 struct foo
がコードのどこかで使用されない限り、コンパイラ/リンカーに関する限り、これらのコード行は存在しない可能性があります。
CとC++が互換性のないstruct
定義を処理する方法には違いがあることに注意してください。
投稿されたコードのstruct foo
の異なる定義は、それらの使用法を混同しない限り、Cプログラムでは問題ありません。
ただし、C++では無効です。 C++では、外部リンケージがあり、同じように定義する必要があります。詳細については、 .2 One definition rule/5 を参照してください。
この場合の際立った概念はlinkageと呼ばれます。
C構造体では、unionタグまたはenumタグにリンケージなしがあります。それらは、そのスコープに対して事実上ローカルです。
6.2.2識別子の連結
6次の識別子にはリンケージがありません。オブジェクトまたは関数以外のものとして宣言された識別子。関数パラメーターとして宣言された識別子。ストレージクラス指定子extern
なしで宣言されたオブジェクトのブロックスコープ識別子。
同じスコープ内で再宣言することはできません(いわゆる前方宣言を除く)。ただし、異なる翻訳単位を含む異なるスコープで自由に再宣言できます。異なるスコープでは、完全に独立した型を宣言できます。これはあなたの例にあるものです:2つの異なる翻訳単位(つまり、2つの異なるファイルスコープ)で、2つの異なる関連のないstruct foo
タイプ。これは完全に合法です。
一方、関数にはCのリンケージがあります。この例では、これら2つの定義はexternalリンケージで同じ関数foo
を定義しています。また、プログラム全体で外部リンケージ機能の複数の定義を提供することはできません
6.9外部定義
5[...]外部リンケージで宣言された識別子が式で使用される場合(のオペランドの一部として以外)sizeof
または_Alignof
演算子の結果が整数定数である場合)、プログラム全体のどこかに、識別子の外部定義が1つだけ存在するものとします。それ以外の場合は、1つのみです。
C++では、linkageの概念が拡張されています。これは、特定のリンケージを、タイプを含むはるかに多様なエンティティに割り当てます。 C++クラスでは、型にリンケージがあります。名前空間スコープで宣言されたクラスには、外部リンケージがあります。また、C++の1つの定義ルールでは、外部リンケージを持つクラスに(異なる翻訳単位にわたって)複数の定義がある場合、これらのすべての翻訳単位で同等に定義されることを明示的に規定しています( http://eel.is/c+ + draft/basic.def.odr#12 )。したがって、C++では、struct
定義は違法になります。
C++ ODRルールのために、C++でも関数定義は違法のままです(ただし、本質的にはCと同じ理由です)。
関数定義は両方とも、外部リンケージを持つfoo
というエンティティを宣言します。C標準では、外部リンケージを持つエンティティの定義は複数あってはならないと述べています。定義した構造体タイプは外部リンケージを持つエンティティではないため、struct foo
の定義を複数持つことができます。
同じ名前を使用して外部リンケージを持つオブジェクトを宣言した場合、エラーになります。
foo.c
struct foo {
int a;
};
struct foo obj;
bar.c
struct foo {
char a;
};
struct foo obj;
これで、obj
と呼ばれる2つのオブジェクトがあり、どちらにも外部リンクがありますが、これは許可されていません。
オブジェクトの1つが宣言されているだけで、定義されていない場合でも、依然として間違っています。
foo.c
struct foo {
int a;
};
struct foo obj;
bar.c
struct foo {
char a;
};
extern struct foo obj;
obj
の2つの宣言が同じオブジェクトを参照しているため、これは未定義です。ただし、互換性のある型はありません(各ファイルでstruct foo
の定義が異なるため)。
C++には、inline
関数とinline
変数、テンプレート、およびその他のC++機能を考慮して、同様の、より複雑なルールがあります。 C++では、関連する要件はOne-Definition Rule(またはODR)として知られています。顕著な違いの1つは、C++では、2つの異なるstruct
定義を許可しないことです。たとえそれらが外部リンケージまたは翻訳ユニット間で共有されるオブジェクトを宣言するために使用されない場合でもです。
struct foo
の2つの宣言は、メンバーの型が同じではないため、互いに互換性がありません。これらを混同するために何もしない限り、各翻訳単位内で両方を使用しても問題ありません。
たとえば、これを行った場合:
foo.c:
struct foo {
char a;
};
void bar_func(struct foo *f);
void foo_func()
{
struct foo f;
bar_func(&f);
}
bar.c:
struct foo {
int a;
};
void bar_func(struct foo *f)
{
f.a = 1000;
}
struct foo
が期待するbar_func
は、struct foo
が提供するfoo_func
と互換性がないため、 ndefined behavior を呼び出します。
構造体の互換性については、 C standard のセクション6.2.7で詳しく説明しています。
1タイプが同じ場合、2つのタイプには互換性のあるタイプがあります。 2つの型に互換性があるかどうかを判断するための追加の規則については、型指定子については6.7.2、型修飾子については6.7.3、宣言子については6.7.6で説明しています。さらに、別の翻訳単位で宣言された2つの構造体、ユニオン、または列挙型は、それらのタグとメンバーが次の要件を満たす場合に互換性があります。両方がそれぞれの翻訳単位内のどこかで完了する場合、次の追加要件が適用されます。対応するメンバーの各ペアが互換性のある型で宣言されるように、メンバー間に1対1の対応があります。ペアの一方のメンバーがアライメント指定子で宣言されている場合、もう一方のメンバーは同等のアライメント指定子で宣言されています。ペアの一方のメンバーが名前で宣言されている場合、もう一方のメンバーは同じ名前で宣言されています。 2つの構造の場合、対応するメンバーは同じ順序で宣言されます。 2つの構造体またはユニオンの場合、対応するビットフィールドの幅は同じでなければなりません。 2つの列挙の場合、対応するメンバーは同じ値を持ちます。
2同じオブジェクトまたは関数を参照するすべての宣言は、互換性のある型を持たなければなりません。それ以外の場合、動作は未定義です。
要約すると、struct foo
の2つのインスタンスには、互換性を確保するために、同じ名前とタイプで同じ順序のメンバーが必要です。
struct
をヘッダーファイルで1回定義し、その後そのヘッダーを複数のソースファイルに含めるには、このようなルールが必要です。これにより、複数のソースファイルでstruct
が定義されますが、各インスタンスには互換性があります。
関数定義をリンカーから隠すには、キーワードstaticを使用します。
foo.c
static int foo() {
return 1;
}
bar.c
static int foo() {
return 0;
}
名前の違いは、存在するほどではありません。構造体の定義はどこにも保存されず、その名前はコンパイル中にのみ存在します。
(同じ名前の構造体の使用に競合がないことを確認するのは、プログラマーの責任です。
一方、関数はどこかに保存する必要があり、外部リンケージがある場合、リンカーにはその名前が必要です。
関数をstatic
にして、それぞれのコンパイル単位の外で「見えない」ようにすると、リンクエラーはなくなります。