简体   繁体   English

范围中的结构与函数定义

[英]Struct vs. Function Definitions in Scope

So, as far as I know, this is legal in C: 所以,据我所知,这在C中是合法的:

foo.c foo.c的

struct foo {
   int a;
};

bar.c bar.c

struct foo {
    char a;
};

But the same thing with functions is illegal: 但功能相同是非法的:

foo.c foo.c的

int foo() {
    return 1;
}

bar.c bar.c

int foo() {
    return 0;
}

and will result in linking error (multiple definition of function foo ). 并将导致链接错误(函数foo多重定义)。

Why is that? 这是为什么? What's the difference between struct names and function names that makes C unable to handle one but not the other? 结构名称和函数名称之间的区别是什么使C无法处理一个而不能处理另一个? Also does this behavior extend to C++? 这种行为也扩展到C ++吗?

Why is that? 这是为什么?

struct foo {
   int a;
};

defines a template for creating objects. 定义用于创建对象的模板。 It does not create any objects or functions. 它不会创建任何对象或功能。 Unless struct foo is used somewhere in your code, as far as the compiler/linker is concerned, those lines of code may as well not exist. 除非在代码中的某处使用struct foo否则就编译器/链接器而言,这些代码行也可能不存在。

Please note that there is a difference in how C and C++ deal with incompatible struct definitions. 请注意,C和C ++处理不兼容的struct定义的方式不同。

The differing definitions of struct foo in your posted code, is ok in a C program as long as you don't mix their usage. 只要您不混用它们的用法,在C程序中,您发布的代码中struct foo的不同定义是可以的。

However, it is not legal in C++. 但是,它在C ++中是不合法的。 In C++, they have external linkage and must be defined identically. 在C ++中,它们具有外部链接,必须以相同的方式定义。 See 3.2 One definition rule/5 for further details. 有关详细信息,请参见3.2一个定义规则/ 5

The distinguishing concept in this case is called linkage . 在这种情况下,区别概念称为联系

In C struct, union or enum tags have no linkage . 在C struct中,union或enum标记没有链接 They are effectively local to their scope. 它们的范围实际上是本地的。

6.2.2 Linkages of identifiers 6.2.2标识符的链接
6 The following identifiers have no linkage: an identifier declared to be anything other than an object or a function; 6以下标识符没有链接:声明为对象或函数以外的任何标识符; an identifier declared to be a function parameter; 声明为函数参数的标识符; a block scope identifier for an object declared without the storage-class specifier extern . 没有存储类说明符extern声明的对象的块作用域标识符。

They cannot be re-declared in the same scope (except for so called forward declarations ). 它们不能在同一范围内重新声明(除了所谓的前向声明 )。 But they can be freely re-declared in different scopes, including different translation units. 但是它们可以在不同的范围内自由重新声明,包括不同的翻译单元。 In different scopes they may declare completely independent types. 在不同的范围内,他们可以声明完全独立的类型。 This is what you have in your example: in two different translation units (ie in two different file scopes) you declared two different and unrelated struct foo types. 这就是您的示例中的内容:在两个不同的转换单元中(即在两个不同的文件范围中),您声明了两个不同且不相关的struct foo类型。 This is perfectly legal. 这是完全合法的。

Meanwhile, functions have linkage in C. In your example these two definitions define the same function foo with external linkage. 同时,函数在C中具有链接。在您的示例中,这两个定义使用外部链接定义相同的函数foo And you are not allowed to provide more than one definition of any external linkage function in your entire program 并且不允许在整个程序中提供任何外部链接功能的多个定义

6.9 External definitions 6.9外部定义
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; 5 [...]如果在表达式中使用了使用外部链接声明的标识符(除了作为sizeof_Alignof运算符的操作数的一部分,其结果是整数常量),整个程序中的某个地方应该只有一个标识符的外部定义; otherwise, there shall be no more than one. 否则,不得超过一个。


In C++ the concept of linkage is extended: it assigns specific linkage to a much wider variety of entities, including types. 在C ++中, 链接的概念得到了扩展:它为更广泛的实体(包括类型)分配特定的链接。 In C++ class types have linkage. 在C ++类类中有链接。 Classes declared in namespace scope have external linkage . 在命名空间范围内声明的类具有外部链接 And One Definition Rule of C++ explicitly states that if a class with external linkage has several definitions (across different translation units) it shall be defined equivalently in all of these translation units ( http://eel.is/c++draft/basic.def.odr#12 ). C ++的一个定义规则明确指出,如果具有外部链接的类具有多个定义(跨不同的翻译单元),则应在所有这些翻译单元中等效地定义( http://eel.is/c++draft/basic .def.odr#12 )。 So, in C++ your struct definitions would be illegal. 因此,在C ++中,您的struct定义将是非法的。

Your function definitions remain illegal in C++ as well because of C++ ODR rule (but essentially for the same reasons as in C). 由于C ++ ODR规则,您的函数定义在C ++中仍然是非法的(但基本上与C中的原因相同)。

Your function definitions both declare an entity called foo with external linkage, and the C standard says there must not be more than one definition of an entity with external linkage. 您的函数定义都声明了一个名为foo的实体和外部链接,而C标准表示不能有多个具有外部链接的实体定义。 The struct types you defined are not entities with external linkage, so you can have more than one definition of struct foo . 您定义的结构类型不是具有外部链接的实体,因此您可以拥有多个struct foo定义。

If you declared objects with external linkage using the same name then that would be an error: 如果使用相同的名称声明具有外部链接的对象,那么这将是一个错误:

foo.c foo.c的

struct foo {
   int a;
};
struct foo obj;

bar.c bar.c

struct foo {
    char a;
};
struct foo obj;

Now you have two objects called obj that both have external linkage, which is not allowed. 现在你有两个名为obj ,它们都有外部链接,这是不允许的。

It would still be wrong even if one of the objects is only declared, not defined: 即使其中一个对象仅被声明而未定义,它仍然是错误的:

foo.c foo.c的

struct foo {
   int a;
};
struct foo obj;

bar.c bar.c

struct foo {
    char a;
};
extern struct foo obj;

This is undefined, because the two declarations of obj refer to the same object, but they don't have compatible types (because struct foo is defined differently in each file). 这是未定义的,因为obj的两个声明引用相同的对象,但它们没有兼容的类型(因为struct foo在每个文件中的定义不同)。

C++ has similar, but more complex rules, to account for inline functions and inline variables, templates, and other C++ features. C ++具有类似但更复杂的规则,以考虑inline函数和inline变量,模板和其他C ++特性。 In C++ the relevant requirements are known as the One-Definition Rule (or ODR). 在C ++中,相关要求称为单一定义规则(或ODR)。 One notable difference is that C++ doesn't even allow the two different struct definitions, even if they are never used to declare objects with external linkage or otherwise "shared" between translation units. 一个值得注意的区别是C ++甚至不允许两种不同的struct定义,即使它们从未用于声明具有外部链接的对象或在翻译单元之间“共享”。

The two declarations for struct foo are incompatible with each other because the types of the members are not the same. struct foo的两个声明彼此不兼容,因为成员的类型不同。 Using them both within each translation unit is fine as long as you don't do anything to confuse the two. 只要你不做任何事情来混淆两者,在每个翻译单元中使用它们都是可以的。

If for example you did this: 例如,如果您这样做:

foo.c: foo.c的:

struct foo {
   char a;
};

void bar_func(struct foo *f);

void foo_func()
{
    struct foo f;
    bar_func(&f);
}

bar.c: bar.c:

struct foo {
   int a;
};

void bar_func(struct foo *f)
{
    f.a = 1000;
}

You would be invoking undefined behavior because the struct foo that bar_func expects is not compatible with the struct foo that foo_func is supplying. 你会被调用不确定的行为 ,因为struct foobar_func预计是不兼容的struct foofoo_func的供应。

The compatibility of structs is detailed in section 6.2.7 of the C standard : 结构的兼容性详见C标准 6.2.7节:

1 Two types have compatible type if their types are the same. 1如果类型相同,则两种类型具有兼容类型。 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. 用于确定两种类型是否兼容的附加规则在6.7.2中描述了类型说明符,在6.7.3中描述了类型限定符,在6.7.6中描述了声明符。 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; 2涉及同一对象或功能的所有声明均应具有兼容类型; otherwise, the behavior is undefined. 否则,行为未定义。

To summarize, the two instances of struct foo must have members with the same name and type and in the same order to be compatible. 总而言之, struct foo的两个实例必须具有相同名称和类型以及相同顺序的成员才能兼容。

Such rules are needed so that a struct can be defined once in a header file and that header is subsequently included in multiple source files. 需要这样的规则,以便可以在头文件中定义一次struct ,并且该头随后包含在多个源文件中。 This results in the struct being defined in multiple source files, but with each instance being compatible. 这导致struct在多个源文件中定义,但每个实例都是兼容的。

The difference isn't so much in the names as in existence; 名称与存在的区别并不大; a struct definition isn't stored anywhere and its name only exists during compilation. 结构定义不存储在任何地方,其名称仅在编译期间存在。
(It is the programmer's responsibility to ensure that there is no conflict in the uses of identically named structs. Otherwise, our dear old friend Undefined Behaviour comes calling.) (程序员有责任确保在使用同名结构时不会发生冲突。否则,我们亲爱的老朋友Undefined Behavior会来电。)

On the other hand, a function needs to be stored somewhere, and if it has external linkage, the linker needs its name. 另一方面,函数需要存储在某处,如果它具有外部链接,则链接器需要其名称。

If you make your functions static , so they're "invisible" outside their respective compilation unit, the linking error will disappear. 如果你的函数是static ,那么它们在各自的编译单元之外是“不可见的”,链接错误就会消失。

To hide the function definition from the linker use the keyword static. 要从链接器隐藏函数定义,请使用关键字static。

foo.c foo.c的

    static int foo() {
        return 1;
    }

bar.c bar.c

    static int foo() {
        return 0;
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM