次のコードで警告が表示されるのはなぜですか?
int main(void)
{
struct {int x; int y;} test = {42, 1337};
struct {int x; int y;} *test_ptr = &test;
}
結果:
warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
struct {int x; int y;} *test_ptr = &test;
^
これらは2つの匿名構造タイプです(どちらにもタグはありません)。このような構造タイプはすべて(単一の翻訳単位で)区別されます—同じタイプになることはありません。タグを追加してください!
標準の関連する文は§6.7.2.1にあります構造体と共用体の指定子:
¶8struct-or-union-specifier内のstruct-declaration-listの存在は、内の新しい型を宣言します翻訳ユニット。
struct-declaration-listは、タイプの{
と}
の間のマテリアルを参照します。
つまり、コードには、struct { … }
ごとに1つずつ、2つの異なるタイプがあります。 2つのタイプは別々です。あるタイプの値を別のタイプに正式に割り当てたり、ポインタを作成したりすることはできません。実際、セミコロンの後でこれらのタイプを再度参照することはできません。
それはあなたが持つことができることを意味します:
int main(void)
{
struct {int x; int y;} test = {42, 1337}, *tp = &test;
struct {int x; int y;} result, *result_ptr;
result_ptr = &result;
…
}
ここで、test
とtp
は同じタイプ(1つは構造体、もう1つは構造体へのポインター)を参照し、同様にresult
とresult_ptr
は同じタイプを参照します。タイプ、初期化と割り当ては問題ありませんが、2つのタイプは異なります。どちらのタイプの複合リテラルを作成するかは明確ではありません— (struct {int x; int y;}){.y = 9, .x = 8}
を記述する必要がありますが、struct-declaration-listの存在はそれが別の新しいタイプ。
コメントに記載されているように、セクション§6.2.7互換型と複合型もあります。
¶1…さらに、別々の翻訳単位で宣言された2つの構造体、共用体、または列挙型は、タグとメンバーが次の要件を満たしている場合に互換性があります。一方がタグで宣言されている場合、もう一方は同じタグで宣言されます。両方がそれぞれの翻訳単位内のどこかで完了した場合、次の追加要件が適用されます。対応するメンバーの各ペアが互換性のあるタイプで宣言されるように、メンバー間に1対1の対応が必要です。ペアの一方のメンバーがアライメント指定子で宣言されている場合、もう一方のメンバーは同等のアライメント指定子で宣言されます。ペアの一方のメンバーが名前で宣言されている場合、もう一方のメンバーは同じ名前で宣言されます。 2つの構造の場合、対応するメンバーは同じ順序で宣言されるものとします。 2つの構造体または和集合の場合、対応するビットフィールドの幅は同じでなければなりません。
大まかに言えば、2つの変換ユニットのタイプの定義(「ソースファイル」とインクルードヘッダーを考えてください)が同じである場合、それらは同じタイプを参照します。よろしくお願いします!そうしないと、他の細かい点の中でも、標準のI/Oライブラリを機能させることができませんでした。
変数&test
およびtest_ptr
は匿名の構造体であり、さまざまなタイプがあります。
同じ翻訳単位で定義された匿名の構造体は、互換性のあるタイプにはなりません。1 標準では、同じ変換単位内の2つの構造タイプ定義の互換性が定義されていないためです。
コードをコンパイルするには、次のようにします。
struct {int x; int y;} test = {42, 1337} , *test_ptr;
test_ptr = &test;
1 (引用元:ISO:IEC 9899:201X 6.2.7互換型および複合型1)
2つのタイプは、タイプが同じである場合、互換性のあるタイプになります。 2つの型に互換性があるかどうかを判断するための追加の規則は、型指定子については6.7.2、型修飾子については6.7.3、宣言子については6.7.6で説明されています。 さらに、別々の変換単位で宣言された2つの構造体、共用体、または列挙型は互換性がありますそれらのタグとメンバーが次の要件を満たしている場合:一方がタグで宣言されている場合、もう一方は同じで宣言されます鬼ごっこ。両方がそれぞれの翻訳単位内のどこかで完了した場合、次の追加要件が適用されます。対応するメンバーの各ペアが互換性のあるタイプで宣言されるように、メンバー間に1対1の対応が必要です。ペアの一方のメンバーがアライメント指定子で宣言されている場合、もう一方のメンバーは同等のアライメント指定子で宣言されます。ペアの一方のメンバーが名前で宣言されている場合、もう一方のメンバーは同じ名前で宣言されます。 2つの構造の場合、対応するメンバーは同じ順序で宣言されるものとします。 2つの構造体または和集合の場合、対応するビットフィールドの幅は同じでなければなりません。 2つの列挙の場合、対応するメンバーは同じ値を持つ必要があります。
Cは元々、部分的または完全に同一のレイアウトを持つ構造体へのポインターを交換可能に使用して共通部分にアクセスできるように設計されており、構造体メンバーに個別の名前空間を実装したC89より前のバージョンの言語は、通常、ポインターを交換可能に使用する機能を保持していました。タイプキャスト、voidによる変換などの助けを借りて。コンパイラが異なるサイズの配列の前に異なる量のパディングを挿入することは合法ですが、ほとんどのコンパイラは、そうせずにレイアウトを実行することを指定します。つまり、簡単に記述できます。次のオブジェクトのいずれか、または同様に宣言された他のオブジェクト(サイズ4、5、24601など)へのポインターを受け入れる関数。
struct { int size; int foo[2]; } my_two_foos = {2, {1,2} };
struct { int size; int foo[3]; } my_three_foos = {3, {4,5,6} };
実装は、そのような構造を不可欠にするレイアウトについての保証を提供する必要がなかったため、標準の作成者は、そのような機能が不可欠であるコンパイラ(たとえば、構造が必要なコンパイラ)のために、コンパイラがレイアウトの互換性の概念を認識することを義務付けることを拒否しました上記のように一貫した方法でレイアウトされます)すでにそれをサポートしており、標準がそれを義務付けているかどうかにかかわらず、彼らがそうし続けないと信じる理由はありませんでした。機能または保証が義務付けられるべきかどうかの推進要因は、その機能または保証を安価かつ簡単にサポートできるプラットフォームでのメリットをコストが上回るかどうかではなく、サポートが最も高価で最小限の有用性であるプラットフォームでのコストであるかどうかでした。メリットよりも重要です同じプラットフォーム上で。
残念ながら、コンパイラの作成者は、標準が実装が「準拠」するために必要なものだけを定義し、特定のプラットフォームに適したコンパイラを作成する機能を定義していないという事実を見失っています。行動が最小限のコストで何十年もサポートされてきたプラットフォームで前例を無視する言い訳を見つけることにますます積極的になります。結果として、以前はありふれた動作に依存するコードは、攻撃性の低いコンパイラを使用する場合に必要となるよりもはるかに多くの最適化を無効にする-fno-strict-aliasing
などのコンパイラオプションを使用する場合にのみ正しく機能する可能性があります。