首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么GCC只在使用结构名而不是typedef时才抛出错误和未包含内容的警告?

为什么GCC只在使用结构名而不是typedef时才抛出错误和未包含内容的警告?
EN

Stack Overflow用户
提问于 2022-06-29 03:19:24
回答 1查看 123关注 0票数 2

我有一个程序,由两个源文件(far.c,init.c)和两个相应的头文件(far.h,init.h)组成,这两个源文件都包含头保护,因为它们都需要来自彼此的函数/变量。

I.h:

代码语言:javascript
运行
复制
#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

农场:

代码语言:javascript
运行
复制
#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

C.c:

代码语言:javascript
运行
复制
#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;
}

农场c:

代码语言:javascript
运行
复制
#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;
    }
}

在我的Linux系统上,使用这两种类型的名称(即struct anianimal)通常都能很好地使用C99标准。但是,当我在这里使用struct ani而不是animal时,对于struct anistruct resources类型使用的每个实例,我都会得到以下警告。

代码语言:javascript
运行
复制
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个警告:

代码语言:javascript
运行
复制
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;

有谁能解释一下为什么会发生这种行为,我怎样才能避免这些问题?解决这些错误通常需要我一段不可行的时间,而且我仍然没有真正理解我的错误。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-06-29 04:37:31

你碰到了一个奇怪的C范围规则的拐角处。

非正式地说,标记的struct (或union,但我不打算一遍又一遍地重复)在命名时会出现,如果它的声明是不可见的。“泉的存在”是指它被认为是在当前范围内宣布的。另外,如果标记的结构以前在作用域中被命名,然后使用相同的标记声明一个结构,那么这两个结构被认为是相同的。在完成结构声明之前,结构被视为不完整类型,但指向不完全类型的指针是完整类型,因此在实际完成结构定义之前,可以声明指向标记结构的指针。

在大多数情况下,这只需要很少的思考就可以了。但是功能原型有点特殊,因为函数原型本身就是一个作用域。(作用域只持续到函数声明结束。)

当你把这些放在一起的时候,你最终会遇到你所面临的问题。除非在函数原型出现之前就知道了标记的结构,否则不能使用指向函数原型中标记的结构的指针。如果前面提到过它,即使在外部作用域中,标记也是可见的,因此将被认为是相同的结构(即使它仍然不完整)。但是,如果标记以前是不可见的,则将在prototype范围内创建一个新的struct类型,在该作用域结束后,该结构类型将不可见(几乎是立即出现的)。

具体来说,如果您编写了以下内容:

代码语言:javascript
运行
复制
extern struct animal * barnyard;
void exclaim(struct animal*);

然后,struct animal的两种用法引用相同的struct类型,这可能会在稍后(或在另一个翻译单元中)完成。

但是如果没有extern struct animal * barnyard;声明,在exclaim prototype中命名的struct animal以前是不可见的,因此只在prototype作用域中声明,因此它与后续使用struct animal的类型不同。如果您将声明按相反的顺序放置,您就会看到编译器警告(假设您要求编译警告):

代码语言:javascript
运行
复制
void exclaim(struct animal*);
extern struct animal * barnyard;

(上帝保佑它的心)

typedef声明的执行方式与上面的extern声明相同;它是一个类型别名这一事实与此无关。重要的是在声明中使用struct animal会导致类型的存在,随后您可以在原型中自由地使用它。这也是相同的原因,即您的struct定义中的函数指针是OK的;struct定义的开始就足以导致声明标记,因此原型看到了它。

事实上,任何包含有标记结构(struct whatever)的句法结构都具有相同的目的,因为重要的是在没有可见声明的情况下提及标记结构的效果。上面,我使用extern全局声明作为示例,因为它们是可能出现在标题中的行,但还有许多其他可能性,甚至包括返回指向struct的指针的函数的声明(因为函数声明的返回类型不在原型范围内)。

有关编辑后的问题的其他评论,请参见下文。

我个人的偏好是始终使用typedefs作为标记的前向声明,并且在我的代码中的任何地方都不要使用struct foo,除了typedef和随后的定义:

代码语言:javascript
运行
复制
typedef struct Animal Animal;

void exclaim(Animal*);

// ...

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

请注意,我总是对标记和ty清晰度使用相同的标识符。为什么不行?没有混淆,而且标记与其他标识符的名称空间不同,因为C是前置的。

对我来说,这种样式的一个很大的优点是它允许我分离实现细节;公共头只包含ty对联f声明(以及使用该类型的原型),并且只有实现需要包含实际的定义(在首先包含了公共头之后)。

注意:由于这个答案是写好的,所以对问题进行了编辑,以添加一个更详细的代码示例。现在,我将把这些附加的注释留在这里:

总的来说,当你提供更好的信息时,你会得到更好的答案。因为我看不到您的实际代码,所以我已经尽力了,这就是尝试解释发生了什么,让您将其应用到实际的代码中。

在您现在添加到问题中的代码中,存在一个循环标题依赖项。这些都应该避免,几乎是不可能正确的。循环依赖意味着您无法控制包含的顺序,因此在一个标头中的声明可能不会在另一个头中使用之前出现。您不再有前向声明,因为根据包含顺序,它可能是向后声明。

要解决循环依赖关系,请提取共享组件并将它们放入新的头文件中。在这里,结构的前向声明(例如使用typedefs)非常有用,因为它们不依赖于结构定义中使用的任何内容。共享头可能只包含类型防御,也可能包含不需要额外依赖项的原型。

此外,避免将库包含的长列表放置在头文件中;只包括定义在头文件中实际使用的类型所需的那些头。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72795434

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档