单字、双字、四字在内存的自然边界上,所以不需要再内存中对齐。
自然边界是指:偶数地址,可以被4整除的地址、可以被8整除的地址;
编译器会默认将结构、栈中的成员数据进行对齐。
这是因为如果未对齐,比如对一个字或者双字操作数跨越了4字节边界,那么就需要两个总线周期来访问内存。从时间角度来看效率比较差。
所以为了提高数据操作的效率,编译器尽量将成员数据进行自然边界对齐。不过这样做会牺牲一定的空间。导致整个结构的尺寸变大。
虽然牺牲了空间,但是提升了性能,所以还是值得的。
有什么办法可以既提高性能,而且节约空间?
就是写结构体的时候尽量使成员自然对齐,而避免编译器进行自动对齐;
例如:
struct TestStruct1{ //不自然对齐
char c1; //1字节
short s; //4字节
char c2; //1字节
int i; //4字节
}
struct TestStruct2{
char ch1; //1字节
char ch2; //1字节
short s; //4字节
int i; //4字节
}
对于32位的处理器,其一次可以读取32位二进制数即4个字节大小的数据。
例如一个结构体如下:
struct A{
char a;
int i;
};
struct A aa;
变量a占用1个字节的空间,变量i占用4个字节的空间;所以不按照4字节对齐的话,结构体变量aa占用5个字节空间;
假设变量a的地址是0x00,变量i的地址是0x01(数据所占用4个字节的空间的地址范围为0x01~0x04);
那么在CPU读取数据的时候,用一个读周期读取0x00到0x03地址范围内的数据。其中0x00地址就是变量a;
为了读取变量i,就要再用一个读周期,读取0x04~0x07地址范围内的数据。
然后将两次读取的结果进行拼接,0x01~0x03与0x04进行拼接,才完整读取了变量i;可以发现这样做的时间效率较低。
因为32位CPU一次可以读取4字节大小的数据,那么采用4字节对齐的方式读取数据,读取变量i效率更高。
按照4字节方式对齐的方式存储数据的话,变量a本身只占用1个字节空间,但是实际上占用4个空间0x00~0x03;其中0x01~0x03是无效的数据;
然后变量i本身占用4个字节空间,对应地址空间0x04~0x07;
那么只要读一次0x00地址开始的4个字节,再读一次0x04开始的4个字节,就可以把变量a和i完整读取出来了。当然存在一个问题就是浪费了0x01~0x03的空间;
定义结构体时有什么要注意的吗?
尽量把4字节整数倍大小的变量放前面,然后把最小大小的变量放后面(1字节);
例如:
struct A
{
uint32_t i;
char a;
char b;
}
struct B
{
char a;
uint32_t i;
char b;
}
满足4字节对齐的情况下:(4字节大小的变量的地址都是4的整数倍,1字节大小的变量的地址都是1的整数倍)
结构体A数据的空间分布:0x00~0x03属于变量i;0x04~0x07空间内:0x04属于变量a,0x05属于变量b,其余空间被浪费掉;总共占用8个字节空间
结构体B数据的空间分布:0x04~0x07空间内:0x00属于变量a,其中0x01~0x03被浪费掉;0x04~0x07属于变量i;0x08~0x11空间内:0x08属于变量b,其余空间被浪费掉;总共占用12个字节空间;
我们可以发现使用结构体B的书写顺序,会导致数据空间的进一步浪费。本来满足4字节对齐的情况,就是为了用空间换时间效率的方式。结构体成员变量书写顺序的不同,也会很大影响空间的效率和时间的效率;
进一步解释一下结构体A的读取过程,第一个读取周期内会读取0x00~0x03地址空间的数据,第二个读取周期内会读取0x04~0x07地址空间内的数据。并且两个读取周期就读完整个结构体的成员变量,而且不需要做两次读取结果数据的拼接来获取完整成员变量这一动作。
兼顾了时间和空间效率;
4字节对齐的解释:https://blog.csdn.net/windows260/article/details/78370699