奇怪的编译器警告C:警告:在参数列表中声明’struct’

我刚发现C中的一个怪癖,我觉得很困惑。 在C语言中,可以在声明结构之前使用指向结构的指针。 这是一个非常有用的function,因为当你只处理指针时,声明是无关紧要的。 我刚刚发现了一个角落的情况,但是(令人惊讶的是)并非如此,我无法解释原因。 对我来说,这似乎是语言设计中的一个错误。

拿这个代码:

#include  #include  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  #include  struct lol* wut; typedef void (*a)(struct lol* etc); void a2(struct lol* etc) { } int main(void) { return 0; } 

无法解释的问题现在因无法解释的原因而消失。 为什么?

请注意,这个问题是关于语言C的行为(或者可能是gcc和clang的编译器行为),而不是我粘贴的具体示例。

编辑:

除非你还解释为什么C会在函数参数列表中第一次使用结构指针但在任何其他上下文中允许它,否则我不会接受“声明的顺序很重要”作为答案。 为什么这可能是一个问题?

要理解编译器抱怨的原因,你需要知道关于C“struct”的两件事:

  • 一旦命名它们就会被创建(作为声明但尚未定义的类型),因此第一次出现的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 */ 

这里命名为ab变量在括号内声明,但这些声明需要避开,以便下一个函数声明不会抱怨它们被重新声明。

struct tag-names 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 ,你struct sttag完成struct sttag原型中的gronk


回答问题编辑:肯定可以不同地定义struct,union和enum标签的动作,使它们“冒出”原型到它们的封闭范围。 这将使问题消失。 但它没有这样定义。 既然是ANSI C89委员会发明了(或者真的,从那时起来的C ++)原型,你就可以把它归咎于它们。 🙂

这是因为,在第一个示例中,结构先前未定义,因此编译器会尝试将此结构的第一个引用视为定义。

通常,C是一种语言,其声明的顺序很重要。 您使用的所有内容都应该以某种身份提前正确声明,以便编译器可以在其他上下文中引用时对其进行推理。

这不是语言设计中的错误或错误。 相反,我认为这是为了简化第一个C编译器的实现而做出的选择。 前向声明允许编译器一次性串行转换源代码(只要知道一些信息,如大小和偏移量)。 如果不是这种情况,那么只要编译器遇到无法识别的标识符,编译器就能够在程序中来回运行,这要求其代码发送循环要复杂得多。

编译器警告你有关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传递给函数),并且几乎肯定不是程序员想要的,所以编译器警告。