2013-05-30 106 views
15

我剛剛在C中發現一個怪癖,我覺得很混亂。在C語言中,可以在聲明之前使用指向結構體的指針。這是一個非常有用的特性,因爲當你只是處理一個指針時,聲明是無關緊要的。然而,我發現了一個角落的情況,但這並不是真的,我真的不能解釋爲什麼。對我來說,在語言設計中看起來像是一個錯誤。奇怪的編譯器警告C:警告:在參數列表中聲明'struct'

把這個代碼:

#include <stdio.h> 

#include <stdlib.h> 


typedef void (*a)(struct lol* etc); 

void a2(struct lol* etc) { 

} 

int main(void) { 
     return 0; 
} 

給出:

foo.c:6:26: warning: ‘struct lol’ declared inside parameter list [enabled by default] 
foo.c:6:26: warning: its scope is only this definition or declaration, which is probably not what you want [enabled by default] 
foo.c:8:16: warning: ‘struct lol’ declared inside parameter list [enabled by default] 

要刪除這個問題,我們可以簡單地這樣做:

#include <stdio.h> 

#include <stdlib.h> 

struct lol* wut; 

typedef void (*a)(struct lol* etc); 

void a2(struct lol* etc) { 

} 

int main(void) { 
     return 0; 
} 

無法解釋的問題現在已經走了一個無法解釋原因。爲什麼?

請注意,這個問題是關於語言C的行爲(或可能編譯器行爲的gcc和叮噹),而不是我粘貼的具體示例。

編輯:

我不會接受「聲明的順序是非常重要的」作爲回答,除非你也解釋了爲什麼C將警告在函數參數列表使用結構指針的第一次,但允許它在任何其他情況下。爲什麼這可能是一個問題?

+2

你應該仍然告訴它的結構預先存在使用'struct lol;' – Dave

+0

你應該首先告訴編譯器存在這樣的類型結構lol,然後你可以使用struct lo或一個指向結構的指針在新函數的聲明中 – hetepeperfan

+2

另外它看起來像編譯器向您解釋了確切的問題:範圍。閱讀警告! – Dave

回答

27

要理解爲什麼編譯器會抱怨,你需要了解C「結構」 S兩件事情:

  • 創建它們(作爲一個聲明,但尚未定義,類型),只要你的名字他們,所以struct lol最先發生創建一個聲明
  • 他們遵守相同的「申報範圍」的規則,普通變量

struct lol {聲明,然後開始定義的結構,它是struct lol;struct lol *或別的東西沒有開括號後停止了「申報」的一步。)

即宣告但尚未定義的結構類型是什麼C稱之爲「不完全類型」的實例。你被允許使用指向不完全類型,只要你不要試圖跟隨指針:

struct lol *global_p; 
void f(void) { 
    use0(global_p);  /* this is OK */ 
    use1(*global_p);  /* this is an error */ 
    use2(global_p->field); /* and so is this */ 
} 

你必須完成,以「跟隨鼠標指針」,換句話說類型。

在任何情況下,不過,考慮函數聲明與普通int參數:

命名 ab這裏的括號內聲明
int imin2(int a, int b); /* returns a or b, whichever is smaller */ 
int isum2(int a, int b); /* returns a + b */ 

變量,但這些聲明需要走出的方式,以使next函數聲明不會抱怨它們被重新聲明。

同樣的事情發生與struct標籤名稱:

void gronk(struct sttag *p); 

struct sttag聲明一個結構,然後將聲明一掃,就像那些爲ab。但是這會產生一個很大的問題:標籤不見了,現在又不能再給結構類型命名了!如果你寫:

struct sttag { int field1; char *field2; }; 

定義一個新的和不同struct sttag,就像:

void somefunc(int x) { int y; ... } 
int x, y; 

定義了一個新的和不同xy在文件級別的範圍,從somefunc的有所不同。

幸運的是,如果你寫的函數聲明之前申報(甚至定義)的結構,原型級別宣言「是指回」到外作用域聲明:

struct sttag; 
void gronk(struct sttag *p); 

現在,這兩個struct sttag s是「相同的」struct sttag,所以當您稍後完成struct sttag時,您也正在完成gronk原型中的一個。


重的問題編輯:它肯定會遭到可以定義不同的結構,聯合和枚舉標記的作用,使它們的原型「泡出」自己的封閉範圍。這會讓問題消失。但它沒有這樣定義。由於它是ANSI C89委員會發明的(或者真的從當時的C++中偷取)原型,所以你可以將它歸咎於它們。 :-)

+0

謝謝。我認爲這是最有洞察力的答案。 –

+2

當他們發明這些(原型範圍和所有這些)時,我就在身邊。當人們開始使用原型時,人們在原型中發現了結構聲明的問題 - 通常會從執行類型檢查的編譯器中獲取不可理解的錯誤消息,但是隻是說struct'foo *與struct foo *不兼容而不解釋* why *。那很有趣。 :-) – torek

5

這是因爲,在第一個示例中,此結構先前未定義,因此編譯器會嘗試將此結構的第一個引用視爲定義。

一般來說,C語言的聲明順序很重要。您使用的所有內容都應該以某種身份提前正確聲明,以便編譯器可以在其他上下文中引用它時對其進行推理。

這不是語言設計中的錯誤或錯誤。相反,我相信這是一種選擇,可以簡化首批C編譯器的實現。前向聲明允許編譯器一次轉換源代碼(只要知道諸如尺寸和偏移的一些信息)。如果情況並非如此,編譯器只要遇到無法識別的標識符就能夠在程序中來回切換,因此要求其編碼發射循環要複雜得多。

+1

從技術上講,你仍然可以在「一次通過」中做到這一切,你只需要在內存中保存更多的數據。當然,編譯器中的簡單和複雜的工作方式是相似的。 :-) – torek

+0

@torek真。我會看看我能否更準確地說出來。 –

3

編譯器警告你一個向前聲明的struct lol。 C允許你這樣做:

struct lol;  /* forward declaration, the size and members of 
        struct lol are unknown */ 

定義自引用的結構時,這是最常用的,但界定是從來沒有在頭文件中定義私有結構時,它也很有用。因爲後者的使用情況下,允許以聲明接收功能或返回指向不完全結構:

void foo(struct lol *x); 

然而,僅僅使用未聲明的結構在函數聲明中,像你一樣,會被解釋爲本地struct lol的不完整聲明,其範圍受函數限制。這個解釋是由C標準規定的,但它沒有用(沒有辦法構造傳遞給函數的struct lol),並且幾乎肯定不是程序員的意圖,所以編譯器警告。