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

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

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:

  1. struct {
  2. uint8_t /* 保留 */ : 3;
  3. uint8_t foo : 3;
  4. uint8_t bar : 3;
  5. } bits;

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

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

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

  1. struct {
  2. uint8_t /* 保留 */ : 3;
  3. uint8_t foo : 3;
  4. uint8_t bar : 1;
  5. } bits;
  6. // 断言通过,但我希望它失败,因为有一位是填充位
  7. 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:

  1. struct {
  2. uint8_t /* Reserved */ : 3;
  3. uint8_t foo : 3;
  4. uint8_t bar : 3;
  5. } bits;

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

  1. // assertion fails due to 1 extra bit
  2. 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?

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

答案1

得分: 4

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

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

  1. struct {
  2. [[deprecated("This field should not be used")]] uint8_t Reserved1 : 2;
  3. uint8_t foo : 3;
  4. uint8_t bar : 3;
  5. } bits;
  6. // 如果`Reserved1`的位大小为3,它将会有填充,这将失败
  7. 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:

  1. struct {
  2. [[deprecated(&quot;This field should not be used&quot;)]] uint8_t Reserved1 : 2;
  3. uint8_t foo : 3;
  4. uint8_t bar : 3;
  5. } bits;
  6. // If `Reserved1` had a bitsize of 3, it would have padding and this would fail
  7. static_assert(std::has_unique_object_representations_v&lt;decltype(bits)&gt;);

答案2

得分: 3

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

位域中的填充问题

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

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

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

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

  1. struct {
  2. int a: 3;
  3. short b: 3;
  4. } bits;
  5. 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 上的位域文章

使用宏作为解决方法

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

  1. // 此列表用于定义位域成员和计算位数之和。
  2. #define LIST \
  3. E(char, , 3) \
  4. E(char, foo, 3) \
  5. E(char, bar, 1)
  6. // 定义列表中的条目意味着什么。
  7. #define E(type, name, size) type name: size;
  8. struct {
  9. // LIST 扩展为 char: 3; char foo: 3; char bar: 1;
  10. LIST
  11. } bits;
  12. // 重新定义条目的含义。
  13. #undef E
  14. #define E(type, name, size) +size
  15. // LIST 扩展为 +3 +3 +1
  16. constexpr int bits_bits = LIST;
  17. #undef E
  18. #undef LIST
  19. // 这个断言通过,因为我们总共有 7 位。
  20. static_assert(bits_bits == 7);
  21. // 这个断言失败,但如果我们有 8 位的话就会通过。
  22. // 我们也可以检查 bits_bits 是否是 8 的倍数
  23. // bits_bits % 8 == 0。
  24. 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:

  1. 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:

  1. struct {
  2. int a: 3;
  3. short b: 3;
  4. } bits;
  5. 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:

  1. // This list is being used to define the bit-field members
  2. // and to compute the sum of bits.
  3. #define LIST \
  4. E(char, , 3) \
  5. E(char, foo, 3) \
  6. E(char, bar, 1)
  7. // Define what an entry in the list means.
  8. #define E(type, name, size) type name: size;
  9. struct {
  10. // LIST expands to char: 3; char foo: 3; char bar: 1;
  11. LIST
  12. } bits;
  13. // Re-define what an entry means.
  14. #undef E
  15. #define E(type, name, size) +size
  16. // LIST expands to +3 +3 +1
  17. constexpr int bits_bits = LIST;
  18. #undef E
  19. #undef LIST
  20. // This assertion passes, since we have 7 bits in total.
  21. static_assert(bits_bits == 7);
  22. // This assertion fails, but would have passed if we had 8 bits.
  23. // We could also check if bits_bits is a multiple of 8 with
  24. // bits_bits % 8 == 0.
  25. static_assert(sizeof(bits) * 8 == bits_bits);

答案3

得分: 1

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

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

  1. struct {
  2. uint8_t /* 保留 */ : 3;
  3. uint8_t foo : 3;
  4. uint8_t bar : 1;
  5. #ifdef DEBUG
  6. uint8_t BOOM : 1;
  7. #endif
  8. } bits;
  9. #ifdef DEBUG
  10. static_assert(sizeof(bits) > sizeof(uint8_t)); // 在调试模式下会断言,因为位数不足
  11. #else
  12. static_assert(sizeof(bits) == sizeof(uint8_t)); // 不会断言
  13. #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.

  1. struct {
  2. uint8_t /* Reserved */ : 3;
  3. uint8_t foo : 3;
  4. uint8_t bar : 1;
  5. #ifdef DEBUG
  6. uint8_t BOOM : 1;
  7. #endif
  8. } bits;
  9. #ifdef DEBUG
  10. static_assert(sizeof(bits) &gt; sizeof(uint8_t)); // Will assert in debug mode because there are insufficient bits
  11. #else
  12. static_assert(sizeof(bits) == sizeof(uint8_t)); // Will NOT assert
  13. #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位分辨率):

  1. #include <array>
  2. // 创建一个constexpr以查看所有位字段常量是否总和为底层类型的大小。
  3. // 如果不是,则在编译时进行断言
  4. template<typename underlying_t, std::size_t...sz>
  5. static constexpr auto checked_bit_fields()
  6. {
  7. // 数字将作为数组返回
  8. std::array<std::size_t, sizeof...(sz)> bit_layout{ sz... };
  9. // (sz + ...) 是折叠表达式,用于计算位字段的总和
  10. // 8 * sizeof 计算底层类型的位大小。
  11. static_assert((sz + ...) == 8ul * sizeof(underlying_t));
  12. return bit_layout;
  13. }
  14. struct bits_t
  15. {
  16. private:
  17. // 具有每个字段位数的模板参数。
  18. // 如果位字段大小的总和不匹配底层类型大小
  19. // 代码将无法编译(测试一下)
  20. static constexpr auto bits = checked_bit_fields<uint8_t, 2, 3, 3>();
  21. // 遗憾的是,标准不允许在这里使用结构化绑定
  22. // 所以我不能做 [unused,foo_bits,bar_bits] = checked_bit_fields<uint8_t,2,2,3>();
  23. // 我们需要从数组中访问位字段的大小
  24. public:
  25. uint8_t : bits[0]; // == 2
  26. uint8_t foo : bits[1]; // == 3
  27. uint8_t bar : bits[2]; // == 3
  28. };
  29. int main()
  30. {
  31. bits_t bits;
  32. bits.foo = 3;
  33. return 0;
  34. }
英文:

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:

  1. #include &lt;array&gt;
  2. // create a constexpr to see if all bit field constants
  3. // add up to the size of the underlying type.
  4. // if not then assert at compile time
  5. template&lt;typename underlying_t, std::size_t...sz&gt;
  6. static constexpr auto checked_bit_fields()
  7. {
  8. // the numbers will be returned as an array
  9. std::array&lt;std::size_t, sizeof...(sz)&gt; bit_layout{ sz... };
  10. // (sz + ...) is fold expression calculating the sum of the bit fields
  11. // 8 * sizeof calculates the size in bits of the underlying type.
  12. static_assert((sz + ...) == 8ul * sizeof(underlying_t));
  13. return bit_layout;
  14. }
  15. struct bits_t
  16. {
  17. private:
  18. // the template parameters with number of bits per field.
  19. // if the sum of the bitfield sizes do not match the underlying type size
  20. // the code will not compile (test it out)
  21. static constexpr auto bits = checked_bit_fields&lt;uint8_t, 2, 3, 3&gt;();
  22. // regretably the standard does not allow for a structured binding here
  23. // so I could not do [unused,foo_bits,bar_bits] = checked_bit_fields&lt;uint8_t,2,2,3&gt;();
  24. // an we need to access the bit field sizes from the array
  25. public:
  26. uint8_t : bits[0]; // == 2
  27. uint8_t foo : bits[1]; // == 3
  28. uint8_t bar : bits[2]; // == 3
  29. };
  30. int main()
  31. {
  32. bits_t bits;
  33. bits.foo = 3;
  34. return 0;
  35. }

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:

确定