让我们考虑以下任务:我的C ++模块作为嵌入式系统的一部分,接收8个字节的数据,例如:uint8_t data [8]。第一个字节的值确定其余字节的布局(20-30个不同)。为了有效地获取数据,我将为每个布局创建不同的结构,并将每个结构放到一个并集中,然后通过这样的指针直接从输入地址中读取数据:
struct Interpretation_1 {
uint8_t multiplexer;
uint8_t timestamp;
uint32_t position;
uint16_t speed;
};
// and a lot of other struct like this (with bitfields, etc..., layout is not defined by me :( )
union DataInterpreter {
Interpretation_1 movement;
//Interpretation_2 temperatures;
//etc...
};
...
uint8_t exampleData[8] {1u, 10u, 20u,0u,0u,0u, 5u,0u};
DataInterpreter* interpreter = reinterpret_cast<DataInterpreter*>(&exampleData);
std::cout << "position: " << +interpreter->movement.position << "\n";
我的问题是,编译器可以在解释结构中插入填充字节,这扼杀了我的想法。我知道我可以使用
struct MyStruct{} __attribute__((__packed__));
#pragma pack(push, 1) MyStruct{}; #pragma pack(pop)
但是有没有可移植的方法来实现这一目标?我知道c ++ 11有alignas用于对齐控制,但是我可以使用它吗?我必须使用c ++ 11,但是如果更高版本的c ++有更好的解决方案,我将很感兴趣。
但是有什么便携式方法可以实现这一目标吗?
不,没有(标准)方法来“制作”一个具有填充而在C ++中没有填充的类型。所有对象的对齐方式至少与其类型所需的对齐方式一致,并且如果该对齐方式与先前的子对象不匹配,则将存在填充,这是不可避免的。
此外,还有另一个问题:您正在通过未指向兼容类型对象的重新解释的指针进行访问。该程序的行为是不确定的。
我们可以得出结论,类通常对于表示任意二进制数据没有用。打包结构是非标准的,并且它们在不同系统之间也不兼容,这些系统具有不同的整数表示形式(字节字节序)。
有一种检查类型是否包含填充的方法:将子对象的大小与完整对象的大小进行比较,然后对每个成员进行递归操作。如果大小不匹配,则存在填充。但是,这非常棘手,因为C ++具有最小的反射功能,因此您需要采用硬编码或元编程。
通过这种检查,您可以使在假设不成立的系统上编译失败。
[另一个方便的工具是std::has_unique_object_representations
(自C ++ 17起),对于所有具有填充的类型,它始终为false。但是请注意,例如对于包含浮点数的类型也将为false。只有返回true的类型才能使用std::memcmp
有意义地进行相等性比较。
从未对齐的内存中读取是C ++中未定义的行为。换句话说,允许编译器假定每个uint32_t
位于alignof(uint32_t)
字节边界,并且每个uint16_t
位于alignof(uint16_t)
字节边界。这意味着,如果您设法以某种方式设法打包字节,执行interpreter->movement.position
仍会触发未定义的行为。
(实际上,在大多数体系结构上,未对齐的内存访问仍然可以使用,但是会导致性能下降。)
但是,您可以编写包装器,例如std::vector<bool>::operator[]
的工作方式:
#include <cstdint>
#include <cstring>
#include <iostream>
#include <type_traits>
template <typename T>
struct unaligned_wrapper {
static_assert(std::is_trivial<T>::value);
std::aligned_storage_t<sizeof(T), 1> buf;
operator T() const noexcept {
T ret;
memcpy(&ret, &buf, sizeof(T));
return ret;
}
unaligned_wrapper& operator=(T t) noexcept {
memcpy(&buf, &t, sizeof(T));
return *this;
}
};
struct Interpretation_1 {
unaligned_wrapper<uint8_t> multiplexer;
unaligned_wrapper<uint8_t> timestamp;
unaligned_wrapper<uint32_t> position;
unaligned_wrapper<uint16_t> speed;
};
// and a lot of other struct like this (with bitfields, etc..., layout is not defined by me :( )
union DataInterpreter {
Interpretation_1 movement;
//Interpretation_2 temperatures;
//etc...
};
int main(){
uint8_t exampleData[8] {1u, 10u, 20u,0u,0u,0u, 5u,0u};
DataInterpreter* interpreter = reinterpret_cast<DataInterpreter*>(&exampleData);
std::cout << "position: " << interpreter->movement.position << "\n";
}
这将确保对未对齐整数的每次读取或写入都将转换为不需任何对齐要求的按字节排列的memcpy
。能够快速访问未对齐内存的体系结构可能会为此降低性能,但是它可以在任何符合要求的编译器上使用。