有没有办法防止组合 class 的填充
Is there a way to prevent padding of a composed class
假设一台 64 位机器:
如果我从 class A:
开始
struct A
{
int* a1; //8
int* a2; //8
uint16_t a3; //2
uint16_t a4; //2
uint32_t a5; //4
uint32_t a6; //4
uint32_t a7; //4
};
现在所有成员都正确对齐了,A
的大小是32字节,从a5
到a1
的偏移量是20字节
现在,如果我尝试按如下方式重构它:
struct A_part1
{
int* a1; //8
int* a2; //8
uint16_t a3; //2
uint16_t a4; //2
};
struct A_new
{
A_part1 a1;
uint32_t a5; //4
uint32_t a6; //4
uint32_t a7; //4
};
现在 A_new
的大小是 40 字节,因为 A_part1
被填充到 24 字节并且 A_new
随后从 36 填充到 40 字节。
大概编译器试图确保连续的 A_part1
对齐。
如果我知道我只会在 A
中使用 A_part1
,这是我必须在 a_part
1 中使用 pragma
包的唯一选择确保 A_new
将具有与原始 A
?
相同的特征
你是对的:标准 C++ 中没有办法指定 struct
的填充布局,因此你只能使用 compiler-specific 控制机制,如 #pragma pack
。
If I know that I will only ever use A_part1 in A, is the only option I
have to use a pragma pack on a_part1 here to ensure that A_new will
have the same characteristics as the original A?
没有。 pragma 不是指定布局和填充的唯一方法。
在我曾经工作过的一个嵌入式软件中,我们找到了几种解决 pragma 问题的方法。
C 和 C++ 均未提供内存布局语义。
但是一种易于理解的技术(至少在逐个字段的基础上)是明确编码字节驻留在 class 或结构中的位置。通过重构和添加小字节移动方法,工作量大大减少。
注意:七个字段很小,因此 'easy'。五十田很累。一百个字段是"lets write some code to write some code."你的容忍度会有所不同。
此技术明确地将字节 byte-by-byte 移动到 class 数据中。优点:1)没有填充(除非你想要它)。 2) 对编译器选项变化不敏感。 3) 便携。
小例子(编译,但未测试)(注意:任意字节序选择,我们使用常规endian-transformation)
class A
{
// int* a1; //8 0..7
// int* a2; //8 8..15
// uint16_t a3; //2 16..17
// uint16_t a4; //2 18..19
//
// uint32_t a5; //4 etc
// uint32_t a6; //4
// uint32_t a7; //4
// ...
// modify field a3
void a3(uint16_t val) {
// if needed, correct val for destination endianess ------vvv
data[Oa3+0] = static_cast<uint8_t>((val >> 0) & 0xff); // LSB
data[Oa3+1] = static_cast<uint8_t>((val >> 8) & 0xff); // MSB
}
// access field a3
uint16_t a3() {
// if needed, correct val for endianess ---------------------vvv
uint16_t val = static_cast<uint16_t>((data[Oa3+0] << 0) + // LSB
(data[Oa3+8] << 8)); // MSB
return (val);
}
// continue for each field
private:
enum {
Oa1 = 0, // Offset a1
Oa2 = 8,
Oa3 = 16
//... etc
};
uint8_t data[(8+8+2+2+(3*4))];
};
假设一台 64 位机器:
如果我从 class A:
开始struct A
{
int* a1; //8
int* a2; //8
uint16_t a3; //2
uint16_t a4; //2
uint32_t a5; //4
uint32_t a6; //4
uint32_t a7; //4
};
现在所有成员都正确对齐了,A
的大小是32字节,从a5
到a1
的偏移量是20字节
现在,如果我尝试按如下方式重构它:
struct A_part1
{
int* a1; //8
int* a2; //8
uint16_t a3; //2
uint16_t a4; //2
};
struct A_new
{
A_part1 a1;
uint32_t a5; //4
uint32_t a6; //4
uint32_t a7; //4
};
现在 A_new
的大小是 40 字节,因为 A_part1
被填充到 24 字节并且 A_new
随后从 36 填充到 40 字节。
大概编译器试图确保连续的 A_part1
对齐。
如果我知道我只会在 A
中使用 A_part1
,这是我必须在 a_part
1 中使用 pragma
包的唯一选择确保 A_new
将具有与原始 A
?
你是对的:标准 C++ 中没有办法指定 struct
的填充布局,因此你只能使用 compiler-specific 控制机制,如 #pragma pack
。
If I know that I will only ever use A_part1 in A, is the only option I have to use a pragma pack on a_part1 here to ensure that A_new will have the same characteristics as the original A?
没有。 pragma 不是指定布局和填充的唯一方法。
在我曾经工作过的一个嵌入式软件中,我们找到了几种解决 pragma 问题的方法。
C 和 C++ 均未提供内存布局语义。
但是一种易于理解的技术(至少在逐个字段的基础上)是明确编码字节驻留在 class 或结构中的位置。通过重构和添加小字节移动方法,工作量大大减少。
注意:七个字段很小,因此 'easy'。五十田很累。一百个字段是"lets write some code to write some code."你的容忍度会有所不同。
此技术明确地将字节 byte-by-byte 移动到 class 数据中。优点:1)没有填充(除非你想要它)。 2) 对编译器选项变化不敏感。 3) 便携。
小例子(编译,但未测试)(注意:任意字节序选择,我们使用常规endian-transformation)
class A
{
// int* a1; //8 0..7
// int* a2; //8 8..15
// uint16_t a3; //2 16..17
// uint16_t a4; //2 18..19
//
// uint32_t a5; //4 etc
// uint32_t a6; //4
// uint32_t a7; //4
// ...
// modify field a3
void a3(uint16_t val) {
// if needed, correct val for destination endianess ------vvv
data[Oa3+0] = static_cast<uint8_t>((val >> 0) & 0xff); // LSB
data[Oa3+1] = static_cast<uint8_t>((val >> 8) & 0xff); // MSB
}
// access field a3
uint16_t a3() {
// if needed, correct val for endianess ---------------------vvv
uint16_t val = static_cast<uint16_t>((data[Oa3+0] << 0) + // LSB
(data[Oa3+8] << 8)); // MSB
return (val);
}
// continue for each field
private:
enum {
Oa1 = 0, // Offset a1
Oa2 = 8,
Oa3 = 16
//... etc
};
uint8_t data[(8+8+2+2+(3*4))];
};