代码之家  ›  专栏  ›  技术社区  ›  jyoung

一个只在一个编译单元中使用的类型如何违反一个定义规则?

c++
  •  12
  • jyoung  · 技术社区  · 16 年前

    我被告知,这些类型,在那里是可见的,有自己独特的翻译单元,违反了一个定义规则。有人能解释一下吗?

    //File1.cpp
    #include "StdAfx.h"
    static struct S { int Value() { return 1; } } s1;
    int GetValue1() { return s1.Value(); }
    
    //File2.cpp
    #include "StdAfx.h"
    static struct S { int Value() { return 2; } } s2;
    int GetValue2() { return s2.Value(); }
    
    // main.cpp
    #include "stdafx.h"
    extern int GetValue1();
    extern int GetValue2();
    int _tmain(int argc, _TCHAR* argv[])
    {
        if( GetValue1() != 1 ) throw "ODR violation";
        if( GetValue2() != 2 ) throw "ODR violation";
        return 0;
    } 
    

    我知道如何解决这个问题。根据标题,我想知道为什么会违反ODR。它如何违反:“在任何翻译单元中,模板、类型、函数或对象只能有一个定义。”?或者它违反了规则的另一部分。

    3 回复  |  直到 14 年前
        1
  •  7
  •   Mike Seymour    14 年前

    你已经定义了 struct S 在全局命名空间中以两种不同的方式,这打破了一个定义规则。特别是,有两种不同的定义 ::S::Value() ,它是未定义的,最终会被调用。

    您应该使用无名称的命名空间来确保 结构体 在每个翻译单元中定义:

    namespace { struct S {int Value() {return 1;}} s1; }
    int GetValue1() {return s1.Value();}
    

    一个定义规则比你引用的第一段要多得多。最后一段基本上说一些东西,包括类定义,可以在程序中出现多次, 只要 它们都是相同的。您的代码破坏了最后一个条件。或者,用本标准(节略)的话说:

    类类型可以有多个定义…在程序中,每个定义出现在不同的翻译单元中,并且前提是这些定义满足以下要求。如果在多个翻译单元中定义了这样一个名为d的实体,则d的每个定义应包含相同的令牌序列。

        2
  •  11
  •   Martin B    16 年前

    问题是,即使 s1 和 s2 只有内部链接,两个对应的定义 S 有外部连接。

    您要做的是使用匿名命名空间:

    //File1.cpp
    #include "StdAfx.h"
    namespace {
        struct S { int Value() { return 1; } } s1;
    }
    int GetValue1() { return s1.Value(); }
    
    //File2.cpp
    #include "StdAfx.h"
    namespace {
        struct S { int Value() { return 2; } } s2;
    }
    int GetValue2() { return s2.Value(); }
    

    编辑:

    匿名命名空间内的所有内容(包括类定义)都具有内部链接。

    匿名名称空间中的定义仍然具有外部链接,但编译器确保它们接收到的唯一名称不会与来自其他翻译单元的任何定义冲突。

        3
  •  10
  •   Michael Mrozek    16 年前

    这是不安全的,因为您有两个名为 S . 这个 static 关键字只适用于变量声明;它相当于您编写了:

    struct S {
        int Value() {return 1;}
    };
    
    static S s1;
    

    编译器在编译时不会注意到这一点,因为它分别处理每个翻译单元。这个 Value 结构中的函数被损坏为完全相同的名称,并在对象文件中成为弱全局符号,因此链接器不会引发有关符号名称冲突的错误;它只选择一个用于完全链接的二进制文件。这可能是第一个符号定义,这意味着您实际上可以根据链接对象的顺序获得不同的行为:

    > g++ -o test test.o test1.o test2.o && ./test
    s1 is 1
    s2 is 1
    
    > g++ -o test test.o test2.o test1.o && ./test
    s1 is 2
    s2 is 2
    

    您可以通过将结构包装在匿名命名空间中(这将使 价值 函数符号局部变量而不是弱全局变量):

    namespace {
        struct S {
            int Value() {return 1;}
        } s1;
    }
    

    或者简单地删除结构的名称,因为您实际上不需要它:

    struct {
        int Value() {return 1;}
    } s1;