2.结构体内存对齐问题
例子1:
struct S1
{
char c1;
int i;
char c2;
};
#include<stdio.h>
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
输出:
12
例子2:
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S2));
return 0;
}
输出:
8
对齐规则 :
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。这里有两个点需要说明:
偏移量: 偏移量就是程序的逻辑地址与段首的差值。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
VS中默认的值为8
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
实际例子1:
struct S1
{
char c1;
int i;
char c2;
};
实际例子2:
struct S2
{
char c1;
char c2;
int i;
};
切记:最后一步一定要检查结构体大小是否为最大对齐数的整数倍!
————————————————
版权声明:本文为CSDN博主「花想云」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gllll_yu/article/details/127814428
●结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
●未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐。)
c++11以后引入两个关键字 alignas (opens new window)与 alignof (opens new window)。其中alignof
可以计算出类型的最大对齐数,alignas
可以指定结构体的对齐方式。
但是alignas
在某些情况下是不能使用的,具体见下面的例子:
// alignas 生效的情况
struct Info {
uint8_t a;
uint16_t b;
uint8_t c;
};
std::cout << sizeof(Info) << std::endl; // 6 2 + 2 + 2
std::cout << alignof(Info) << std::endl; // 2
/*
这段代码输出的6和2+2+2是不同的,因为它们分别表示结构体Info的字节数和对齐方式。
首先,sizeof(Info)返回的是结构体Info占用的字节数,包括成员变量a、b和c所占用的空间。在这个例子中,由于a占用1个字节,b占用2个字节,c占用1个字节,因此Info总共占用了6个字节。
其次,alignof(Info)返回的是结构体Info对齐方式的大小,也就是说,它表示一个整数类型的变量至少需要占用多少个字节才能满足对齐要求。在这个例子中,由于a占用1个字节,b占用2个字节,c占用1个字节,因此它们的对齐方式应该是2个字节对齐。因此,alignof(Info)的值为2。
*/
struct alignas(4) Info2 {
uint8_t a;
uint16_t b;
uint8_t c;
};
std::cout << sizeof(Info2) << std::endl; // 8 4 + 4
std::cout << alignof(Info2) << std::endl; // 4
alignas
将内存对齐调整为4个字节。所以sizeof(Info2)
的值变为了8。
// alignas 失效的情况
struct Info {
uint8_t a;
uint32_t b;
uint8_t c;
};
std::cout << sizeof(Info) << std::endl; // 12 4 + 4 + 4
std::cout << alignof(Info) << std::endl; // 4
struct alignas(2) Info2 {
uint8_t a;
uint32_t b;
uint8_t c;
};
std::cout << sizeof(Info2) << std::endl; // 12 4 + 4 + 4
std::cout << alignof(Info2) << std::endl; // 4
若alignas
小于自然对齐的最小单位,则被忽略。
●如果想使用单字节对齐的方式,使用alignas
是无效的。应该使用#pragma pack(push,1)
或者使用__attribute__((packed))
。
#if defined(__GNUC__) || defined(__GNUG__)
#define ONEBYTE_ALIGN __attribute__((packed))
#elif defined(_MSC_VER)
#define ONEBYTE_ALIGN
#pragma pack(push,1)
#endif
struct Info {
uint8_t a;
uint32_t b;
uint8_t c;
} ONEBYTE_ALIGN;
#if defined(__GNUC__) || defined(__GNUG__)
#undef ONEBYTE_ALIGN
#elif defined(_MSC_VER)
#pragma pack(pop)
#undef ONEBYTE_ALIGN
#endif
std::cout << sizeof(Info) << std::endl; // 6 1 + 4 + 1
std::cout << alignof(Info) << std::endl; // 1
●确定结构体中每个元素大小可以通过下面这种方法:
#if defined(__GNUC__) || defined(__GNUG__)
#define ONEBYTE_ALIGN __attribute__((packed))
#elif defined(_MSC_VER)
#define ONEBYTE_ALIGN
#pragma pack(push,1)
#endif
/**
* 0 1 3 6 8 9 15
* +-+---+-----+---+-+-------------+
* | | | | | | |
* |a| b | c | d |e| pad |
* | | | | | | |
* +-+---+-----+---+-+-------------+
*/
struct Info {
uint16_t a : 1;
uint16_t b : 2;
uint16_t c : 3;
uint16_t d : 2;
uint16_t e : 1;
uint16_t pad : 7;
} ONEBYTE_ALIGN;
#if defined(__GNUC__) || defined(__GNUG__)
#undef ONEBYTE_ALIGN
#elif defined(_MSC_VER)
#pragma pack(pop)
#undef ONEBYTE_ALIGN
#endif
std::cout << sizeof(Info) << std::endl; // 2
std::cout << alignof(Info) << std::endl; // 1
这种处理方式是alignas
处理不了的
参考资料:
阿秀
1.什么是内存对齐??
内存对齐可以提高CPU的内存访问效率,因为CPU在读取内存时是按照一块一块的方式进行读取,每块的大小由内存读取粒度确定,通常为2、4、8或16个字节。
1.硬件存储和读取
一个内存是由若干个黑色的内存颗粒构成的。每一个内存颗粒叫做一个chip。每个chip内部,是由8个bank组成的。构造如下图:
每一个bank是一个二维平面上的矩阵。矩阵中每一个元素中都是保存了1个字节,也就是8个bit。
那么对于我们在应用程序中内存地址连续的8个字节,例如0x0000-0x0007,是从位于bank上的呢?直观感觉,应该是在第一个bank上吗?其实不是的,程序员视角看起来连续的0x0000-0x0007,实际上位于8个bank中,每一个bank只保存了一个字节。在物理上,他们并不连续。下图很好地阐述了实际情况。
结论:一个int会被存放在连续的bank上,而不是分别存在四个颗粒上。
2.对齐规则
1.每个特定平台上的编译器都有自己的默认“对齐系数”#pragma pack(show)可以查看
2.有效对齐值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位。
3.结构体第一个成员变量的偏移量(offset)为0,以后每个数据成员的起始位置要从自身大小的整数倍开始存储
4.结构体的总大小为:若没有设定对齐字节数,则最大成员为对齐字节数。若有设定对齐字节数,则对齐字节数为:min(最大成员,设定的对齐字节数)的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
所占用字节数分别为:40 36 40
参考资料来源:
程序员老吴