9

別の質問への回答の一環として、このようなコードに出くわしました。これは、gcc が問題なくコンパイルします。

typedef struct {
    struct xyz *z;
} xyz;
int main (void) {
    return 0;
}

これは、自分自身を指す型 (リンクされたリストなど) を構築するために常に使用してきた手段ですが、自己参照を使用できるように構造体に名前を付ける必要があると常に考えていました。xyz *zつまり、その時点では typedef がまだ完成していないため、構造内で使用できませんでした。

ただし、この特定のサンプルでは構造体に名前が付けられておら、それでもコンパイルされます。構造体と typedef の名前が同じだったため、上記のコードを自動的に変換するコンパイラで黒魔術が行われていると最初は思っていました。

しかし、この小さな美しさも同様に機能します。

typedef struct {
    struct NOTHING_LIKE_xyz *z;
} xyz;

ここで何が欠けていますか?struct NOTHING_LIKE_xyzどこにもタイプが定義されていないため、これは明らかな違反のようです。

ポインターから実際の型に変更すると、予想されるエラーが発生します。

typedef struct {
    struct NOTHING_LIKE_xyz z;
} xyz;

qqq.c:2: error: field `z' has incomplete type

また、 を削除するとstruct、エラー ( parse error before "NOTHING ...) が表示されます。

これはISO Cで許可されていますか?


更新: Astruct NOSUCHTYPE *variable;もコンパイルされるため、有効と思われる構造内だけではありません。c99 標準には、構造体ポインターに対してこの寛大さを許すものは何も見つかりません。

4

7 に答える 7

8

警告が2番目のケースで述べているように、struct NOTHING_LIKE_xyzは、または未知のサイズの配列のような不完全な型です。void不完全な型は、(C17 6.7.2.1:3)を指す型としてのみ構造体に表示されます。ただし、構造体の最後のメンバーとして許可されるサイズが不明な配列を除き、構造体自体が不完全な型になります。この場合。次のコードは、不完全な型へのポインタを逆参照することはできません(正当な理由があります)。

不完全なタイプは、Cでソートのデータ型カプセル化を提供する可能性があります... http://www.ibm.com/developerworks/library/pa-ctypes1/の対応する段落は良い説明のようです。

于 2010-05-24T06:45:46.530 に答える
7

あなたが求めているC99標準の部分は6.7.2.3、パラグラフ7です:

struct-or-union identifier上記のフォームの一部として以外にフォームの型指定子が 発生し、タグとしての識別子の他の宣言が表示されない場合、不完全な構造体または共用体型を宣言し、識別子をのタグとして宣言します。そのタイプ。

...および6.2.5段落22:

未知のコンテンツの構造体または共用体タイプ(6.7.2.3で説明)は不完全なタイプです。そのタイプのすべての宣言について、同じスコープ内で後で定義するコンテンツを使用して同じ構造体または共用体タグを宣言することで完了します。

于 2010-05-24T07:11:10.893 に答える
2

1 番目と 2 番目のケースは明確に定義されています。これは、ポインターのサイズと配置がわかっているためです。C コンパイラは、構造体を定義するためにサイズとアラインメント情報のみを必要とします。

実際の構造体のサイズが不明であるため、3 番目のケースは無効です。

ただし、最初のケースを論理的にするには、構造体に名前を付ける必要があることに注意してください。

//             vvv
typedef struct xyz {
    struct xyz *z;
} xyz;

それ以外の場合、外側の構造体と は*z2 つの異なる構造体と見なされます。


2 番目のケースには、 「不透明なポインター」 (pimpl)として知られる一般的なユース ケースがあります。たとえば、ラッパー構造体を次のように定義できます。

 typedef struct {
    struct X_impl* impl;
 } X;
 // usually just: typedef struct X_impl* X;
 int baz(X x);

ヘッダーで、次に のいずれかで.c

 #include "header.h"
 struct X_impl {
    int foo;
    int bar[123];
    ...
 };
 int baz(X x) {
    return x.impl->foo;
 }

利点はそこ.cにあります。オブジェクトの内部をいじることはできません。これは一種のカプセル化です。

于 2010-05-24T06:55:28.290 に答える
1

うーん...私が言えるのは、あなたの以前の仮定が間違っていたということだけです。コンストラクトを (単独で、またはより大きな宣言の一部として)使用するたびstruct Xに、それは struct tag を持つ構造体型の宣言として解釈されXます。以前に宣言された構造体型の再宣言である可能性があります。または、新しい構造体型の最初の宣言にすることもできます。新しいタグは、それが表示されるスコープで宣言されます。あなたの特定の例では、たまたまファイルスコープです(C言語にはC ++のように「クラススコープ」がないため)。

この動作のより興味深い例は、宣言が関数プロトタイプに表示される場合です。

void foo(struct X *p); // assuming `struct X` has not been declared before

この場合、新しいstruct X宣言にはfunction-prototype スコープがあり、プロトタイプの最後で終了します。struct X後でファイルスコープを宣言する場合

struct X;

型のポインターをstruct X上記の関数に渡そうとすると、コンパイラーは、一致しないポインター型に関する診断を提供します

struct X *p = 0;
foo(p); // different pointer types for argument and parameter

これはまた、次の宣言で

void foo(struct X *p);
void bar(struct X *p);
void baz(struct X *p);

struct X宣言は異なる型の宣言であり、それぞれが独自の関数プロトタイプ スコープに対してローカルです。

しかし、次のように事前宣言するstruct X

struct X;
void foo(struct X *p);
void bar(struct X *p);
void baz(struct X *p);

すべてstruct Xの関数プロトタイプのすべての参照は、以前に宣言された同じstruct X型を参照します。

于 2010-05-24T07:16:41.947 に答える
1

名前を付ける必要があります。これで:

typedef struct {
    struct xyz *z;
} xyz;

z定義した名前のない構造体ではなく、完全な他の型を参照するため、それ自体を指すことはできません。これを試して:

int main()
{
    xyz me1;
    xyz me2;
    me1.z = &me2;   // this will not compile
}

互換性のない型に関するエラーが表示されます。

于 2010-05-24T06:55:58.527 に答える
0

私もこれについて疑問に思っていました。struct NOTHING_LIKE_xyz * zが前方宣言していることがわかりstruct NOTHING_LIKE_xyzます。ややこしい例として、

typedef struct {
    struct foo * bar;
    int j;
} foo;

struct foo {
    int i;
};

void foobar(foo * f)
{
    f->bar->i;
    f->bar->j;
}

ここでは、 ではなくf->bar、タイプを参照します。1 行目は正常にコンパイルされますが、2 行目はエラーになります。その場合、リンクされたリストの実装はあまり役に立ちません。struct footypedef struct { ... } foo

于 2010-05-24T06:56:44.577 に答える
0

構造体型の変数またはフィールドが宣言されると、コンパイラはその構造体を保持するのに十分なバイトを割り当てる必要があります。構造体には 1 バイトが必要な場合もあれば、数千バイトが必要な場合もあるため、コンパイラーが割り当てる必要のあるスペースの量を知る方法はありません。一部の言語は、1 つのパスで構造体のサイズを検出し、後のパスでそのスペースを割り当てることができるマルチパス コンパイラを使用します。ただし、C はシングルパス コンパイルを許可するように設計されているため、それは不可能です。したがって、C では、不完全な構造体型の変数またはフィールドの宣言を禁止しています。

一方、構造体へのポインター型の変数またはフィールドが宣言されている場合、コンパイラーは構造体へのポインターを保持するのに十分なバイトを割り当てる必要があります。 構造が 1 バイトであるか、100 万バイトであるかに関係なく、ポインターは常に同じ量のスペースを必要とします。 事実上、コンパイラは、その型に関する詳細情報を取得するまで、不完全な型へのポインターを void* として処理し、詳細が判明したら、適切な型へのポインターとして扱うことができます。不完全型ポインターは、void* とはまったく似ていません。つまり、不完全型ではできないことを void* で行うことができます (たとえば、p1 が構造体 s1 へのポインターであり、p2 が構造体へのポインターである場合)。 s2、p1 を p2 に代入することはできません) が、void* に対して実行できなかった不完全な型へのポインタでは何もできません。基本的に、コンパイラの観点からは、不完全な型へのポインターは、ポインター サイズのバイトの塊です。他の同様のポインターサイズのバイトのブロブとの間でコピーできますが、それだけです。

于 2011-11-21T00:40:49.320 に答える