14

私はいくつかのコードのバグを修正していましたが、コンパイラは関数dynscat()が宣言されていないことを(合法的に)警告しました-受け入れ可能なコーディング標準についての誰かの考え-それで私は関数が定義されている場所(十分に簡単)とそれを宣言したヘッダー(なし)を追跡しました; Grrr!)。externしかし、私は構造体定義の詳細が次の宣言に必要であることに気付くと期待していましたqqparse_val

extern struct t_dynstr qqparse_val;

extern void dynscat(struct t_dynstr *s, char *p);
extern void qqcat(char *s);

void qqcat(char *s)
{
    dynscat(&qqparse_val, s);
    if (*s == ',')
        dynscat(&qqparse_val, "$");
}

元のコードのqqcat()関数は静的でした。extern宣言は、このコードスニペットに対するコンパイラの警告を鎮めます。dynscat()関数宣言が完全に欠落していました。繰り返しますが、それを追加すると警告が鎮められます。

示されているコードフラグメントを使用すると、変数のアドレスのみが使用されていることは明らかです。したがって、構造の詳細が不明であっても問題がないことは、あるレベルでは理にかなっています。変数の場合extern struct t_dynstr *p_parseval;、この質問は表示されません。それは100%期待されます。コードが構造体の内部にアクセスする必要がある場合は、構造体の定義が必要になります。しかし、変数が(構造体へのポインターではなく)構造体であると宣言した場合、コンパイラーは構造体のサイズを知りたいと常に思っていましたが、明らかにそうではありません。

私はGCCに文句を言うように仕向けようとしましたが、GCC4.7.1でさえもそうではありません。

gcc-4.7.1 -c -Wall -Wextra -std=c89 -pedantic surprise.c

このコードは、AIX、HP-UX、Solaris、Linuxで10年間コンパイルされているため、GCC固有ではありません。

質問

これはC標準で許可されていますか(主にC99またはC11ですが、C89でも許可されます)?どのセクション?それとも、移植されたすべてのマシンで動作するが、標準によって正式に認可されていない奇妙なケースにぶつかっただけですか?

4

6 に答える 6

15

あなたが持っているのは不完全なタイプです(ISO / IEC 9899:1999と2011 —これらの参照はすべて両方で同じです—§6.2.5¶22):

未知のコンテンツの構造体または共用体タイプ(§6.7.2.3で説明)は不完全なタイプです。

不完全な型は依然として左辺値である可能性があります。

§6.3.2.1¶1(左辺値、配列、および関数指定子)

左辺値は、オブジェクト型またはvoid以外の不完全な型を持つ式です。..。

&したがって、結果として、左辺値を持つ他の単項演算とまったく同じになります。

于 2012-08-30T15:50:56.417 に答える
8

型が不完全なオブジェクトのアドレスを取得する場合のように見えます。

不完全な型へのポインターの使用は完全に正気であり、voidへのポインターを使用するたびにそれを行います(しかし、誰もあなたに言ったことはありません:-)

別のケースは、あなたが次のようなものを宣言した場合です

extern char a[];

の要素に割り当てることができるのは当然ですよaね?それでも、これは不完全な型であり、コンパイラは、そのような識別子をのオペランドにするとすぐに通知しますsizeof

于 2012-08-30T15:33:03.870 に答える
5

あなたのライン

extern struct t_dynstr qqparse_val;

オブジェクトの外部宣言であり、定義ではありません。外部オブジェクトとして、それは「リンケージを持っている」、すなわち外部リンケージを持っています。

標準は言う:

オブジェクトの識別子がリンケージなしで宣言されている場合、オブジェクトの型はその宣言子の終わりまでに完了する必要があります...

これは、リンケージがある場合、タイプが不完全である可能性があることを意味します。&qqparse_valですから、後で問題はありません。sizeof(qqparse_val)オブジェクトタイプが不完全なため、実行できない可能性があります。

于 2012-08-30T16:06:59.387 に答える
4

何かを「参照」するには、宣言が必要です。何かを「使う」には定義が必要です。宣言は、「inta[];」のようにいくつかの限定された定義を提供する場合があります。私を困惑させるのは:

int f(struct _s {int a; int b;} *sp)
{
    sp->a = 1;
}

gccは、「struct_s」がパラメータリスト内で宣言されていることを警告します。そして、「そのスコープは、この定義または宣言のみです...」と述べています。ただし、パラメータリストにない「sp->a」ではエラーになりません。'C'パーサーを作成するとき、定義スコープがどこで終了するかを決定する必要がありました。

于 2012-10-24T22:52:53.597 に答える
3

最初の行に焦点を当てる:

extern struct t_dynstr qqparse_val;

これは、型と変数を作成する別々のステップに分割でき、次のような同等の行のペアになります。

struct t_dynstr; /* declaration of an incomplete (opaque) struct type */
extern struct t_dynstr qqparse_val; /* declaration of an object of that type */

2行目は元の行と同じように見えますが、1行目のためにすでに存在するタイプを参照しています。

最初の行は、不透明な構造体が実行される方法であるため、機能します。

2行目は、extern宣言を行うために完全な型を必要としないために機能します。

型宣言と変数宣言の組み合わせは一般的に機能するため、組み合わせ(2行目は1行目なしで機能します)が機能します。これらはすべて同じ原則を使用しています。

struct { int x,y; } loc; /* define a nameless type and a variable of that type */
struct point { int x,y; } location; /* same but the type has a name */
union u { int i; float f; } u1, u2; /* one type named "union u", two variables */

型自体を「extern」にしようとしているように、型宣言がすぐに続くのは少しおかしいように見えますが、externこれは意味がありません。しかし、それはそれが意味することではありません。地理的に離れているにもかかわらず、に適用externされます。qqparse_val

于 2012-08-30T22:43:04.123 に答える
2

これが標準(C11)に対する私の考えです。

セクション6.5.3.2:アドレス演算子と間接演算子

制約

パラグラフ1:単項&演算子のオペランドは、関数指定子、[]または単項*演算子の結果、またはビットフィールドではなくレジスタストレージで宣言されていないオブジェクトを指定する左辺値のいずれかでなければなりません。 -クラス指定子。

パラグラフ2:単項*演算子のオペランドは、ポインター型でなければなりません。

ここでは、オブジェクトがオブジェクトである(ビットフィールドやレジスタではない)ことを除いて、オブジェクトに要件を指定していません。

一方、sizeofを見てみましょう。

6.5.3.4sizeofおよび_Alignof演算子

制約

パラグラフ1: sizeof演算子は、関数型または不完全型を持つ式、そのような型の括弧で囲まれた名前、またはビットフィールドメンバーを指定する式に適用してはなりません。_Alignof演算子は、関数型または不完全型には適用されません。

ここで、標準では、オブジェクトが不完全なタイプでないことを明示的に要求しています。

したがって、これは明示的に拒否されていないことが許可されている場合だと思います。

于 2012-08-30T15:35:34.817 に答える