为什么只有在使用struct名称而不是typedef时,海湾合作委员会才会出现错误和不可信的警告?
我有一个由两个源文件(farm.c,init.c)和两个相应的标头文件(farm.h,init.h)组成的程序彼此。
init.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:
#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:
#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:
#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;
}
}
使用两种类型的名称(即,struct ani
和Animal
)通常在我的Linux上效果很好具有C99标准的系统。但是,当我使用struct ani
而不是Animal
时,我会在此处获取以下警告。 >结构资源。
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);
对于形式的功能指针的每种用法,总共有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;
有人可以解释为什么会发生这种行为以及我如何避免问题?通常,我花了很多时间来解决这些错误,我仍然并不真正理解自己的错误。
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.
init.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:
#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:
#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:
#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 (i.e.,struct ani
and animal
) normally works perfectly fine on my Linux system with the C99 standard. 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
.
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:
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您已经击中了C范围规则的奇怪角案例之一。
非正式地,标记的
struct
(或union
,但是如果没有可见的声明,我不会一遍又一遍地重复它的弹簧) 。 “弹簧成立”是指在当前范围中被视为声明。同样,如果先前在范围中命名的标记结构,然后您声明具有相同标签的结构,则两个结构被认为是相同的。直到完成结构的声明为止,结构被认为是不完整的类型,但是指向不完整类型的指针是完整的类型,因此您可以在实际完成结构的定义之前声明指向标记的结构的指针。在大多数情况下,这只是最少的思想。但是功能原型有点特别,因为函数原型本身就是范围。 (范围仅持续到函数声明结束。)
将它们放在一起时,您最终会遇到面对的问题。除非在函数原型出现之前已知标记的struct,否则您不能在功能原型中使用指向标记的结构的指针。如果以前提到,即使在外部范围中,标签也可以看到,因此将被认为是相同的结构(即使它仍然不完整)。但是,如果标签以前不可见,则将在原型范围内创建一种新的结构类型,在该范围结束后(几乎是立即)将不可见。
具体来说,如果您写下以下内容:
那么
struct Animal
的两种用途请参阅相同的结构类型,大概将稍后完成(或在另一个翻译单元中)。但是如果没有
Extern Struct Animal * barnyard;
声明,struct Animal
在Apaim中命名
原型原型原型不可见,因此仅声明为在原型范围中,它与 不是与后续使用struct Animal
相同的类型。如果您以相反的顺序发布声明,您会看到一个编译器警告(假设您要求进行编译警告):(在Godbolt上,祝福它的心)
A
typedef
声明的执行方式与上面的extern
声明相同;它是类型的别名这一事实无关。重要的是,在声明中使用struct Animal
会导致类型出现,您随后可以在原型中自由使用它。这就是函数指针在您的结构定义中的指针相同的原因。结构定义的开始足以使标签声明,因此原型看到了标签。实际上,任何包含标记结构的句法结构(
struct whyther
)都会达到相同的目的,因为重要的是提及没有可见声明的标记结构的效果。上面,我使用extern
全局声明为示例,因为它们是可能出现在标题中的行,但是还有许多其他可能性,包括返回的函数的声明指向struct
的指针(因为函数声明的返回类型不在原型范围中)。有关编辑问题的其他评论,请参见下文。
我个人的偏爱是始终使用Typedef作为标签的正向声明,而从我的代码中的任何地方使用
struct foo
除typedef和后续定义:请注意,我始终将同一标识符用于标签和Typedef。为什么不呢?没有混淆,自Cheartial以来,标签没有与其他标识符相同的命名空间。
对我来说,这种风格的一个很大的优势在于,它使我可以分开实施细节。公共标头仅包含Typedef声明(以及使用该类型的原型),并且仅实现需要包含实际定义(首先包含公共标头后)。
注意:由于编写了此答案,因此编辑了该问题以添加更详细的代码示例。现在,我将在此处留下这些其他笔记:
总体上,当您提供更好的信息时,您会得到更好的答案。由于我看不到您的实际代码,所以我尽了最大的努力,那就是尝试解释发生的事情,使您将其应用于实际代码。
在“代码”中,您现在添加了问题,存在一个圆形标题依赖性。应该避免这些;让他们正确几乎是不可能的。循环依赖性意味着您不控制包含的顺序,因此在另一个标头中使用之前,一个标头中的声明可能不会出现。您不再有远期声明,因为根据包含命令,这可能是向后的声明。
为了解决循环依赖关系,请抽象共享组件,然后将它们放入新的标题文件中。在这里,结构的正向声明(例如使用TypedEfs)非常有用,因为它们不依赖于结构的定义中使用的任何内容。共享标头可能仅包含Typedefs,或者还包括不需要其他依赖项的原型。
另外,避免在标题文件中包含一长串库列表;仅包括实际上定义标题中实际使用的类型所需的那些标题。
You've hit one of the odd corner cases of C scoping rules.
Informally, a tagged
struct
(orunion
, 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. "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:
then the two uses of
struct animal
refer to the same struct type, which will presumably be completed later (or in another translation unit).But without the
extern struct animal * barnyard;
declaration, thestruct animal
named in theexclaim
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 ofstruct animal
. Had you put the declarations in the opposite order, you would have seen a compiler warning (assuming you'd asked for compile warnings):(On godbolt, bless it's heart)
A
typedef
declaration performs the same way as theextern
declaration above; the fact that it is a type alias is not relevant. What's important is that the use ofstruct animal
in the declaration causes the type to spring into existence, and you can subsequently use it freely in a prototype. 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. Above, I usedextern
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 astruct
(because the return type of a function declaration is not in the prototype scope).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:Note that I always use the same identifier for the tag and the typedef. Why not? There's no confusion and tags have not been in the same namespace as other identifiers since C was premordial.
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).
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. A shared header might include only typedefs, or it might also include prototypes which don't require additional dependencies.
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.