[英]Struct vs. Function Definitions in Scope
所以,據我所知,這在C中是合法的:
foo.c的
struct foo {
int a;
};
bar.c
struct foo {
char a;
};
但功能相同是非法的:
foo.c的
int foo() {
return 1;
}
bar.c
int foo() {
return 0;
}
並將導致鏈接錯誤(函數foo
多重定義)。
這是為什么? 結構名稱和函數名稱之間的區別是什么使C無法處理一個而不能處理另一個? 這種行為也擴展到C ++嗎?
這是為什么?
struct foo {
int a;
};
定義用於創建對象的模板。 它不會創建任何對象或功能。 除非在代碼中的某處使用struct foo
否則就編譯器/鏈接器而言,這些代碼行也可能不存在。
請注意,C和C ++處理不兼容的struct
定義的方式不同。
只要您不混用它們的用法,在C程序中,您發布的代碼中struct foo
的不同定義是可以的。
但是,它在C ++中是不合法的。 在C ++中,它們具有外部鏈接,必須以相同的方式定義。 有關詳細信息,請參見3.2一個定義規則/ 5 。
在這種情況下,區別概念稱為聯系 。
在C struct中,union或enum標記沒有鏈接 。 它們的范圍實際上是本地的。
6.2.2標識符的鏈接
6以下標識符沒有鏈接:聲明為對象或函數以外的任何標識符; 聲明為函數參數的標識符; 沒有存儲類說明符extern
聲明的對象的塊作用域標識符。
它們不能在同一范圍內重新聲明(除了所謂的前向聲明 )。 但是它們可以在不同的范圍內自由重新聲明,包括不同的翻譯單元。 在不同的范圍內,他們可以聲明完全獨立的類型。 這就是您的示例中的內容:在兩個不同的轉換單元中(即在兩個不同的文件范圍中),您聲明了兩個不同且不相關的struct foo
類型。 這是完全合法的。
同時,函數在C中具有鏈接。在您的示例中,這兩個定義使用外部鏈接定義相同的函數foo
。 並且不允許在整個程序中提供任何外部鏈接功能的多個定義
6.9外部定義
5 [...]如果在表達式中使用了使用外部鏈接聲明的標識符(除了作為sizeof
或_Alignof
運算符的操作數的一部分,其結果是整數常量),整個程序中的某個地方應該只有一個標識符的外部定義; 否則,不得超過一個。
在C ++中, 鏈接的概念得到了擴展:它為更廣泛的實體(包括類型)分配特定的鏈接。 在C ++類類中有鏈接。 在命名空間范圍內聲明的類具有外部鏈接 。 C ++的一個定義規則明確指出,如果具有外部鏈接的類具有多個定義(跨不同的翻譯單元),則應在所有這些翻譯單元中等效地定義( http://eel.is/c++draft/basic .def.odr#12 )。 因此,在C ++中,您的struct
定義將是非法的。
由於C ++ ODR規則,您的函數定義在C ++中仍然是非法的(但基本上與C中的原因相同)。
您的函數定義都聲明了一個名為foo
的實體和外部鏈接,而C標准表示不能有多個具有外部鏈接的實體定義。 您定義的結構類型不是具有外部鏈接的實體,因此您可以擁有多個struct foo
定義。
如果使用相同的名稱聲明具有外部鏈接的對象,那么這將是一個錯誤:
foo.c的
struct foo {
int a;
};
struct foo obj;
bar.c
struct foo {
char a;
};
struct foo obj;
現在你有兩個名為obj
,它們都有外部鏈接,這是不允許的。
即使其中一個對象僅被聲明而未定義,它仍然是錯誤的:
foo.c的
struct foo {
int a;
};
struct foo obj;
bar.c
struct foo {
char a;
};
extern struct foo obj;
這是未定義的,因為obj
的兩個聲明引用相同的對象,但它們沒有兼容的類型(因為struct foo
在每個文件中的定義不同)。
C ++具有類似但更復雜的規則,以考慮inline
函數和inline
變量,模板和其他C ++特性。 在C ++中,相關要求稱為單一定義規則(或ODR)。 一個值得注意的區別是C ++甚至不允許兩種不同的struct
定義,即使它們從未用於聲明具有外部鏈接的對象或在翻譯單元之間“共享”。
struct foo
的兩個聲明彼此不兼容,因為成員的類型不同。 只要你不做任何事情來混淆兩者,在每個翻譯單元中使用它們都是可以的。
例如,如果您這樣做:
foo.c的:
struct foo {
char a;
};
void bar_func(struct foo *f);
void foo_func()
{
struct foo f;
bar_func(&f);
}
bar.c:
struct foo {
int a;
};
void bar_func(struct foo *f)
{
f.a = 1000;
}
你會被調用不確定的行為 ,因為struct foo
即bar_func
預計是不兼容的struct foo
是foo_func
的供應。
結構的兼容性詳見C標准 6.2.7節:
1如果類型相同,則兩種類型具有兼容類型。 用於確定兩種類型是否兼容的附加規則在6.7.2中描述了類型說明符,在6.7.3中描述了類型限定符,在6.7.6中描述了聲明符。 此外,如果它們的標記和成員滿足以下要求,則在單獨的轉換單元中聲明的兩個結構,聯合或枚舉類型是兼容的:如果使用標記聲明一個,則另一個應使用相同的標記聲明。 如果兩者都在其各自的翻譯單元內的任何地方完成,則以下附加要求適用:其成員之間應存在一對一的對應關系,以便每對相應的成員被宣布為兼容類型; 如果使用對齊說明符聲明該對中的一個成員,則使用等效的對齊說明符聲明另一個成員; 如果該對的一個成員使用名稱聲明,則另一個成員使用相同的名稱聲明。 對於兩個結構,相應的成員應按相同的順序聲明。 對於兩個結構或聯合,相應的位域應具有相同的寬度。 對於兩個枚舉,相應的成員應具有相同的值。
2涉及同一對象或功能的所有聲明均應具有兼容類型; 否則,行為未定義。
總而言之, struct foo
的兩個實例必須具有相同名稱和類型以及相同順序的成員才能兼容。
需要這樣的規則,以便可以在頭文件中定義一次struct
,並且該頭隨后包含在多個源文件中。 這導致struct
在多個源文件中定義,但每個實例都是兼容的。
名稱與存在的區別並不大; 結構定義不存儲在任何地方,其名稱僅在編譯期間存在。
(程序員有責任確保在使用同名結構時不會發生沖突。否則,我們親愛的老朋友Undefined Behavior會來電。)
另一方面,函數需要存儲在某處,如果它具有外部鏈接,則鏈接器需要其名稱。
如果你的函數是static
,那么它們在各自的編譯單元之外是“不可見的”,鏈接錯誤就會消失。
要從鏈接器隱藏函數定義,請使用關鍵字static。
foo.c的
static int foo() {
return 1;
}
bar.c
static int foo() {
return 0;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.