具有预处理器分支实现的结构是否违反了 ODR?
Are structs with preprocessor branched implementation an ODR violation?
在同时使用 C 和 C++ 的项目中,.h
文件包含类型的定义。如果该定义取决于 header 是否包含在 c
或 cpp
文件中,我是否违反了一个定义规则?
// my_header.h
struct MyStruct
{
#ifdef __cplusplus
std::size_t member;
int surprise;
#else
unsigned member;
#endif
};
我知道 ODR 与不同的翻译单元有关,但是在 "my case" 中,不同的翻译单元不会最终对一个通用结构有不同的实现吗?我在生产代码中看到过这一点,最初我想知道链接器在这种情况下做了什么。
有什么想法吗?
是的,我有一个想法(嘿,抱歉,你问了):请不要那样写代码。好的,我会保留正确的方法直到最后。但就您的问题而言:是的,如果您在构建过程中同时使用 C 和 C++ 编译器,这将导致 ODR 违规。实际的文件扩展名可能无关紧要(它可能会更改编译器的默认值,但您的构建系统可能会明确指定编译器的语言)。也就是说,这是一个非常糟糕的想法,而且非常不寻常,因为 C 非常接近于成为 C++ 的真子集,因此更常见的是简单地编写也可以使用 C++ 编译器构建的 C 代码。在同时具有 C 和 C++ 组件的项目中,您将使用 C++ 编译器,而在纯 C 的项目中,您仍然可以使用该代码。因此,无论文件扩展名如何,只要给定项目只使用一个编译器就可以了。
// my_header.h
#ifdef __cplusplus
constexpr bool is_cpp = true;
#else
constexpr bool is_cpp = false;
#endif
struct cpp {
std::size_t member;
int surprise;
};
struct cc {
unsigned member;
};
template <bool CPP>
struct MyStructImpl : private std::conditional_t<cpp, cc, CPP>
{
};
using MyStruct = MyStructImpl<is_cpp>;
无论宏选项如何,这都会在以相同方式无条件定义的结构中保留尽可能多的代码,并尽可能晚地推迟与宏相关的内容。这在工具和测试方面也是一个巨大的胜利,例如您可以 运行 对结构的两个版本进行单元测试而无需重新编译。
只要使用一种编译器(C 或 C++),就不会有问题。头文件的扩展名无关紧要。
但是,如果您将来自不同语言的翻译单元链接在一起,那么是的,您违反了 ODR。
总的来说,这看起来很容易出错。我会给 C++ 类型一个完全不同的名称。您可以使用您的宏在两者之间切换,也许使用围绕 typedef
?
的预处理器
有两种情况:
所有包含 header(对于给定程序)的翻译单元都编译为相同的语言(C 或 C++):
==> 没问题。
一些包含header的翻译单元被翻译成C,一些被翻译成C++。
==> ODR 违规。
但是,ODR 违规只是“未定义的行为”,实际上,在将 C 和 C++ 链接在一起的标准中, 并没有定义那么多(除了一些模糊的建议“它应该工作”)。换句话说,如果您将 C 和 C++ 链接在一起,您可能无论如何都取决于实现的细节。
一般来说,如果您正在编译 32 位(因此 std::size_t
和 unsigned
大小相同),并且前提是 C++ 完成所有分配,并且前提是您从不处理在 C 中的这些东西的数组中,您可能会摆脱它。
我不清楚将多种语言链接在一起时违反 ODR 意味着什么; C++ 标准没有说明您可以在 C 目标文件中定义哪些 struct
。因此,我们被迫在 "assembly terms" 中回答(基于常见的实现),在这种情况下,答案(已经被更雄辩地给出)是 wat.
在同时使用 C 和 C++ 的项目中,.h
文件包含类型的定义。如果该定义取决于 header 是否包含在 c
或 cpp
文件中,我是否违反了一个定义规则?
// my_header.h
struct MyStruct
{
#ifdef __cplusplus
std::size_t member;
int surprise;
#else
unsigned member;
#endif
};
我知道 ODR 与不同的翻译单元有关,但是在 "my case" 中,不同的翻译单元不会最终对一个通用结构有不同的实现吗?我在生产代码中看到过这一点,最初我想知道链接器在这种情况下做了什么。
有什么想法吗?
是的,我有一个想法(嘿,抱歉,你问了):请不要那样写代码。好的,我会保留正确的方法直到最后。但就您的问题而言:是的,如果您在构建过程中同时使用 C 和 C++ 编译器,这将导致 ODR 违规。实际的文件扩展名可能无关紧要(它可能会更改编译器的默认值,但您的构建系统可能会明确指定编译器的语言)。也就是说,这是一个非常糟糕的想法,而且非常不寻常,因为 C 非常接近于成为 C++ 的真子集,因此更常见的是简单地编写也可以使用 C++ 编译器构建的 C 代码。在同时具有 C 和 C++ 组件的项目中,您将使用 C++ 编译器,而在纯 C 的项目中,您仍然可以使用该代码。因此,无论文件扩展名如何,只要给定项目只使用一个编译器就可以了。
// my_header.h
#ifdef __cplusplus
constexpr bool is_cpp = true;
#else
constexpr bool is_cpp = false;
#endif
struct cpp {
std::size_t member;
int surprise;
};
struct cc {
unsigned member;
};
template <bool CPP>
struct MyStructImpl : private std::conditional_t<cpp, cc, CPP>
{
};
using MyStruct = MyStructImpl<is_cpp>;
无论宏选项如何,这都会在以相同方式无条件定义的结构中保留尽可能多的代码,并尽可能晚地推迟与宏相关的内容。这在工具和测试方面也是一个巨大的胜利,例如您可以 运行 对结构的两个版本进行单元测试而无需重新编译。
只要使用一种编译器(C 或 C++),就不会有问题。头文件的扩展名无关紧要。
但是,如果您将来自不同语言的翻译单元链接在一起,那么是的,您违反了 ODR。
总的来说,这看起来很容易出错。我会给 C++ 类型一个完全不同的名称。您可以使用您的宏在两者之间切换,也许使用围绕 typedef
?
有两种情况:
所有包含 header(对于给定程序)的翻译单元都编译为相同的语言(C 或 C++):
==> 没问题。
一些包含header的翻译单元被翻译成C,一些被翻译成C++。
==> ODR 违规。
但是,ODR 违规只是“未定义的行为”,实际上,在将 C 和 C++ 链接在一起的标准中, 并没有定义那么多(除了一些模糊的建议“它应该工作”)。换句话说,如果您将 C 和 C++ 链接在一起,您可能无论如何都取决于实现的细节。
一般来说,如果您正在编译 32 位(因此 std::size_t
和 unsigned
大小相同),并且前提是 C++ 完成所有分配,并且前提是您从不处理在 C 中的这些东西的数组中,您可能会摆脱它。
我不清楚将多种语言链接在一起时违反 ODR 意味着什么; C++ 标准没有说明您可以在 C 目标文件中定义哪些 struct
。因此,我们被迫在 "assembly terms" 中回答(基于常见的实现),在这种情况下,答案(已经被更雄辩地给出)是 wat.