范围中的结构与函数定义

时间:2018-05-29 18:31:21

标签: c++ c linkage

因此,据我所知,这在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 ++?

6 个答案:

答案 0 :(得分:24)

  

为什么?

struct foo {
   int a;
};

定义用于创建对象的模板。它不会创建任何对象或功能。除非在代码中的某处使用struct foo,否则就编译器/链接器而言,这些代码行也可能不存在。

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

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

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

答案 1 :(得分:18)

本案例中的区别概念称为链接

在C struct中,union或enum标记具有无链接。它们的范围实际上是本地的。

  

6.2.2标识符的链接
   6 以下标识符没有链接:声明为除。之外的任何标识符   对象或功能;声明为函数参数的标识符;块范围   没有存储类说明符extern声明的对象的标识符。

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

同时,函数在C中具有链接。在您的示例中,这两个定义使用外部链接定义相同的函数foo。并且不允许在整个程序中提供任何外部链接函数的多个定义

  

6.9外部定义
   5 [...]如果使用外部声明的标识符   链接用在表达式中(除了作为sizeof_Alignof运算符的操作数的一部分,其结果是整数常量),在整个表达式中   程序应该只有一个标识符的外部定义;否则,那里   不得超过一个。

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

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

答案 2 :(得分:12)

您的函数定义都声明了一个名为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定义,即使它们从未用于通过外部链接声明对象或以其他方式共享"翻译单位之间。

答案 3 :(得分:3)

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,因为struct foo期望的bar_funcstruct foo提供的foo_func不兼容。

结构的兼容性详见C standard

的第6.2.7节
  

1 如果类型相同,则两种类型具有兼容类型。确定两种类型是否兼容的附加规则是   在6.7.2中描述了类型说明符,在6.7.3中描述了类型限定符,   在6.7.6中为声明者。而且,两个结构,联合,或   在单独的翻译单元中声明的枚举类型是兼容的   如果他们的标签和成员满足以下要求:如果一个   使用标记声明,另一个声明使用相同的标记。   如果两者都在各自的翻译中完成   单位,则适用以下附加要求:应有   是他们的成员之间的一对一的对应,每个   一对相应的成员用兼容的类型声明;如果   该对的一个成员用对齐说明符声明   另一个用等效的对齐说明符声明;如果一个   该对的成员用名称声明,另一个用声明   同名。对于两个结构,相应的成员应为   以相同的顺序声明。对于两个结构或工会,   相应的位域应具有相同的宽度。两个   枚举,相应的成员应具有相同的值。

     

2 引用同一对象或函数的所有声明都应具有兼容类型;否则,行为未定义。

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

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

答案 4 :(得分:2)

与现有名称的区别并不大;结构定义不存储在任何地方,其名称仅在编译期间存在 (程序员有责任确保在使用同名结构时不会发生冲突。否则,我们亲爱的老朋友Undefined Behavior会来电。)

另一方面,函数需要存储在某处,如果它具有外部链接,则链接器需要其名称。

如果你使你的函数static,所以它们在各自的编译单元之外“不可见”,链接错误就会消失。

答案 5 :(得分:2)

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

foo.c的

    static int foo() {
        return 1;
    }

bar.c

    static int foo() {
        return 0;
    }