C 和 C++ 结构体 ODR 规则差异的澄清

Qui*_*mby 27 c c++ types linkage language-lawyer

我知道 ODR、链接、static和如何extern "C"与函数配合使用。但我不确定类型的可见性,因为它们无法声明static,并且 C 中没有匿名名称空间。

特别是,我想知道以下代码如果编译为 C 和 C++ 的有效性

// A.{c,cpp}
typedef struct foo_t{
    int x;
    int y;
} Foo;

static int use_foo() 
{ 
    Foo f;
    f.x=5;
    return f.x;
}
Run Code Online (Sandbox Code Playgroud)
// B.{c,cpp}
typedef struct foo_t{
    double x;
} Foo;

static int use_foo() 
{ 
    Foo f;
    f.x=5.0;
    return f.x;// Cast on purpose
}
Run Code Online (Sandbox Code Playgroud)

使用以下两个命令(我知道两个编译器都会根据扩展自动检测语言,因此名称不同)。

  • g++ -std=c++17 -pedantic -Wall -Wextra a.cpp b.cpp
  • gcc -std=c11 -pedantic -Wall -Wextra a.c b.c

8.3 版本可以愉快地编译两者,没有任何错误。显然,如果两个结构符号都具有外部链接,则存在 ODR 违规,因为定义不相同。是的,编译器不需要报告它,因此我的问题是因为两者都没有报告。

它是有效的 C++ 程序吗?

我不这么认为,这就是匿名名称空间的用途。

它是有效的 C 程序吗?

我在这里不确定,我读到考虑了类型static,这将使​​程序有效。有人可以确认一下吗?

C、C++ 兼容性

如果这些定义位于公共头文件中,也许位于不同的 C 库中,并且 C++ 程序包含两者,每个定义也位于不同的 TU 中,那么这会是 ODR 吗?如何防止这种情况发生呢?extern "C"起到什么作用吗?

Ser*_*sta 19

我将使用C 语言的C11 草案 n1570和 C++ 语言的 C++20 草案 n4860作为参考。

\n
    \n
  1. C语言

    \n

    C 中的类型没有链接: 6.2.2 标识符 \xc2\xa76 的链接:

    \n
    \n

    以下标识符没有链接:声明为除对象或函数之外的任何标识符...

    \n
    \n

    这意味着 ac 和 bc 中使用的类型是不相关的:您在两个编译单元中正确声明了不同的对象。

    \n
  2. \n
  3. C++语言

    \n

    C++ 中的类型确实具有链接。6.6 程序和链接 [basic.link] 说(强调我的):

    \n
      \n
    • \xc2\xa72:
    • \n
    \n
    \n

    当名称可能表示与另一个作​​用域中的声明引入的名称相同的对象、引用、函数、类型、模板\n名称空间或值时,则称该名称具有链接

    \n
    \n
      \n
    • \xc2\xa74
    • \n
    \n
    \n

    未命名命名空间或在未命名命名空间中直接或间接声明的命名空间具有\n内部链接。所有其他名称空间都有外部链接。具有名称空间范围的名称\n尚未在上面给出内部链接,并且该名称是
    ...
    \n命名类...
    ...
    \n其链接确定如下:\n
    \xe2\x80\x94 如果封闭的命名空间具有内部链接,该名称具有内部链接;\n
    \xe2\x80\x94 否则,如果该名称的声明附加到命名模块 (10.1) 并且未导出 (10.2),\n该名称具有模块链接;\n
    \xe2\x80\x94 否则,该名称具有外部链接

    \n
    \n

    a.cpp 和 b.cpp 中声明的类型与外部链接共享相同的标识符,并且不兼容:程序格式错误。

    \n
  4. \n
\n
\n

话虽这么说,大多数常见的编译器都能够编译 C 或 C++ 源代码,我敢打赌,他们会努力共享这两种语言的大部分实现。因此,我相信现实世界的实现即使对于 C++ 语言也能产生预期的结果。但未定义的行为并不禁止预期的结果......

\n

  • @PeterCordes:很长一段时间我没有尝试猜测大多数 C++ 语言决策的基本原理...... (2认同)

tst*_*isl 7

对于C。该程序有效。这里适用的唯一要求是“严格别名规则”,即只能通过兼容类型的左值访问该对象(+ 6.5p7中描述的一些例外)。

单独翻译单元中定义的结构/联合的兼容性在6.2.7p1中定义。

...在单独的翻译单元中声明的两个结构、联合或枚举类型是兼容的,如果它们的标签和成员满足以下要求:如果一个使用标签声明,则另一个应使用相同的标签声明。如果两者都在各自翻译单元内的任何位置完成,则适用以下附加要求:其成员之间应存在一一对应关系,以便每对相应成员都声明为兼容类型;如果该对中的一个成员使用对齐说明符声明,则另一个成员使用等效的对齐说明符声明;如果该对中的一个成员声明了名称,则另一个成员也声明为相同的名称。对于两个结构体,相应的成员应以相同的顺序声明。对于两个结构或联合,相应的位域应具有相同的宽度。对于两个枚举,对应的成员应具有相同的值。

因此,示例中的结构不兼容。

但是,这不是问题,因为f对象是通过本地定义的类型创建和访问的。如果对象是使用Foo一个翻译单元中定义的类型创建的,并通过Foo另一个翻译单元中的其他类型访问的,则将调用 UB:

// A.c
typedef struct foo_t{
    int x;
    int y;
} Foo;

void bar(void *f);

void foo() 
{ 
    Foo f;
    bar(&f);
}

// B.c
typedef struct foo_t{
    double x;
} Foo;

// using void* to avoid passing pointer to incompatible types
void bar(void *f_) 
{ 
    Foo *f = f_;
    f->x=5.0; // UB!
}
Run Code Online (Sandbox Code Playgroud)