1. ホーム
  2. c

[解決済み] コンパイラの奇妙な警告 C: 警告: 'struct' はパラメータリストの内部で宣言されています。

2022-02-16 14:03:10

質問

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のコンパイラの動作の可能性)についてのもので、私が貼り付けた特定の例ではないことに注意してください。

EDIT

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

解決方法は?

コンパイラが文句を言う理由を理解するには、C の "struct"s について 2 つのことを知る必要があります。

  • は、名前を付けるとすぐに(宣言されているがまだ定義されていない型として)作成されるため、最初に登場する struct lol は宣言を作成します。
  • 通常の変数と同じように宣言のスコープに従います。

( struct lol { を宣言してから構造の定義を始めると、それは struct lol; または struct lol * または、"declare" のステップの後で停止するオープンブレースを持たない他の何か)。

宣言されているがまだ定義されていない構造体型は、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 */

という変数があります。 ab は括弧の中で宣言されていますが、これらの宣言は邪魔にならないようにする必要があります。 関数宣言が再宣言されても文句を言われないようにするためです。

と同じことが起こります。 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;

は、新しい別の xy とは異なり、ファイルレベルのスコープで somefunc .

幸いなことに、構造体を宣言(定義)すれば 前に 関数宣言を書くと、プロトタイプレベルの宣言はアウタースコープ宣言を "参照"します。

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

これで、両方の struct sttag は "同じ" です。 struct sttag を完成させると struct sttag のプロトタイプの中にあるものを完成させることになります。 gronk もあります。


Re question edit: 確かに、struct, union, enumタグの動作を別の方法で定義して、プロトタイプからそのスコープにバブルアウトさせることは可能だったでしょう。 そうすれば、この問題は解決します。 しかし、そのようには定義されていません。 プロトタイプを発明したのはANSI C89委員会ですから(というか、当時のC++から盗んだのですが)、彼らのせいにしてください :-)