2013-05-30 60 views
15

我刚刚在C中发现一个怪癖,我觉得很混乱。在C语言中,可以在声明之前使用指向结构体的指针。这是一个非常有用的特性,因为当你只是处理一个指针时,声明是无关紧要的。然而,我发现了一个角落的情况,但这并不是真的,我真的不能解释为什么。对我来说,在语言设计中看起来像是一个错误。奇怪的编译器警告C:警告:在参数列表中声明'struct'

把这个代码:

#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和叮当),而不是我粘贴的具体示例。

编辑:

我不会接受“声明的顺序是非常重要的”作为回答,除非你也解释了为什么C将警告在函数参数列表使用结构指针的第一次,但允许它在任何其他情况下。为什么这可能是一个问题?

+2

你应该仍然告诉它的结构预先存在使用'struct lol;' – Dave

+0

你应该首先告诉编译器存在这样的类型结构lol,然后你可以使用struct lo或一个指向结构的指针在新函数的声明中 – hetepeperfan

+2

另外它看起来像编译器向您解释了确切的问题:范围。阅读警告! – Dave

回答

27

要理解为什么编译器会抱怨,你需要了解C“结构” S两件事情:

  • 创建它们(作为一个声明,但尚未定义,类型),只要你的名字他们,所以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参数:

命名 ab这里的括号内声明
int imin2(int a, int b); /* returns a or b, whichever is smaller */ 
int isum2(int a, int b); /* returns a + b */ 

变量,但这些声明需要走出的方式,以使next函数声明不会抱怨它们被重新声明。

同样的事情发生与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 s是“相同的”struct sttag,所以当您稍后完成struct sttag时,您也正在完成gronk原型中的一个。


重的问题编辑:它肯定会遭到可以定义不同的结构,联合和枚举标记的作用,使它们的原型“泡出”自己的封闭范围。这会让问题消失。但它没有这样定义。由于它是ANSI C89委员会发明的(或者真的从当时的C++中偷取)原型,所以你可以将它归咎于它们。 :-)

+0

谢谢。我认为这是最有洞察力的答案。 –

+2

当他们发明这些(原型范围和所有这些)时,我就在身边。当人们开始使用原型时,人们在原型中发现了结构声明的问题 - 通常会从执行类型检查的编译器中获取不可理解的错误消息,但是只是说struct'foo *与struct foo *不兼容而不解释* why *。那很有趣。 :-) – torek

5

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

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

这不是语言设计中的错误或错误。相反,我相信这是一种选择,可以简化首批C编译器的实现。前向声明允许编译器一次转换源代码(只要知道诸如尺寸和偏移的一些信息)。如果情况并非如此,编译器只要遇到无法识别的标识符就能够在程序中来回切换,因此要求其编码发射循环要复杂得多。

+1

从技术上讲,你仍然可以在“一次通过”中做到这一切,你只需要在内存中保存更多的数据。当然,编译器中的简单和复杂的工作方式是相似的。 :-) – torek

+0

@torek真。我会看看我能否更准确地说出来。 –

3

编译器警告你一个向前声明的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),并且几乎肯定不是程序员的意图,所以编译器警告。