简体   繁体   English

为什么 GCC 只有在使用结构名称而不是 typedef 时才会抛出错误和不必要的警告?

[英]Why is GCC make throwing errors and uneccesary warnings only when using the struct name instead of typedef?

I have a program consisting of two source files (farm.c, init.c) and two corresponding header files (farm.h, init.h) Both source files contain header guards and each other, because they both require functions/variables from each other.我有一个由两个源文件(farm.c、init.c)和两个相应的头文件(farm.h、init.h)组成的程序,两个源文件都包含标题保护,因为它们都需要来自彼此。

init.h:初始化.h:

#ifndef INIT_H
#define INIT_H

#include<stdio.h>
#include<stdlib.h>
#include"farm.h"

#define PIG_SOUND "oink"
#define CALF_SOUND "baa"

enum types {PIG, CALF};

typedef struct resources {
    size_t pork;
    size_t veal;
    size_t lamb;
    size_t milk;
    size_t eggs;
} resources;

typedef struct animal {
    size_t legs;
    char* sound;
    int efficiency;
    void (*exclaim)(struct animal*);
    void (*work)(struct animal*, struct resources*);
} animal;

/* I have tried various ways of declaring structs in addition to
   the typedef such as this */

//animal stock;
//animal farm;

void make_pig(struct animal* a, int perf);
void make_calf(struct animal* a, int perf);

#endif

farm.h:农场.h:

#ifndef FARM_H
#define FARM_H

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>
#include"init.h"

/* GCC does not recognise the typedef or struct identifier 
   until these forward declarations have been made in 
   addition to the included init.h header file */

//typedef struct animal animal;
//typedef struct resources resources;

void exclaim(animal* b);
void work(struct animal* b, struct resources* s);

#endif

init.c:初始化.c:

#include"init.h"

void make_pig(animal* a, int perf) {
    a->legs = 4;
    a->sound = PIG_SOUND;
    a->efficiency = perf;
    a->exclaim = exclaim;
    a->work = work;
}

void make_calf(animal* a, int perf) {
    a->legs = 4;
    a->sound = CALF_SOUND;
    a->efficiency = perf;
    a->exclaim = exclaim;
    a->work = work;
}

farm.c:农场.c:

#include"farm.h"

int main() {
    return 0;
}

void exclaim(animal* a) {
    for (int i = 0; i < 3; i++) {
        printf("%s ", a->sound);
    }
    printf("\n");
}

void work(animal* a, struct resources* r) {
    if (!strcmp(a->sound, PIG_SOUND)) {
        r->pork += a->efficiency;
    }

    if (!strcmp(a->sound, CALF_SOUND)) {
        r->veal += a->efficiency;
    }
}

Using both types of names (ie, struct ani and animal ) normally works perfectly fine on my Linux system with the C99 standard.使用这两种类型的名称(即struct anianimal )通常在我的具有 C99 标准的 Linux 系统上工作得很好。 However when I use struct ani instead of animal here, I get the below warnings for every instance of the type usage for struct ani and struct resources .但是,当我在这里使用struct ani而不是animal时,对于struct anistruct resources的类型使用的每个实例,我都会收到以下警告。

lib/farm.h:10:21: warning: ‘struct ani’ declared inside parameter list will not be visible outside of this definition or declaration
   10 | void exclaim(struct ani* a);
      |                     ^~~
lib/farm.h:11:33: warning: ‘struct resources’ declared inside parameter list will not be visible outside of this definition or declaration
   11 | void work(struct ani* a, struct resources* r);

And 10 warnings in total for every usage of function pointers of the form:每次使用以下形式的函数指针总共有 10 个警告:

src/init.c:17:16: warning: assignment to ‘void (*)(struct ani *)’ from incompatible pointer type ‘void (*)(struct ani *)’ [-Wincompatible-pointer-types]
   17 |     a->exclaim = exclaim;
      |                ^
src/init.c:18:13: warning: assignment to ‘void (*)(struct ani *, struct resources *)’ from incompatible pointer type ‘void (*)(struct ani *, struct resources *)’ [-Wincompatible-pointer-types]
   18 |     a->work = work;

Can someone please explain why such behaviour occurs and how I can avoid problems?有人可以解释为什么会发生这种行为以及如何避免问题吗? It typically takes me an unfeasible amount of time to solve these errors and I still don't truly understand my mistake in the first place.解决这些错误通常会花费我大量的时间,而且我仍然没有真正理解我的错误。

You've hit one of the odd corner cases of C scoping rules.您遇到了 C 范围规则的奇怪极端情况之一。

Informally, a tagged struct (or union , but I'm not going to repeat that over and over) springs into existence when it is named if no declaration for it is visible.非正式地,一个标记的struct (或union ,但我不会一遍又一遍地重复)当它被命名时,如果没有可见的声明,它就会出现。 "Springs into existence" means that it is considered declared in the current scope . “开始存在”意味着它被认为是在当前范围内声明的。 Also, if a tagged struct was previously named in a scope and you then declare a struct with the same tag, the two structs are considered the same.此外,如果之前在作用域中命名了标记结构,然后您声明了具有相同标记的结构,则这两个结构被认为是相同的。 Until a declaration for the struct is completed, the struct is considered an incomplete type, but a pointer to an incomplete type is a complete type, so you can declare a pointer to a tagged struct before you actually complete the definition of the struct.在结构的声明完成之前,结构被认为是不完整类型,但指向不完整类型的指针是完整类型,因此您可以在实际完成结构定义之前声明指向标记结构的指针。

Most of the time, that just works with minimal thought.大多数时候,这只是用最少的想法来工作。 But function prototypes are a bit special, because a function prototype is a scope, all by itself.但是函数原型有点特殊,因为函数原型本身就是一个作用域。 (The scope lasts only until the end of the function declaration.) (范围只持续到函数声明的结尾。)

When you put that together, you end up with the issue you're facing.当你把它放在一起时,你最终会遇到你面临的问题。 You cannot use a pointer to a tagged struct in a function prototype unless the tagged struct was known before the function prototype appears.您不能在函数原型中使用指向标记结构的指针,除非标记结构在函数原型出现之前已知。 If it had been mentioned before, even in an outer scope, the tag is visible and therefore will be considered to be the same struct (even if it is still incomplete).如果之前提到过,即使在外部范围内,标签也是可见的,因此将被认为是同一个结构(即使它仍然不完整)。 But if the tag was not previously visible, a new struct type will be created within the prototype scope , which will not be visible after that scope ends (which is almost immediately).但是如果标签以前不可见,则会在原型范围内创建一个新的结构类型,在该范围结束后(几乎立即)将不可见。

Concretely, if you write the following:具体来说,如果您编写以下内容:

extern struct animal * barnyard;
void exclaim(struct animal*);

then the two uses of struct animal refer to the same struct type, which will presumably be completed later (or in another translation unit).那么struct animal的两种用法是指同一个 struct 类型,想必以后会完成(或者在另一个翻译单元中)。

But without the extern struct animal * barnyard;但是没有extern struct animal * barnyard; declaration, the struct animal named in the exclaim prototype is not previously visible, so thus is declared only in the prototype scope, so it is not the same type as some subsequent use of struct animal .声明中,在exclaim原型中命名的struct animal以前是可见的,因此仅在原型范围内声明,因此它与struct animal的某些后续使用不同。 Had you put the declarations in the opposite order, you would have seen a compiler warning (assuming you'd asked for compile warnings):如果您以相反的顺序放置声明,您会看到编译器警告(假设您要求编译警告):

void exclaim(struct animal*);
extern struct animal * barnyard;

( On godbolt, bless it's heart ) 在上帝螺栓上,祝福它的心

A typedef declaration performs the same way as the extern declaration above; typedef声明的执行方式与上面的extern声明相同; the fact that it is a type alias is not relevant.它是类型别名的事实是不相关的。 What's important is that the use of struct animal in the declaration causes the type to spring into existence, and you can subsequently use it freely in a prototype.重要的是在声明中使用struct animal会导致该类型突然出现,您随后可以在原型中自由使用它。 That's the same reason that the function pointers inside your struct definitions are OK;这与结构定义中的函数指针正常的原因相同; the start of the struct definition was sufficient to cause the tag to be declared, so the prototype sees it.结构定义的开始足以导致标签被声明,因此原型可以看到它。

In fact, any syntactic construction which contains a tagged struct ( struct whatever ) will serve the same purpose, because what matters is the effect of mentioning a tagged struct with no visible declaration.事实上,任何包含标记结构 ( struct whatever ) 的句法构造都将用于相同的目的,因为重要的是提及没有可见声明的标记结构的效果。 Above, I used extern global declarations as examples because they are lines which might appear in a header, but there are many other possibilities, including even the declaration of a function which returns a pointer to a struct (because the return type of a function declaration is not in the prototype scope).上面,我使用extern全局声明作为示例,因为它们是可能出现在标题中的行,但还有许多其他可能性,甚至包括返回指向struct的指针的函数声明(因为函数声明的返回类型不在原型范围内)。

See below for some additional comments about the edited question.有关已编辑问题的其他评论,请参见下文。


My personal preference is to always use typedefs as forward declarations of tags, and never use struct foo anywhere in my code other than the typedef and the subsequent definition:我个人的偏好是始终使用 typedefs 作为标签的前向声明,并且在我的代码中除了 typedef 和后续定义之外的任何地方都不要使用struct foo

typedef struct Animal Animal;

void exclaim(Animal*);

// ...

// Later or in a different header
struct Animal {
  Animal* next;
  void (*exclaim)(Animal *);
  // etc.
};

Note that I always use the same identifier for the tag and the typedef.请注意,我总是对标签和 typedef 使用相同的标识符。 Why not?为什么不? There's no confusion and tags have not been in the same namespace as other identifiers since C was premordial.自从 C 是原始的以来,没有混淆,并且标签与其他标识符不在同一个命名空间中。

For me, a big advantage of this style is that it lets me separate implementation details;对我来说,这种风格的一大优势是它可以让我分离实现细节; the public header only contains the typedef declarations (and prototypes which use that type) and only the implementation needs to contain the actual definitions (after first having included the public header).公共标头仅包含 typedef 声明(以及使用该类型的原型),并且只有实现需要包含实际定义(在首先包含公共标头之后)。


Note: since this answer was written, the question was edited to add a more detailed code sample.注意:由于编写了此答案,因此对问题进行了编辑以添加更详细的代码示例。 For now, I'll just leave these additional notes here:现在,我将在此处留下这些附加说明:

On the whole, you get better answers when you provide better information.总的来说,当你提供更好的信息时,你会得到更好的答案。 Since I couldn't see your actual code, I did the best I could, which was to try to explain what is going on, leaving you to apply that to your actual code.由于我看不到您的实际代码,因此我尽了最大努力,即尝试解释发生了什么,让您将其应用到您的实际代码中。

In the code you have now added to the question, there is a circular header dependency.在您现在添加到问题的代码中,有一个循环头依赖项。 These should be avoided;应该避免这些; it's almost impossible to get them right.几乎不可能让它们正确。 The circular dependency means that you don't control the order of inclusion, so a declaration in one header might not come before the use in another header.循环依赖意味着您不控制包含顺序,因此一个标头中的声明可能不会在另一个标头中的使用之前出现。 You no longer have a forward declaration, because, depending on the inclusion order, it might be a backward declaration.您不再有前向声明,因为根据包含顺序,它可能是后向声明。

To resolve the circular dependency, abstract out the shared components and put them in a new header file.为了解决循环依赖,抽象出共享组件并将它们放在一个新的头文件中。 Here, forward declarations of structs (using, for example, typedefs) are highly useful because they don't depend on anything used in the definition of the struct.在这里,结构的前向声明(例如,使用 typedef)非常有用,因为它们不依赖于结构定义中使用的任何内容。 A shared header might include only typedefs, or it might also include prototypes which don't require additional dependencies.共享标头可能仅包含 typedef,也可能包含不需要额外依赖项的原型。

Also, avoid putting long lists of library includes in your header files;另外,避免在头文件中包含很长的库列表; include only those headers actually necessary to define types actually used in the header.仅包括那些实际上需要定义在标头中实际使用的类型的标头。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM