我必须将struct Data数组写入硬盘:
struct Data {
char cmember;
/* padding bytes */
int imember;
};
AFAIK,大多数编译器会在cmember和imember成员之间添加一些填充字节,但是我想保存只存档实际数据(没有填充)。
我有下一个用于保存数据阵列的代码(在缓冲区而不是文件中以简化):
bool saveData(Data* data, int dataLen, char* targetBuff, int buffLen)
{
int actualLen = sizeof(char) + sizeof(int); // this code force us to know internal
// representation of Data structure
int actualTotalLen = dataLen * actualLen;
if(actualTotalLen > buffLen) {
return false;
}
for(int i = 0; i < dataLen; i++) {
memcpy(targetBuff, &data[i].cmember, sizeof(char));
targetBuff += sizeof(char);
memcpy(targetBuff, &data[i].imember, sizeof(int));
targetBuff += sizeof(int);
}
return true;
}
如您所见,我使用代码
int actualLen = sizeof(char) + sizeof(int)
计算Data struct的实际大小。有没有替代方案? (类似于int actualLen = actualSizeof(Data)
)
P.S。这是合成的例子,但我想你理解我的问题...
答案 0 :(得分:8)
一次只保存一个结构的每个成员。如果你重载&lt;&lt;要将变量写入文件,您可以
myfile << mystruct.member1 << mystruct.member2;
然后你甚至可以重载&lt;&lt;获取整个结构,并在结构的运算符&lt;&lt;中进行,所以最后你有:
myfile << mystruct;
导致保存代码如下:
myfile << count;
for (int i = 0; i < count; ++i)
myFile << data[i];
IMO所有那些摆弄内存地址和memcpy的东西,当你可以这样做时,太麻烦了。这种通用技术称为序列化 - 谷歌更多,它是一个发展良好的领域。
答案 1 :(得分:3)
你必须收拾你的结构。
执行此操作的方法会根据您使用的编译器而改变。
对于visual c ++:
#pragma pack(push)
#pragma pack(1)
struct PackedStruct {
/* members */
};
#pragma pack(pop)
这将告诉编译器不填充结构中的成员并将pack参数恢复为其初始值。请注意,这会影响性能。如果在关键代码中使用此结构,则可能需要将解压缩的结构复制到打包结构中。
此外,抵制诱惑使用完全禁用填充的命令行参数,这将极大地影响性能。
答案 2 :(得分:2)
这个问题没有一个简单的解决方案。您通常可以创建单独的结构并告诉编译器将它们打包紧密,例如:
/* GNU has attributes */
struct PackedData {
char cmember;
int imember;
} __attribute__((packed));
或:
/* MSVC has headers and #pragmas */
#include <pshpack1.h>
struct PackedData {
char cmember;
int imember;
};
#include <poppack.h>
然后你必须编写将解压缩的结构转换为压缩结构的代码,反之亦然。如果您使用的是C ++,则可以创建基于结构类型的模板辅助函数,然后对其进行特化:
template <typename T>
std::ostream& encode_to_stream(std::ostream& os, T const& object) {
return os.write((char const*)&object, sizeof(object));
}
template <typename T>
std::istream& decode_from_stream(std::istream& is, T& object) {
return is.read((char*)&object, sizeof(object));
}
template<>
std::ostream& encode_to_stream<Data>(std::ostream& os, Data const& object) {
encode_to_stream<char>(os, object.cmember);
encode_to_stream<int>(os, object.imember);
return os;
}
template <>
std::istream& decode_from_stream<Data>(std::istream& is, Data& object) {
decode_from_stream<char>(is, object.cmember);
decode_from_stream<int>(is, object.imember);
return is;
}
奖励是默认值将读取和写入POD对象,包括填充。您可以根据需要进行专门设计以优化存储。但是,您可能还想考虑endianess,版本控制和其他二进制存储问题。简单地编写一个包装存储的归档类并提供基元序列化和反序列化的方法,然后根据需要进行专门化的开放式方法可能是谨慎的做法:
class Archive {
protected:
typedef unsigned char byte;
void writeBytes(byte const* byte_ptr, std::size_t byte_size) {
m_fstream.write((char const*)byte_ptr, byte_size);
}
public:
template <typename T>
void writePOD(T const& pod) {
writeBytes((byte const*)&pod, sizeof(pod));
}
// Users are required to specialize this to use it. If it is used
// for a type that it is not specialized for, a link error will occur.
template <typename T> void serializeObject(T const& obj);
};
template<>
void Archive::serializeObject<Data>(Data const& obj) {
writePOD(cmember);
writePOD(imember);
}
这是我经常在一系列扰动之后结束的方法。它可以很好地扩展而不需要继承,并且可以根据需要灵活地更改底层数据存储格式。您甚至可以使writePOD
专门针对不同的底层数据类型执行不同的操作,例如确保以网络顺序或其他方式编写多字节整数。
答案 3 :(得分:2)
我会说你实际上在寻找序列化。
有许多序列化框架,但我个人更喜欢Google Protocol Buffers而不是Boost.Serialization和其他方法。
协议缓冲区具有版本控制和二进制/人类可读输出。
如果您担心尺寸,您始终可以压缩数据。有像LZW这样的闪电般快速压缩算法,它提供了良好的比率速度/压缩,例如。
答案 4 :(得分:1)
查看编译器的#pragma pack
宏。有些编译器使用#pragma options align=packed
或类似的东西。
答案 5 :(得分:1)
如您所见,我使用代码计算Data struct的实际大小:int actualLen = sizeof(char)+ sizeof(int)。有没有替代方案?
不,不是标准的C ++。
答案 6 :(得分:1)
IIUC,您试图复制结构成员的值而不是整个结构并将其存储到磁盘。你的方法对我来说很好。我不同意那些暗示#pragma pack
的内容 - 因为它们会帮助你在运行时获得一个打包的结构。
很少注意到:
sizeof(char)== 1,总是按照定义
使用offsetof()
宏
Data
实例化targetBuff
对象(即通过投射) - 这是你遇到对齐问题和旅行时。相反,在编写缓冲区的同时复制成员,你不应该有问题答案 7 :(得分:1)
如果您不想使用pragma pack,请尝试手动重新排序变量, 喜欢
struct Data {
int imember;
char cmember;
};
答案 8 :(得分:1)
不知道这是否会对你有所帮助,但我习惯于订购我打算写入文件(或通过网络发送)的结构成员,以便它们尽可能少地填充。这是完成的,我首先使用最广泛的数据类型和最严格的对齐:
•指针首先
•double
•long long
•long
•float
•int
•short
•char
•位域最后
编译器添加的任何填充都将出现在struct数据的末尾。
换句话说,您可以通过重新排序结构成员来消除填充(如果可能)来简化您的问题:
struct Data
{
int imember;
char cmember;
/* padding bytes here */
};
显然,如果您无法对结构成员进行重新排序(因为它由第三方API使用,或者因为您需要初始成员具有特定的数据类型),这将无法解决您的问题。
答案 9 :(得分:1)
你说@Coincoin不能打包。如果由于某种原因你只需要尺寸,这里是脏解决方案
#define STRUCT_ELEMENTS char cmember;/* padding bytes */ int imember;
typedef struct
{
STRUCT_ELEMENTS
}paddedData;
#pragma pack(push)
#pragma pack(1)
typedef struct
{
STRUCT_ELEMENTS
}packedData;
#pragma pop
现在你有两个尺寸;
sizeof(packedData);
sizeof(paddedData);
只有我能想到你无法打包的原因才将其与其他程序联系起来。在这种情况下,您需要打包结构,然后在使用外部程序时取消选择。
答案 10 :(得分:0)
不,在语言范围内无法获取此信息。解决方案的一种方法是使用该语言的某些功能间接定义您的数据类 - 它可以像宏和预处理器一样老式,或者像元组模板一样新颖。你需要一些能让你系统地迭代类成员的东西。
这是一种基于宏的方法:
#undef Data_MEMBERS
#define Data_MEMBERS(Data_OP) \
Data_OP(c, char) \
Data_OP(i, int)
#undef Data_CLASS_DEFINITION
#define Data_CLASS_DEFINITION(name, type) \
type name##member;
struct Data {
Data_MEMBERS(Data_CLASS_DEFINITION)
};
#define Data_SERIAL_SIZER(name, type) \
sizeof(type) +
#define Data_Serial_Size \
(Data_MEMBERS(Data_SERIAL_SIZER) 0)
等等。
答案 11 :(得分:0)
如果你可以重写结构定义,你可以尝试使用字段说明符去除漏洞,如下所示:
struct Data {
char cmember : 1;
int imember : 4;
};
可悲的是,这并不能保证在cmember启动后它仍然不会放置4个字节。但是许多编译器都会得到这个想法并且无论如何都会这样做。
其他替代方案:
按大小重新排列成员(最大的第一个)。这是一个最小化漏洞的老式嵌入式技巧。
请改用Ada。
代码
type Data is record
cmember : character;
imember : integer;
end record;
for Data use record
cmember at 0 range 0..7;
imemeber at 1 range 0..31;
end record;
完全符合你的要求。