22

私はちょうど私が本当に紛らわしいと思う C の癖を見つけました。C では、宣言される前に構造体へのポインターを使用することができます。これは非常に便利な機能であり、宣言へのポインターを扱っているだけでは意味がないため、意味があります。ただし、これが (驚くほど) 真実ではない 1 つのまれなケースを見つけましたが、その理由を実際に説明することはできません。私には、言語設計の間違いのように見えます。

次のコードを使用します。

#include <stdio.h>

#include <stdlib.h>


typedef void (*a)(struct lol* etc);

void a2(struct lol* etc) {

}

int main(void) {
        return 0;
}

与えます:

foo.c:6:26: warning: ‘struct lol’ declared inside parameter list [enabled by default]
foo.c:6:26: warning: its scope is only this definition or declaration, which is probably not what you want [enabled by default]
foo.c:8:16: warning: ‘struct lol’ declared inside parameter list [enabled by default]

この問題を取り除くには、次のようにするだけです。

#include <stdio.h>

#include <stdlib.h>

struct lol* wut;

typedef void (*a)(struct lol* etc);

void a2(struct lol* etc) {

}

int main(void) {
        return 0;
}

説明のつかない問題は、説明のつかない理由でなくなりました。なんで?

この質問は言語 C の動作 (または gcc と clang のコンパイラの動作) に関するものであり、貼り付けた特定の例ではないことに注意してください。

編集:

関数の引数リストで構造体ポインターを初めて使用することについてCが警告するが、他のコンテキストではそれを許可する理由も説明しない限り、「宣言の順序が重要です」という答えは受け入れません。なぜそれが問題になるのでしょうか?

4

4 に答える 4

37

コンパイラが文句を言う理由を理解するには、C の「構造体」について 2 つのことを知っておく必要があります。

  • それらは、名前を付けるとすぐに(宣言されているがまだ定義されていない型として)作成されるため、最初の の出現でstruct lol宣言が作成されます
  • 通常の変数と同じ「宣言スコープ」規則に従います

struct lol {宣言してから構造の定義を開始します。それは、「宣言」ステップの後に停止する開き括弧を持たない、または何か他のものです。struct lol;struct lol *

宣言されているがまだ定義されていない構造体型は、C で「不完全な型」と呼ばれるもののインスタンスです。ポインターをたどろうとしない限り、不完全な型へのポインターを使用できます。

struct lol *global_p;
void f(void) {
    use0(global_p);     /* this is OK */
    use1(*global_p);       /* this is an error */
    use2(global_p->field); /* and so is this */
}

つまり、「ポインターをたどる」ためには型を完成させる必要があります。

ただし、いずれにしても、通常のintパラメーターを使用した関数宣言を検討してください。

int imin2(int a, int b); /* returns a or b, whichever is smaller */
int isum2(int a, int b); /* returns a + b */

aおよびhereという名前の変数は括弧内で宣言されますが、次の関数宣言がそれらの再宣言について文句を言わないbように、これらの宣言は邪魔にならないようにする必要があります。

structタグ名でも同じことが起こります:

void gronk(struct sttag *p);

は構造体を宣言し、宣言は と の場合とstruct sttag同様に一掃されます。しかし、これは大きな問題を引き起こします: タグがなくなり、構造型に名前を付けることは二度とできなくなります! あなたが書く場合:ab

struct sttag { int field1; char *field2; };

struct sttag次のように、新しくて異なる を定義します。

void somefunc(int x) { int y; ... }
int x, y;

は、のスコープとは異なる、ファイル レベルのスコープで新しく、異なるxを定義します。ysomefunc

幸いなことに、関数宣言を記述する前に構造体を宣言 (または定義)すると、プロトタイプ レベルの宣言は外側のスコープ宣言を "参照" します。

struct sttag;
void gronk(struct sttag *p);

これで、両方struct sttagの が「同じ」struct sttagになったので、struct sttag後で完了すると、プロトタイプ内の 1 つも完了することになりgronkます。


質問の編集について: struct、union、および enum タグのアクションを異なる方法で定義して、プロトタイプからそれらを囲むスコープに「バブルアウト」させることは確かに可能でした。それは問題を解決するでしょう。しかし、そのように定義されていませんでした。プロトタイプを発明した (実際には当時の C++ から盗んだ) のは ANSI C89 委員会だったので、そのせいにすることができます。:-)

于 2013-05-30T09:02:57.887 に答える
6

コンパイラは、 の前方宣言について警告していますstruct lol。C では、これを行うことができます。

struct lol;     /* forward declaration, the size and members of
                   struct lol are unknown */

これは、自己参照構造体を定義するときに最もよく使用されますが、ヘッダーで定義されていないプライベート構造体を定義するときにも役立ちます。この後者の使用例のため、不完全な構造体へのポインターを受け取ったり返したりする関数を宣言することが許可されています。

void foo(struct lol *x);

ただし、関数宣言で宣言されていない構造体を使用しただけでは、スコープが関数に制約されているローカルの不完全な宣言として解釈されます。struct lolこの解釈は C 標準で義務付けられていますが、有用ではなく (struct lol関数に渡す を作成する方法がありません)、ほぼ確実にプログラマーが意図したものではないため、コンパイラーは警告します。

于 2013-05-30T08:58:55.277 に答える
5

これは、最初の例では構造体が以前に未定義であったため、コンパイラはその構造体へのこの最初の参照を定義として処理しようとするためです。

一般に、C は宣言の順序が重要な言語です。使用するものはすべて、事前にある程度適切に宣言する必要があります。これにより、他のコンテキストで参照されたときにコンパイラが推論できるようになります。

これは言語の設計上のバグや間違いではありません。むしろ、最初の C コンパイラの実装を簡素化するために行われたと私が信じている選択です。前方宣言により、コンパイラはソース コードを 1 回のパスでシリアルに変換できます (サイズやオフセットなどの情報がわかっている場合)。そうでない場合、コンパイラは、認識されない識別子に遭遇するたびにプログラム内を行ったり来たりすることができ、コード発行ループがはるかに複雑になる必要があります。

于 2013-05-30T08:47:09.333 に答える