范围内的结构与函数定义

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++?

Why is that?

struct foo {
   int a;
};

定义用于创建对象的模板。它不创建任何对象或函数。除非 struct foo 在您的代码中的某处使用,就 compiler/linker 而言,这些代码行可能不存在。

请注意,C 和 C++ 处理不兼容 struct 定义的方式有所不同。

您发布的代码中 struct foo 的不同定义在 C 程序中是可以的,只要您不混合使用它们即可。

但是,在C++中是不合法的。在 C++ 中,它们具有外部链接并且必须完全相同地定义。有关详细信息,请参阅 3.2 One definition rule/5

区别不在于名称,而在于存在;结构定义不存储在任何地方,其名称仅在编译期间存在。
(程序员有责任确保在使用同名结构时没有冲突。否则,我们亲爱的老朋友未定义行为就会来找你了。)

另一方面,函数需要存储在某个地方,如果它有外部链接,链接器需要它的名字。

如果您创建函数 static,那么它们 "invisible" 在各自的编译单元之外,链接错误将消失。

您的函数定义都声明了一个名为 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 定义,即使它们从未用于声明具有外部链接的对象或翻译单元之间的其他 "shared"。

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;
}

您将调用 undefined behavior,因为 bar_func 期望的 struct foofoo_func 提供的 struct foo 不兼容。

C standard 的第 6.2.7 节详细介绍了结构的兼容性:

1 Two types have compatible type if their types are the same. Additional rules for determining whether two types are compatible are described in 6.7.2 for type specifiers, in 6.7.3 for type qualifiers, and in 6.7.6 for declarators. Moreover, two structure, union, or enumerated types declared in separate translation units are compatible if their tags and members satisfy the following requirements: If one is declared with a tag, the other shall be declared with the same tag. If both are completed anywhere within their respective translation units, then the following additional requirements apply: there shall be a one-to-one correspondence between their members such that each pair of corresponding members are declared with compatible types; if one member of the pair is declared with an alignment specifier, the other is declared with an equivalent alignment specifier; and if one member of the pair is declared with a name, the other is declared with the same name. For two structures, corresponding members shall be declared in the same order. For two structures or unions, corresponding bit-fields shall have the same widths. For two enumerations, corresponding members shall have the same values.

2 All declarations that refer to the same object or function shall have compatible type; otherwise, the behavior is undefined.

总而言之,struct foo 的两个实例必须具有相同名称和类型且顺序相同的成员才能兼容。

需要这样的规则,以便 struct 可以在头文件中定义一次,并且该头随后包含在多个源文件中。这导致 struct 在多个源文件中定义,但每个实例都是兼容的。

要对链接器隐藏函数定义,请使用关键字 static。

foo.c

    static int foo() {
        return 1;
    }

bar.c

    static int foo() {
        return 0;
    }

这种情况下的区别概念称为链接

在 C 结构中,联合或枚举标记没有链接。它们实际上在其范围内是本地的。

6.2.2 Linkages of identifiers
6 The following identifiers have no linkage: an identifier declared to be anything other than an object or a function; an identifier declared to be a function parameter; a block scope identifier for an object declared without the storage-class specifier extern.

它们不能在同一范围内重新声明(所谓的前向声明除外)。但它们可以在不同的范围内自由地重新声明,包括不同的翻译单元。在不同的范围内,它们可以声明完全独立的类型。这就是您的示例中的内容:在两个不同的翻译单元中(即在两个不同的文件范围中),您声明了两种不同且不相关的 struct foo 类型。这是完全合法的。

同时,函数在 C 中有链接。在您的示例中,这两个定义定义了相同的函数 fooexternal 链接。并且你在整个程序中不允许提供超过一个外部链接函数的定义

6.9 External definitions
5 [...] If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.


在 C++ 中,链接 的概念得到了扩展:它将特定链接分配给范围更广的实体,包括类型。在 C++ class 类型中有链接。 类 在命名空间范围内声明的 外部链接 。并且 C++ 的一个定义规则明确指出,如果具有外部链接的 class 有多个定义(跨不同的翻译单元),则应在所有这些翻译单元(http://eel.is/c++draft/basic.def.odr#12)中等效地定义它。因此,在 C++ 中,您的 struct 定义是非法的。

由于 C++ ODR 规则,您的函数定义在 C++ 中仍然是非法的(但本质上与 C 中的原因相同)。