有没有办法检测位字段中的填充位?

huangapple go评论66阅读模式
英文:

Is there a way to detect padding bits in a bitfield?

问题

I extensively utilize bit-fields in my C++ embedded application, and I have encountered a problem. Below is an example showcasing my usage:

struct {
    uint8_t /* 保留 */ : 3;
    uint8_t foo : 3;
    uint8_t bar : 3;
} bits;

为了确保我已经正确指定了位域中的位数,我使用 static_assert(...) 机制:

// 由于多了1位,断言失败
static_assert(sizeof(bits) == sizeof(uint8_t));

这种方法允许我验证是否超出了大小限制。然而,我目前无法确定在 struct 中是否有填充位。是否有一种方法可以检测这种情况?

struct {
    uint8_t /* 保留 */ : 3;
    uint8_t foo : 3;
    uint8_t bar : 1;
} bits;
// 断言通过,但我希望它失败,因为有一位是填充位
static_assert(sizeof(bits) == sizeof(uint8_t));
英文:

I extensively utilize bit-fields in my C++ embedded application, and I have encountered a problem. Below is an example showcasing my usage:

struct {
    uint8_t /* Reserved */ : 3;
    uint8_t foo : 3;
    uint8_t bar : 3;
} bits;

To ensure that I have correctly specified the number of bits in a bit-field, I employ the static_assert(...) mechanism:

// assertion fails due to 1 extra bit
static_assert(sizeof(bits) == sizeof(uint8_t));

This approach allows me to verify if I have surpassed the size limitation. However, I am currently unable to determine whether there are any padding bits in the struct. Is there a method to detect such situations?

struct {
    uint8_t /* Reserved */ : 3;
    uint8_t foo : 3;
    uint8_t bar : 1;
} bits;
// assertion passes, but I want it to fail, because one bit is padding
static_assert(sizeof(bits) == sizeof(uint8_t)); 

答案1

得分: 4

Clang的-Wpadded正是您所需的功能。

如果您不使用Clang,您可以将未命名的位字段重命名为类似于Reserved1ReservedN的名称。然后,您可以使用std::has_unique_object_representations 来判断您的类是否有填充:

struct {
    [[deprecated("This field should not be used")]] uint8_t Reserved1 : 2;
    uint8_t foo : 3;
    uint8_t bar : 3;
} bits;

// 如果`Reserved1`的位大小为3,它将会有填充,这将失败
static_assert(std::has_unique_object_representations_v<decltype(bits)>);
英文:

Clang's -Wpadded does exactly what you want.

If you are not using clang, you can rename your unnamed bitfields into something like Reserved1 through ReservedN. Then you can use std::has_unique_object_representations which tells you if your class has padding or not:

struct {
    [[deprecated(&quot;This field should not be used&quot;)]] uint8_t Reserved1 : 2;
    uint8_t foo : 3;
    uint8_t bar : 3;
} bits;

// If `Reserved1` had a bitsize of 3, it would have padding and this would fail
static_assert(std::has_unique_object_representations_v&lt;decltype(bits)&gt;);

答案2

得分: 3

无法使用 sizeof 或其他方法准确获取位域中位的确切数量。

位域中的填充问题

sizeof 操作符给出了结构体所占的字节数,而不是位数。在你的第二个示例中,你的位域包含 7 位,假设一个字节是 8 位,这行代码:

static_assert(sizeof(bits) == sizeof(uint8_t));

检查的是 1 == 1,因为你的结构体被填充到一个字节。

此外,混合不同的成员类型可能导致比预期更多的位:

struct {
    int a: 3;
    short b: 3;
} bits;

static_assert(sizeof(bits) == 1);

这给我们错误消息:
> cpp &gt; &lt;source&gt;:5:28: 错误: 静态断言失败 &gt; 5 | static_assert(sizeof(bits) == 1); &gt; | ~~~~~~~~~~~~~^~~~ &gt; &lt;source&gt;:5:28: 注意: 比较结果归结为 '(4 == 1)' &gt;

即使我们只使用了 6 位来表示 ab,我们的 bits 占用了 4 个字节。

最终,位域成员之间的填充量是由实现定义的,因此你不能依赖于任何特定数量的位或字节。你只能希望编译器以你打算的方式布局位域,可能使用带有位域大小上限的 static_assert

注意:使用非标准的 [[gnu::packed]] 属性,上面的示例可以编译通过。

有关更多信息,请参阅 cppreference 上的位域文章

使用宏作为解决方法

我们无法检测出确切的位数,但可以使用宏,以便我们简单地知道位数:

// 此列表用于定义位域成员和计算位数之和。
#define LIST \
  E(char, , 3) \
  E(char, foo, 3) \
  E(char, bar, 1)

// 定义列表中的条目意味着什么。
#define E(type, name, size) type name: size;

struct {
// LIST 扩展为 char: 3; char foo: 3; char bar: 1;
    LIST
} bits;

// 重新定义条目的含义。
#undef E
#define E(type, name, size) +size

// LIST 扩展为 +3 +3 +1
constexpr int bits_bits = LIST;

#undef E
#undef LIST

// 这个断言通过,因为我们总共有 7 位。
static_assert(bits_bits == 7);
// 这个断言失败,但如果我们有 8 位的话就会通过。
// 我们也可以检查 bits_bits 是否是 8 的倍数
// bits_bits % 8 == 0。
static_assert(sizeof(bits) * 8 == bits_bits);
英文:

It is not possible to get the exact amount of bits in a bit-field using sizeof, or any other way.

Problems with Padding in Bit-Fields

The sizeof operator gives you the amount of bytes that your struct occupies, not the amount of bits. In your second example, your bit-field consists of 7 bits, and assuming that bytes are 8 bits, the line:

static_assert(sizeof(bits) == sizeof(uint8_t));

is checking 1 == 1, since your struct is padded up to one byte.

Furthermore, mixing different member types can result in way more bits than one would expect:

struct {
    int a: 3;
    short b: 3;
} bits;

static_assert(sizeof(bits) == 1);

This gives us the error message:
> cpp
&gt; &lt;source&gt;:5:28: error: static assertion failed
&gt; 5 | static_assert(sizeof(bits) == 1);
&gt; | ~~~~~~~~~~~~~^~~~
&gt; &lt;source&gt;:5:28: note: the comparison reduces to &#39;(4 == 1)&#39;
&gt;

Even though we only use 6 bits for a and b, our bits occupies 4 bytes.

Ultimately, the amount of padding between bit-field members is implementation-defined, so you cannot rely on any specific amount of bits or bytes.
You can only hope that the compiler laid the bit-field out the way you intended, and perhaps use static_assert with an upper bound for the bit-field size in bytes.

Note: with the non-standard [[gnu::packed]] attribute, the above example would have compiled.

For more information, see the cppreference article on bit-fields.

Using Macros as a Workaround

We cannot detect the exact amount of bits, but we can use a macro so that we simply know the amount:

// This list is being used to define the bit-field members
// and to compute the sum of bits.
#define LIST \
  E(char, , 3) \
  E(char, foo, 3) \
  E(char, bar, 1)

// Define what an entry in the list means.
#define E(type, name, size) type name: size;

struct {
// LIST expands to char: 3; char foo: 3; char bar: 1;
    LIST
} bits;

// Re-define what an entry means.
#undef E
#define E(type, name, size) +size

// LIST expands to +3 +3 +1
constexpr int bits_bits = LIST;

#undef E
#undef LIST

// This assertion passes, since we have 7 bits in total.
static_assert(bits_bits == 7);
// This assertion fails, but would have passed if we had 8 bits.
// We could also check if bits_bits is a multiple of 8 with
// bits_bits % 8 == 0.
static_assert(sizeof(bits) * 8 == bits_bits);

答案3

得分: 1

没有一种优雅的方法来实现这个。

但是,您可以故意添加一个额外的位字段并检查大小是否增加。

struct {
    uint8_t /* 保留 */ : 3;
    uint8_t foo : 3;
    uint8_t bar : 1;
#ifdef DEBUG
    uint8_t BOOM : 1;
#endif
} bits;
#ifdef DEBUG
static_assert(sizeof(bits) > sizeof(uint8_t)); // 在调试模式下会断言,因为位数不足
#else
static_assert(sizeof(bits) == sizeof(uint8_t)); // 不会断言
#endif

当然,只有在更改的结构布局不会使程序的其余部分发生问题时,才能帮助您。

英文:

There's not a way to do it nicely.

But, you can deliberately add one more bit field and check that the size increases.

struct {
    uint8_t /* Reserved */ : 3;
    uint8_t foo : 3;
    uint8_t bar : 1;
#ifdef DEBUG
    uint8_t BOOM : 1;
#endif
} bits;
#ifdef DEBUG
static_assert(sizeof(bits) &gt; sizeof(uint8_t)); // Will assert in debug mode because there are insufficient bits
#else
static_assert(sizeof(bits) == sizeof(uint8_t)); // Will NOT assert
#endif

Of course, this only helps you if the changed struct layout doesn't make the rest of the program explode.

答案4

得分: 1

如果您允许使用constexpr辅助函数进行一些额外声明,那么在编译时可以像这样测试过多或不足的位数(以1位分辨率):

#include <array>

// 创建一个constexpr以查看所有位字段常量是否总和为底层类型的大小。
// 如果不是,则在编译时进行断言
template<typename underlying_t, std::size_t...sz>
static constexpr auto checked_bit_fields()
{
    // 数字将作为数组返回
    std::array<std::size_t, sizeof...(sz)> bit_layout{ sz... };

    // (sz + ...) 是折叠表达式,用于计算位字段的总和
    // 8 * sizeof 计算底层类型的位大小。
    static_assert((sz + ...) == 8ul * sizeof(underlying_t));
    return bit_layout;
}

struct bits_t
{
private:
    // 具有每个字段位数的模板参数。
    // 如果位字段大小的总和不匹配底层类型大小
    // 代码将无法编译(测试一下)
    static constexpr auto bits = checked_bit_fields<uint8_t, 2, 3, 3>();

    // 遗憾的是,标准不允许在这里使用结构化绑定
    // 所以我不能做 [unused,foo_bits,bar_bits] = checked_bit_fields<uint8_t,2,2,3>(); 
    // 我们需要从数组中访问位字段的大小

public:
    uint8_t : bits[0]; // == 2
    uint8_t foo : bits[1]; // == 3
    uint8_t bar : bits[2]; // == 3 
};


int main()
{
    bits_t bits;
    bits.foo = 3;
    return 0;
}
英文:

If you allow for a bit of extra declaration with a constexpr helper function then testing both too many or not enough bits (at 1 bit resolution) is possible at compile time like this:

#include &lt;array&gt;

// create a constexpr to see if all bit field constants
// add up to the size of the underlying type.
// if not then assert at compile time
template&lt;typename underlying_t, std::size_t...sz&gt;
static constexpr auto checked_bit_fields()
{
	// the numbers will be returned as an array
	std::array&lt;std::size_t, sizeof...(sz)&gt; bit_layout{ sz... };

	// (sz + ...) is fold expression calculating the sum of the bit fields
	// 8 * sizeof calculates the size in bits of the underlying type.
	static_assert((sz + ...) == 8ul * sizeof(underlying_t));
	return bit_layout;
}

struct bits_t
{
private:
	// the template parameters with number of bits per field.
	// if the sum of the bitfield sizes do not match the underlying type size
	// the code will not compile (test it out)
	static constexpr auto bits = checked_bit_fields&lt;uint8_t, 2, 3, 3&gt;();

	// regretably the standard does not allow for a structured binding here
	// so I could not do [unused,foo_bits,bar_bits] = checked_bit_fields&lt;uint8_t,2,2,3&gt;(); 
	// an we need to access the bit field sizes from the array

public:
	uint8_t : bits[0]; // == 2
	uint8_t foo : bits[1]; // == 3
	uint8_t bar : bits[2]; // == 3 
};


int main()
{
	bits_t bits;
	bits.foo = 3;
	return 0;
}

huangapple
  • 本文由 发表于 2023年6月8日 18:40:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76431002.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定