我刚发现C中的一个怪癖,我觉得很困惑。在C语言中,可以在声明结构之前使用指向结构的指针。这是一个非常有用的功能,因为当您处理指向它的指针时,声明是无关紧要的。我刚刚找到一个角落的情况,但是(令人惊讶的是)这不是真的,我无法解释原因。对我来说,这似乎是语言设计中的一个错误。
拿这段代码:
#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会在函数参数列表中第一次使用结构指针但在任何其他上下文中允许它,否则我不会接受“声明的顺序很重要”作为答案。为什么这可能是一个问题?
答案 0 :(得分:28)
要理解编译器抱怨的原因,你需要知道关于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 */
此处名为a
和b
的变量在括号内声明,但这些声明需要避开,以便 next 函数声明不会抱怨关于他们被重新宣布。
struct
标记名称也是如此:
void gronk(struct sttag *p);
struct sttag
声明了一个结构,然后声明被扫除,就像a
和b
一样。但这会产生一个大问题:标签已经消失,现在你无法再次命名结构类型!如果你写:
struct sttag { int field1; char *field2; };
定义了一个新的,不同的struct sttag
,就像:
void somefunc(int x) { int y; ... }
int x, y;
在文件级范围定义了一个新的,不同的x
和y
,与somefunc
中的不同。
幸运的是,如果您在之前声明(或甚至定义)struct ,那么您将编写函数声明,原型级声明“返回”外部范围声明:
struct sttag;
void gronk(struct sttag *p);
现在两个struct sttag
都是“相同的”struct sttag
,所以当您稍后完成struct sttag
时,您也会在原型gronk
内完成。{{1}}。
重新编辑问题:肯定可以不同地定义struct,union和enum标签的动作,使它们“原型化”到它们的封闭范围。这将使问题消失。但它没有这样定义。既然是ANSI C89委员会发明了(或者真的,从那时起来的C ++)原型,你就可以把它归咎于它们。 : - )
答案 1 :(得分:5)
这是因为,在第一个示例中,结构先前未定义,因此编译器会尝试将该结构的第一个引用视为定义。
通常,C语言是声明顺序重要的语言。您使用的所有内容都应该以某种身份提前正确声明,以便编译器可以在其他上下文中引用它时对其进行推理。
这不是语言设计中的错误或错误。相反,我认为这是为了简化第一个C编译器的实现而做出的选择。前向声明允许编译器一次性串行转换源代码(只要知道一些信息,如大小和偏移量)。如果不是这种情况,只要遇到无法识别的标识符,编译器就能够在程序中来回转换,要求其代码发出循环要复杂得多。
答案 2 :(得分:3)
编译器警告您struct lol
的转发声明。 C允许您这样做:
struct lol; /* forward declaration, the size and members of
struct lol are unknown */
这在定义自引用结构时最常用,但在定义从未在标头中定义的私有结构时也很有用。由于后一种用例,允许声明接收或返回指向不完整结构的指针的函数:
void foo(struct lol *x);
但是,只是在函数声明中使用未声明的结构,就像你所做的那样,将被解释为{{1>}的本地不完整声明,其范围受限于函数。这个解释是由C标准规定的,但它没有用(没有办法构造struct lol
传递给函数),几乎肯定不是程序员想要的,所以编译器警告。
答案 3 :(得分:-1)
寻找错别字并仔细检查您的行号!在编译之前,我会合并所有源文件,因此来自我正在使用的源代码的行号毫无意义。我必须花费额外的时间来打开浓缩的负载并进行检查。所以通常我不会,只是假设我从控制台输出消息中知道我在看哪一行。
示例: 海湾合作委员会说:
EXAMPLE.C11:27:1:错误:内部声明'struct THIS_STRUCT_IS_OK' 参数列表[-Werror]){
#include <stdio.h> //:for: printf(...)
struct THIS_STRUCT_IS_OKAY{
int whatever;
};
int LookingAtThisFunction(
struct THIS_STRUCT_IS_OKAY* arg
){
//: (Because you are not checking line numbers, you )
//: (assume you are looking here. But you are not. )
//: (Maybe you are concatenating all of your source )
//: (files together before compiling and line numbers )
//: (don't correspond to the original source and you )
//: (didn't examine your concatted source code payload?)
return( arg -> whatever );
}
//:You are not looking here because this is later in the
//:file, so the compiler would be complaining about the
//:FIRST usage of the struct, not the second one, you assume.
//:And you would be correct, if there wasn't a typo.
void WhereYouAreNotLooking(
struct THIS_STRUCT_IS_OK* arg
){
LookingAtThisFunction( arg );
}
int main( void ){
}
摘要:如果您知道错误消息的含义。你向上帝发誓 编译器已损坏,因为您已经检查过... 1.寻找错别字。 2.确保您确实在寻找正确的行号。
我知道这有点愚蠢。但这一直在困扰着我 半小时。因此,希望它对已经看过 显而易见的解决方案。