簡體   English   中英

范圍中的結構與函數定義

[英]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 foobar_func預計是不兼容的struct foofoo_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM