因此,据我所知,这在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 ++?
答案 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_func
与struct 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;
}