22

我刚刚在 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 会警告第一次在函数参数列表中使用结构指针,但允许在任何其他上下文中使用它。为什么这可能是个问题?

4

4 回答 4

37

要了解编译器为何抱怨,您需要了解关于 C“结构”的两件事:

  • 只要您命名它们,它们就会被创建(作为已声明但尚未定义的类型),因此第一次出现的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标记名称也会发生同样的事情:

void gronk(struct sttag *p);

声明一个结构,然后struct sttag声明被扫除,就像aand一样b。但这产生了一个大问题:标签消失了,现在你不能再命名结构类型了!如果你写:

struct sttag { int field1; char *field2; };

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

void somefunc(int x) { int y; ... }
int x, y;

在文件级范围内定义了一个新的和不同x的,与.ysomefunc

幸运的是,如果在编写函数声明之前声明(甚至定义)结构,原型级声明“引用”外部范围声明:

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

现在两个struct sttags 都是“相同的” struct sttag,所以当你struct sttag稍后完成时,你也在完成原型中的一个gronk


重新编辑问题:当然可以以不同的方式定义结构、联合和枚举标签的操作,使它们从原型“冒泡”到它们的封闭范围。这将使问题消失。但它不是这样定义的。由于是 ANSI C89 委员会发明了(或者实际上是从当时的 C++ 中窃取)原型,因此您可以将其归咎于他们。:-)

于 2013-05-30T09:02:57.887 回答
6

The compiler is warning you about a forward declaration of struct lol. C allows you to do this:

struct lol;     /* forward declaration, the size and members of
                   struct lol are unknown */

This is most used when defining self-referencing structs, but it is also useful when defining private structs that are never defined in the header. Because of this latter use case, it is allowed to declare functions that receive or return pointers to incomplete structs:

void foo(struct lol *x);

However, just using an undeclared struct in a function declaration, as you did, will be interpreted as a local incomplete declaration of struct lol whose scope is constrainted to the function. This interpretation is mandated by the C standard, but it is not useful (there is no way to construct the struct lol to pass to the function) and is almost certainly not what the programmer intended, so the compiler warns.

于 2013-05-30T08:58:55.277 回答
5

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

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

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

于 2013-05-30T08:47:09.333 回答
-1

注意错别字并仔细检查您的行号!我在编译之前连接了我所有的源文件,所以我正在使用的源代码中的行号是没有意义的。我必须花额外的时间来打开连接的有效载荷并检查它。所以通常我不知道,我只是假设我知道我从控制台输出消息中看到的是哪一行。

示例:GCC 说:

示例.C11:27:1:错误:在参数列表 [-Werror] 中声明了“struct THIS_STRUCT_IS_OK”){

#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. 确保您查看的是正确的行号。

我知道这有点愚蠢。但它已经让我头疼了半个小时。所以希望它可以帮助那些已经看过明显解决方案的人。

于 2020-04-25T22:43:39.863 回答