幻影类型是否与原始类型具有相同的对齐方式



考虑包含某些环境值的以下结构:

struct environment_values {
  uint16_t humidity;
  uint16_t temperature;
  uint16_t charging;
};

我想用幻影类型*为这些值添加一些其他信息*,并同时使它们的类型不同:

template <typename T, typename P>
struct Tagged {
    T value;
};
// Actual implementation will contain some more features
struct Celsius{};
struct Power{};
struct Percent{};
struct Environment {
  Tagged<uint16_t,Percent> humidity;
  Tagged<uint16_t,Celsius> temperature;
  Tagged<uint16_t,Power>   charging;
};

Environment的内存 - layout与 environment_values相同吗?这也适用于混合类型布局,例如:

struct foo {
    uint16_t value1;
    uint8_t  value2;
    uint64_t value3;
}
struct Foo {
    Tagged<uint16_t, Foo>  Value1;
    Tagged<uint8_t , Bar>  Value2;
    Tagged<uint64_t, Quux> Value3;
}

对于我到目前为止我尝试过的所有类型,持有以下断言:

template <typename T, typename P = int>
constexpr void check() {
    static_assert(alignof(T) == alignof(Tagged<T,P>), "alignment differs");
    static_assert(sizeof(T)  == sizeof(Tagged<T,P>),  "size differs");
}
// check<uint16_t>(), check<uint32_t>(), check<char>() …

由于标记和未标记的变体的大小也一样,所以答案应该是的,但是我想确定。

*我不知道如何在C 中调用这些标记值。"强烈打字"?我以Haskell的名字命名。

标准在[basic.Align]/1:

中提到

对象类型具有对齐要求(3.9.1,3.9.2) 对该类型对象的地址的限制 分配。对齐是实现定义的整数值 表示连续地址之间的字节数 给定的对象可以分配。物体类型强加对齐 该类型的每个对象都需要;更严格的对齐可能是 使用对齐式说明符(7.6.2)。

此外,[basic.compound]/3,提到:

指针类型的值表示是实现定义的。 指向布局兼容类型应具有相同的值 表示和对齐要求(6.11)。[注意:指示 过度对准类型(6.11)没有特殊的代表,但是 有效值范围受扩展对齐的限制 要求]。

结果,有保证与布局兼容类型具有相同的对齐方式。

struct { T m; }T不是布局兼容的。

如下所示,为了使两个元素兼容两个元素,然后它们都必须是标准类型,并且它们的非静态数据成员必须以相同的类型和相同的顺序进行。

struct { T m; }仅包含一个T,但是TT,因此它不能包含T作为其第一个非静态数据成员。

法律的字母,大小和类型的对齐方式是实现定义的,并且标准为您提供了很少的sizeofalignof的保证。

template <typename T, typename P>
struct Tagged {
    T value;
};

在理论上,允许编译器在该结构的末端添加填充物,这显然会改变大小,可能也可能改变对准。在实践中,我唯一可以设想这一发生的事情是,如果给出了T某种特定于编译器的"包装"属性,但是Tagged不是(但是即使那时,GCC似乎都可以正常工作)。

无论如何,我会说添加一些静态断言是一个好主意,以确保编译器是明智的 - 这正是您所做的:)。

GSAMARAS提到的标准保证了布局兼容类的相同对齐。

不幸的是,struct { T m; }T不是布局兼容

在12.2.21中,该标准列出了布局兼容类的要求:

如果其公共初始序列包括两个类别的所有成员和比特场(6.9),则两种标准层结构(第12条)类型是布局兼容类

常见初始序列的定义在12.2.20中:

两个标准layout struct(第12条)类型的公共初始序列是非静态数据成员和位于宣言顺序的最长序列,从每个结构中的第一个实体开始,以便相应的实体具有与布局相兼容的类型,并且任何一个实体都不是比特场,也不是两者都具有相同宽度。[示例:
struct A { int a; char b; };
struct B { const int b1; volatile char b2; };
struct C { int c; unsigned : 0; char b; };
struct D { int d; char b : 4; };
struct E { unsigned int e; char b; };
AB公共初始序列包括任一类的所有成员。ACAD公共初始序列包括每种情况下的第一个成员。AE常见初始序列是空的。
- 结束示例]

因此,从中我们可以进行以下重要观察:

  1. 布局兼容性严格限于标准布局类。(或枚举使用相同的基础类型或TT2实际上是完全相同的类型时。请参见6.9.11。)在一般情况下,T不是标准的布局类。实际上,T在您的示例中甚至都不是类(这是uint16_t,信不信由你,这都根据标准很重要。)*
  2. 即使保证T是标准布局类,struct { T m; }也没有T的常见初始序列。struct { T m; }的序列始于T,而T的序列始于T的非静态数据成员的任何内容。实际上,这是严格保证的,不要成为T,因为类不能按值包含。

因此,保证不能由标准的字母持有。您应该继续执行static_assert离子,以确保编译器以您期望的方式行事。

*请参阅有关联合类型的大多数问题。

最新更新