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

Han*_*olm 19 c

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

拿这个代码:

#include <stdio.h>

#include <stdlib.h>


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

void a2(struct lol* etc) {

}

int main(void) {
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

得到:

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]
Run Code Online (Sandbox Code Playgroud)

要解决此问题,我们可以简单地执行此操作:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:

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

tor*_*rek 30

要理解编译器抱怨的原因,你需要知道关于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 */
}
Run Code Online (Sandbox Code Playgroud)

换句话说,你必须完成类型以"跟随指针".

但无论如何,请考虑使用普通int参数的函数声明:

int imin2(int a, int b); /* returns a or b, whichever is smaller */
int isum2(int a, int b); /* returns a + b */
Run Code Online (Sandbox Code Playgroud)

名称a和变量的变量b在括号内声明,但这些声明需要避开,以便下一个函数声明不会抱怨它们被重新声明.

struct标签名称也会发生同样的事情:

void gronk(struct sttag *p);
Run Code Online (Sandbox Code Playgroud)

struct sttag声明的结构,然后将声明一扫,就像药粥ab.但这会产生一个大问题:标签已经消失,现在你无法再次命名结构类型!如果你写:

struct sttag { int field1; char *field2; };
Run Code Online (Sandbox Code Playgroud)

定义一个新的和不同的struct sttag,就像:

void somefunc(int x) { int y; ... }
int x, y;
Run Code Online (Sandbox Code Playgroud)

定义了一个新的和不同xy在文件级范围,从情况不同somefunc.

幸运的是,如果编写函数声明之前声明(或甚至定义)结构,则原型级声明"返回"到外部作用域声明:

struct sttag;
void gronk(struct sttag *p);
Run Code Online (Sandbox Code Playgroud)

现在两者struct sttag都是"相同的" struct sttag,所以当你struct sttag以后完成时,你gronk也要完成原型中的那个.


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

  • 当他们发明这些(原型范围,以及所有这些)时,我就在身边.当人们开始使用原型时,人们在原型中发现了结构声明的问题 - 经常从进行类型检查的编译器获得难以理解的错误消息,但只是说`struct foo*与struct foo*不兼容,而没有解释*为什么*.那太有趣了.:-) (2认同)

The*_*kis 5

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

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

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


use*_*342 5

编译器警告你一个向前声明struct lol。C允许您执行以下操作:

struct lol;     /* forward declaration, the size and members of
                   struct lol are unknown */
Run Code Online (Sandbox Code Playgroud)

这在定义自引用结构时最常用,但是在定义从未在头文件中定义的私有结构时也很有用。由于存在后者,因此可以声明接收或返回指向不完整结构的指针的函数:

void foo(struct lol *x);
Run Code Online (Sandbox Code Playgroud)

但是,像您一样,仅在函数声明中使用未声明的结构,将被解释为局部不完整声明,struct lol其范围受限于该函数。这种解释是由C标准强制执行的,但是它没有用(无法构造struct lol传递给函数的方法),并且几乎肯定不是程序员想要的,因此编译器警告。