簡體   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