英文:
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,您可以将未命名的位字段重命名为类似于Reserved1
到ReservedN
的名称。然后,您可以使用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("This field should not be used")]] 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<decltype(bits)>);
答案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 > <source>:5:28: 错误: 静态断言失败 > 5 | static_assert(sizeof(bits) == 1); > | ~~~~~~~~~~~~~^~~~ > <source>:5:28: 注意: 比较结果归结为 '(4 == 1)' >
即使我们只使用了 6
位来表示 a
和 b
,我们的 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
> <source>:5:28: error: static assertion failed
> 5 | static_assert(sizeof(bits) == 1);
> | ~~~~~~~~~~~~~^~~~
> <source>:5:28: note: the comparison reduces to '(4 == 1)'
>
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) > 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 <array>
// 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<typename underlying_t, std::size_t...sz>
static constexpr auto checked_bit_fields()
{
// the numbers will be returned as an array
std::array<std::size_t, sizeof...(sz)> 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<uint8_t, 2, 3, 3>();
// regretably the standard does not allow for a structured binding here
// so I could not do [unused,foo_bits,bar_bits] = checked_bit_fields<uint8_t,2,2,3>();
// 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;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论