繁体   English   中英

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

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

我有一个由两个源文件(farm.c、init.c)和两个相应的头文件(farm.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

农场.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

初始化.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;
}

农场.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 anianimal )通常在我的具有 C99 标准的 Linux 系统上工作得很好。 但是,当我在这里使用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);

每次使用以下形式的函数指针总共有 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;

有人可以解释为什么会发生这种行为以及如何避免问题吗? 解决这些错误通常会花费我大量的时间,而且我仍然没有真正理解我的错误。

您遇到了 C 范围规则的奇怪极端情况之一。

非正式地,一个标记的struct (或union ,但我不会一遍又一遍地重复)当它被命名时,如果没有可见的声明,它就会出现。 “开始存在”意味着它被认为是在当前范围内声明的。 此外,如果之前在作用域中命名了标记结构,然后您声明了具有相同标记的结构,则这两个结构被认为是相同的。 在结构的声明完成之前,结构被认为是不完整类型,但指向不完整类型的指针是完整类型,因此您可以在实际完成结构定义之前声明指向标记结构的指针。

大多数时候,这只是用最少的想法来工作。 但是函数原型有点特殊,因为函数原型本身就是一个作用域。 (范围只持续到函数声明的结尾。)

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

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

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

那么struct animal的两种用法是指同一个 struct 类型,想必以后会完成(或者在另一个翻译单元中)。

但是没有extern struct animal * barnyard; 声明中,在exclaim原型中命名的struct animal以前是可见的,因此仅在原型范围内声明,因此它与struct animal的某些后续使用不同。 如果您以相反的顺序放置声明,您会看到编译器警告(假设您要求编译警告):

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

在上帝螺栓上,祝福它的心

typedef声明的执行方式与上面的extern声明相同; 它是类型别名的事实是不相关的。 重要的是在声明中使用struct animal会导致该类型突然出现,您随后可以在原型中自由使用它。 这与结构定义中的函数指针正常的原因相同; 结构定义的开始足以导致标签被声明,因此原型可以看到它。

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

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


我个人的偏好是始终使用 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.
};

请注意,我总是对标签和 typedef 使用相同的标识符。 为什么不? 自从 C 是原始的以来,没有混淆,并且标签与其他标识符不在同一个命名空间中。

对我来说,这种风格的一大优势是它可以让我分离实现细节; 公共标头仅包含 typedef 声明(以及使用该类型的原型),并且只有实现需要包含实际定义(在首先包含公共标头之后)。


注意:由于编写了此答案,因此对问题进行了编辑以添加更详细的代码示例。 现在,我将在此处留下这些附加说明:

总的来说,当你提供更好的信息时,你会得到更好的答案。 由于我看不到您的实际代码,因此我尽了最大努力,即尝试解释发生了什么,让您将其应用到您的实际代码中。

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

为了解决循环依赖,抽象出共享组件并将它们放在一个新的头文件中。 在这里,结构的前向声明(例如,使用 typedef)非常有用,因为它们不依赖于结构定义中使用的任何内容。 共享标头可能仅包含 typedef,也可能包含不需要额外依赖项的原型。

另外,避免在头文件中包含很长的库列表; 仅包括那些实际上需要定义在标头中实际使用的类型的标头。

暂无
暂无

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

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