如何在C++中序列化结构数据
How to serialize structure data in C++?
在一次采访中,我被要求序列化数据(这样它就可以存储在缓冲区中并通过某种网络发送(。这就是我想到的
struct AMG_ANGLES {
float yaw;
float pitch;
float roll;
};
char b[sizeof(struct AMG_ANGLES)];
char* encode(struct AMG_ANGLES *a)
{
std::memcpy(b, &a, sizeof(struct AMG_ANGLES));
return b;
}
void decode(char* data)
{
// check endianess
AMG_ANGLES *tmp; //Re-make the struct
std::memcpy(&tmp, data, sizeof(tmp));
}
这是正确的吗?有人能提供替代设计吗?我没有得到回拨,所以我只是想知道我本可以改进的地方。
这是正确的吗?
很可能,没有
序列化的目的是将数据转换为完全独立于平台的形式——例如,不依赖于endianes之类的东西,或者如果float
是IEEE 754或其他非常不同的东西。这需要:
a( 对预期格式的严格一致性-例如,如果它是某种文本(XML、JSON、CSV…(,或者如果它是对每个单独字节的含义有明确定义的"原始二进制"(例如,可能"字节1总是有效位的最低8位"(。
b( 正确转换为任何预期格式(例如,可能类似于确保字节1始终是有效位的最低8位,而不考虑任何/所有平台差异(
然而;至少在技术上可能的是,代码不应该是可移植的,并且规范("关于预期格式的协议"(恰好与代码设计的唯一平台的结果相匹配;因此,至少从技术上讲,代码是正确的。
可能会有很多改进,但我建议你检查一下谷物,而不是告诉所有的改进。它是一个广泛使用的序列化/反序列化库,因此考虑了许多关键点。
我的一些想法是:
-
由于对齐和端序,您的代码取决于程序运行的硬件。因此,序列化的数据是不可移植的,并且依赖于编译器。
-
char* encode(struct AMG_ANGLES *a)
函数返回char*
,可能是泄漏。为了防止出现此问题,请让std::unique_ptr<T>
决定其生存期,或者用类包装它。但要以某种方式去掉指针。 -
将序列化/反序列化操作模板化。否则,您可以为其他类型编写相同的函数。
template<typename T> char* encode( T* a ) // I leave signature as is, just to demonstrate { std::memcpy( b , &a , sizeof(T) ); return b; }
- 如果格式由您决定,最好选择可读的格式,而不是
JSON
、XML
等二进制存档
有人能用C给出替代设计吗?
"标准"方法是使用printf
和scanf
创建数据的ascii表示:
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <assert.h>
#include <float.h>
struct AMG_ANGLES {
float yaw;
float pitch;
float roll;
};
// declare a buffer at least this long to be sure encode works properly
#define AMG_ANGLES_BUFSIZE (
3 * ( /* 3 floats */
2 + /* digit and dot */
FLT_DECIMAL_DIG - 1 + /* digits after dot */
4 /* the 'e±dd' part */
)
+ 2 /* spaces */
+ 1 /* zero terminating character */
)
int encode(char *dest, size_t destsize, const struct AMG_ANGLES *a) {
return snprintf(dest, destsize, "%.*e %.*e %.*e",
FLT_DECIMAL_DIG - 1, a->yaw,
FLT_DECIMAL_DIG - 1, a->pitch,
FLT_DECIMAL_DIG - 1, a->roll);
// my pedantic self wants to add `assert(snprintf_ret < AMG_ANGLES_BUFSIZE);`
}
int decode(struct AMG_ANGLES *dest, const char *data) {
return sscanf(data, "%e %e %e", &dest->yaw, &dest->pitch, &dest->roll) == 3 ? 0 : -1;
}
int main() {
char buf[AMG_ANGLES_BUFSIZE];
const struct AMG_ANGLES a = { FLT_MIN, FLT_MAX, FLT_MIN };
encode(buf, sizeof(buf), &a);
struct AMG_ANGLES b;
const int decoderet = decode(&b, buf);
assert(decoderet == 0);
assert(b.yaw == FLT_MIN);
assert(b.pitch == FLT_MAX);
assert(b.roll == FLT_MIN);
}
然而,在裸金属嵌入式中,我尽量不使用scanf
——这是一个有一些依赖关系的大函数。因此,最好自己调用strtof
,但它需要一些思考:
int decode2(struct AMG_ANGLES *dest, const char *data) {
errno = 0;
char *endptr = NULL;
dest->yaw = strtof(data, &endptr);
if (errno != 0 || endptr == data) return -1;
if (*endptr != ' ') return -1;
data = endptr + 1;
dest->pitch = strtof(data, &endptr);
if (errno != 0 || endptr == data) return -1;
if (*endptr != ' ') return -1;
data = endptr + 1;
dest->roll = strtof(data, &endptr);
if (errno != 0 || endptr == data) return -1;
if (*endptr != ' ') return -1;
return 0;
}
或删除重复代码:
int decode2(struct AMG_ANGLES *dest, const char *data) {
// array of pointers to floats to fill
float * const dests[] = { &dest->yaw, &dest->pitch, &dest->roll };
const size_t dests_cnt = sizeof(dests)/sizeof(*dests);
errno = 0;
for (int i = 0; i < dests_cnt; ++i) {
char *endptr = NULL;
*dests[i] = strtof(data, &endptr);
if (errno != 0 || endptr == data) return -1;
// space separates numbers, last number is followed by zero
const char should_be_char = i != dests_cnt - 1 ? ' ' : ' ';
if (*endptr != should_be_char) return -1;
data = endptr + 1;
}
return 0;
}
我需要使用一些谷歌和重读chux的答案来正确回忆如何在printf
中使用FLT_DECIMAL_DIG
来打印浮动,这很可能是因为我很少使用浮动。
请记住,在使用memcpy时,不同的体系结构和编译器将以不同的方式应用填充和endianness。为了防止填充结构,可以使用GCC 提供的属性
__attribute__ ((packed))
尽管如此,这并不能保护您免受交替的endiannes的影响
使用memcpy进行序列化和反序列化的代码可能如下所示:
#include <memory>
#include <cstring>
struct __attribute__((packed)) AMG_ANGLES {
float yaw;
float pitch;
float roll;
};
//The buffer is expected to be the same size as the T
template<typename T>
int serialize(const T &data,const std::unique_ptr<char[]> &buffer){
std::memcpy(buffer.get(), &data, sizeof(T));
return sizeof(T);
}
//The buffer is expected to be the same size as the ReturnType
template<typename ReturnType>
ReturnType deserialize(const std::unique_ptr<char[]> &buffer){
ReturnType tmp;
std::memcpy(&tmp, buffer.get(), sizeof(ReturnType));
return tmp;
}
int main()
{
struct AMG_ANGLES angles = {1.2, 1.3, 1.0};
std::unique_ptr<char[]> buffer(new char[sizeof(struct AMG_ANGLES)]);
int size = serialize(angles, buffer);
struct AMG_ANGLES angles_serialized = deserialize<AMG_ANGLES>(buffer);
}
最好制作一些类,比如std::stringstream。。std::stringstream不适合保存二进制数据,但它的工作方式与您想要的相同。所以我可以举一些例子来处理std::stringstream。。
此代码仅实现序列化,但它还添加了用于反序列化的代码。
// C++11
template < typename T, typename decltype(std::declval<T>().to_string())* = nullptr>
std::ostream& operator<< (std::ostream& stream, T&& val)
{
auto str = val.to_string();
std::operator <<(stream, str);
return stream;
}
struct AMG_ANGLES {
float yaw;
float pitch;
float roll;
std::string to_string() const
{
std::stringstream stream;
stream << yaw << pitch << roll;
return stream.str();
}
};
void Test()
{
std::stringstream stream;
stream << 3 << "Hello world" << AMG_ANGLES{1.f, 2.f, 3.f };
}
- 如何在C++中序列化结构数据
- 如何使用boost::具有嵌套结构和最小代码更改的序列化
- 如何在 boost::asio 中将打包的结构作为消息传递?(无序列化)
- 使用指向 struc 的指针序列化结构
- C++ 提升 - 包含类层次结构对象的类的序列化
- 如何从平面缓冲区中反序列化联合结构的 void* 值的大小
- 将载体和cv :: mat to Disk的存储结构 - C 中的数据序列化
- 序列化没有"save"方法的大型结构
- 在 C# 中序列化这些值以在C++中作为已知结构正确读取时遇到问题
- 无序列图会创建一个零初始化结构吗?
- 如何在结构中序列化unique_ptr<char[]>
- 我的数据结构的最佳序列化方法
- 分段错误:C++中的结构序列化和 MPI 数据传输
- 跳过层次结构中的中间类,并使用boost ::序列化
- Boost::序列化存储结构时的堆栈溢出错误
- C 使用谷物序列化结构的静态阵列
- 具有3字节块的序列化C 结构
- 序列化结构以归档,并用字符串再次对其进行启用
- 序列化结构
- 序列化结构的STL映射