导航:首页 > 操作系统 > linux结构体对齐

linux结构体对齐

发布时间:2023-03-24 09:58:34

A. 如何高效的访问内存

影响内存访问速度的因素主要有:
1.内存带宽:每秒读写内存的数据量,由硬件配置决定。
2.CACHE高速缓冲:CPU与内存之间的缓冲器,当命中率比较高时能大大提供内存平均访问速度。
3.TLB转换旁视缓冲:系统虚拟地址向物理地址转换的高速查表机制,转换速度比普通转换机制要快。

我们能够优化的只有第2点和第3点。由于CACHE的小容量与SMP的同步竞争,如何最大限度的利用高速缓冲就是我们的明确优化突破口(以常用的数据结构体为例):
1.压缩结构体大小:针对CACHE的小容量。
2.对结构体进行对齐:针对内存地址读写特性与SMP上CACHE的同步竞争。
3.申请地址连续的内存空间:针对TLB的小容镇源量和CACHE命中。
4.其它优化:综合考虑多种因素

具体优化方法
1.压缩结构体大小
系统CACHE是有限的,并且容量很小,充分压缩结构体大小,使得CACHE能缓存更多的被访问数据,无非是提高内存平均访问速度的有效方法之一。
压缩结构体大小除了需要我们对应用逻辑做好更合理的设计,尽量去除不必要的字段,还有一些额外针对结构体本身的压缩方法。

1.1.对结构体字段进行合理的排列
由于结构体自身对齐的特性,具有同样字段的结构体,不同的字段排列顺序会产生不同大小的结构体。

大小:12字节
struct box_a
{
char a;
short b;
int c;

char d;
};
大小:8字节
struct box_b
{
char a;
char d;
short b;
int c;
};

1.2.利用位域
实际中,有些结构体字段并不需要那么大的存储空间,比如表示真假标记的flag字段只取两个值之一,0或1,此时用1个bit位即可,如果使用int类型的单一字段就大大的浪费了空间。
示例:tcp.h
struct tcphdr {
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)

__u16 res1:4,

doff:4,

fin:1,
syn:1,
rst:1,
psh:1,
ack:1,

urg:1,
ece:1,

cwr:1;

#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your defines"
#endif
__be16 window;

__sum16 check;

__be16 urg_ptr;
};

1.3.利用union
union结构体也是压缩结构体大小的方法之一,它允许我们在某些情况下能对结构体的多个字段进行合并或把小字节字段存放到大字节字段内。
示例:skbuff.h

struct sk_buff {



union {
__wsum csum;
struct {
__u16 csum_start;
__u16 csum_offset;
};
};

};

2.对结构体进行对御模态齐
对结构体进行对齐有两层意思,一是指对较小结构体进行机器字对齐,二是指对较大结构体进行CACHE LINE对齐。

2.1.对较小结构体进行机器字对齐
我们知道,对于现代计算机硬件来说,内存只能通过特定的对齐地址(比如按照机器字)进行访问。举个例子来说,比如在64位的机器上,不管我们是要读取第0个字节还是要读取第1个字节,在硬件上传输的信号都是一样的。因为它都会把地址0到地址7,这8个字节全部读到CPU,只是当我们是需要读取第0个字节时,丢掉后面7个字码拆节,当我们是需要读取第1个字节,丢掉第1个和后面6个字节。
当我们要读取的字节刚好落在两个机器字内时,就出现两次访问内存的情况,同时通过一些逻辑计算才能得到最终的结果。
因此,为了更好的提升性能,我们须尽量将结构体做到机器字(或倍数)对齐,而结构体中一些频繁访问的字段也尽量安排在机器字对齐的位置。

大小:12字节
struct box_c
{
char a;
char d;
short b;
int c;

int e;

};
大小:16字节
struct box_d
{
char a;

char d;

short b;
int c;

int e;

char padding[4];
};

上面表格右边的box_d结构体,通过增加一个填充字段padding将结构体大小增加到16字节,从而与机器字倍数对齐,这在我们申请连续的box_d结构体数组时,仍能保证数组内的每一个结构体都与机器字倍数对齐。
通过填充字段padding使得结构体大小与机器字倍数对齐是一种常见的做法,在linux内核源码里随处可见。

2.2.对较大结构体进行CACHE LINE对齐
我们知道,CACHE与内存交换的最小单位为CACHE LINE,一个CACHE LINE大小以64字节为例。当我们的结构体大小没有与64字节对齐时,一个结构体可能就要占用比原本需要更多的CACHE LINE。比如,把一个内存中没有64字节长的结构体缓存到CACHE时,即使该结构体本身长度或许没有还没有64字节,但由于其前后搭占在两条CACHE LINE上,那么对其进行淘汰时就会淘汰出去两条CACHE LINE。
这还不是最严重的问题,非CACHE LINE对齐结构体在SMP机器上容易引发名为错误共享的CACHE问题。比如,结构体T1和T2都没做CACHE LINE对齐,如果它们(T1后半部和T2前半部)在SMP机器上合占了同一条CACHE,如果CPU 0对结构体T1后半部做了修改则将导致CPU 1的CACHE LINE 1失效,同样,如果CPU 1对结构体T2前半部做了修改则也将导致CPU 0的CACHE LINE 1失效。如果CPU 0和CPU 1反复做相应的修改则导致的不良结果显而易见。本来逻辑上没有共享的结构体T1和T2,实际上却共享了CACHE LINE 1,这就是所谓的错误共享。
Linux源码里提供了利用GCC的__attribute__扩展属性定义的宏来做这种对齐处理,在文件/linux-2.6.xx/include/linux/cache.h内可以找到多个相类似的宏,比如:

点击(此处)折叠或打开
#define ____cacheline_aligned __attribute__((__aligned__(SMP_CACHE_BYTES)))

该宏可以用来修饰结构体字段,作用是强制该字段地址与CACHE LINE映射起始地址对齐。
看/linux-2.6.xx/drivers/net/e100.c内结构体nic的实现,三个____cacheline_aligned修饰字段,表示强制这些字段与CACHE LINE映射起始地址对齐。

点击(此处)折叠或打开
struct nic {
/* Begin: frequently used values: keep adjacent for cache effect */
u32 msg_enable ____cacheline_aligned;
/* 4字节空洞 */
struct net_device *netdev;
struct pci_dev *pdev;
/* 40字节空洞 */
struct rx *rxs ____cacheline_aligned;
struct rx *rx_to_use;
struct rx *rx_to_clean;
struct rfd blank_rfd;
enum ru_state ru_running;

/* 20字节空洞 */
spinlock_t cb_lock ____cacheline_aligned;

spinlock_t cmd_lock;

struct csr __iomem *csr;
enum scb_cmd_lo cuc_cmd;
unsigned int cbs_avail;

struct napi_struct napi;



}

回到前面的问题,如果我们对结构体T2的第一个字段加上____cacheline_aligned修饰,则该错误共享即可解决。

2.3.只读字段和读写字段隔离对齐
只读字段和读写字段隔离对齐的目的就是为了尽量保证那些只读字段和读写字段分别集中在CACHE的不同CACHE LINE中。由于只读字段几乎不需要进行更新,因而能在CACHE中得以稳定的缓存,减少由于混合有读写字段导致的对应CACHE LINE的频繁失效问题,以便提高效率;而读写字段相对集中在一起,这样也能保证当程序读写结构体时,污染的CACHE LINE条数也就相对的较少。

点击(此处)折叠或打开
typedef struct {
/* ro data */
size_t block_count; // number of total blocks
size_t meta_block_size; // sizeof per skb meta block
size_t data_block_size; // sizeof per skb data block

u8 *meta_base_addr; // base address of skb meta buffer
u8 *data_base_addr; // base address of skb data buffer
/* rw data */
size_t current_index ____cacheline_aligned; // index
} bc_buff, * bc_buff_t;

3.申请地址连续的内存空间
随着地址空间由32位转到64位,页内存管理的目录分级也越来越多,4级的目录地址转换也是一笔不小是开销。硬件产商为我们提供了TLB缓冲,加速虚拟地址到物理地址的换算。但是,毕竟TLB是有限,对地址连续的内存空间进行访问时,TLB能得到更多的命中,同时CACHE高速缓冲命中的几率也更大。
两段代码,实现同一功能,但第一种方法在实际使用中,内存读写效率就会相对较好,特别是在申请的内存很大时(未考虑malloc异常):

点击(此处)折叠或打开
方法一:

#define MAX 100

int i;

char *p;

struct box_d *box[MAX];

p = (char *)malloc(sizeof(struct box_d) * MAX);

for (i = 0; i < MAX; i ++)

{

box[i] = (struct box_d *)(p + sizeof(struct box_d) * i);

}

方法二:

#define MAX 100

int i;

struct box_d *box[MAX];

for (i = 0; i < MAX; i ++)

{

box[i] = (struct box_d *)malloc(sizeof(struct box_d));

}

另外,如果我们使用更大页面(比如2M或1G)的分页机制,同样能够提升性能;因为相比于原本每页4K大小的分页机制,应用程序申请同样大小的内存,大页面分页机制需要的页面数目更少,从而占用的TLB项目也更少,减少虚拟地址到物理地址的转换次数的同时,提高TLB的命中率,缩短每次转换所需要的时间。因为大多数操作系统在分配内存时候都需要按页对齐,所以大页面分页机制的缺点就是内存浪费相对比较严重。只有在物理内存足够充足的情况下,大页面分页机制才能够体现出优势。

4.其它优化
4.1.预读指令读内存
提前预取内存中数据到CACHE内,提高CACHE的命中率,加速内存读取速度,这是设计预读指令的主要目的。如果当前运算复杂度比较高,那么预取和运算就可同步进行,从而消除下一步内存访问的时延。相应的预读汇编指令有prefetch0、prefetch1、prefetch2、 prefetchnta。
预取指令只是给CPU一个提示,所以它可被CPU忽略,而且就算预取一段错误的地址也不会导致CPU异常。一般使用prefetchnta预取指令,因为它不会污染CACHE,它把每次取得的数据都存放到L2 CACHE的第一条CACHE LINE,而另外几条指令会替换CACHE中最近最少使用的CACHE LINE。

4.2.非暂时移动指令写内存
我们知道为了保证CACHE与内存之间的数据一致性,CPU对CACHE的写操作主要有两种方式同步到内存,写透式(Write Through)和写回式(Write-back)。不管哪种同步方式都是要消耗性能的,而在某些情况下,写CACHE是不必要的:
有哪些情况不需要写CACHE呢?比如做数据拷贝(高效memcpy函数实现)时,或者我们已经知道写的数据在最近一段时间内(或者永远)都不会再使用了,那么此时就可以不用写CACHE,让对应的CACHE LINE自动失效,以便缓存其它数据。这在某些特殊场景非常有用,相应的汇编指令有movntq、movntsd、movntss、movntps、movntpd、movntdq、movntdqa。
完整的利用预读指令和非暂时移动指令实现的高速内存拷贝函数:

点击(此处)折叠或打开

void X_aligned_memcpy_sse2(void* dest, const void* src, const unsigned long size_t)

{

__asm

{

mov esi, src; //src pointer

mov edi, dest; //dest pointer

mov ebx, size_t; //ebx is our counter

shr ebx, 7; //divide by 128 (8 * 128bit registers)

loop_:

prefetchnta 128[ESI]; //SSE2 prefetch

prefetchnta 160[ESI];

prefetchnta 192[ESI];

prefetchnta 224[ESI];

movdqa xmm0, 0[ESI]; //move data from src to registers

movdqa xmm1, 16[ESI];

movdqa xmm2, 32[ESI];

movdqa xmm3, 48[ESI];

movdqa xmm4, 64[ESI];

movdqa xmm5, 80[ESI];

movdqa xmm6, 96[ESI];

movdqa xmm7, 112[ESI];

movntdq 0[EDI], xmm0; //move data from registers to dest

movntdq 16[EDI], xmm1;

movntdq 32[EDI], xmm2;

movntdq 48[EDI], xmm3;

movntdq 64[EDI], xmm4;

movntdq 80[EDI], xmm5;

movntdq 96[EDI], xmm6;

movntdq 112[EDI], xmm7;

add esi, 128;

add edi, 128;

dec ebx;

jnz loop_; //loop please

loop__end:

}

}

总结
要高效的访问内存,必须充分利用系统CACHE的缓存功能,因为就目前来说,CACHE的访问速度比内存快太多了。具体优化方法有:
1.用设计上压缩结构体大小。
2.结构体尽量做到机器字(倍数)对齐。
3.结构体中频繁访问的字段尽量放在机器字对齐的位置。
4.频繁读写的多个结构体变量尽量同时申请,使得它们尽可能的分布在较小的线性空间范围内,这样可利用TLB缓冲。
5.当结构体比较大时,对结构体字段进行初始化或设置值时最好从第一个字段依次往后进行,这样可保证对内存的访问是顺序进行。
6.额外的优化可以采用非暂时移动指令(如movntdq)与预读指令(如prefetchnta)。
7.特殊情况可考虑利用多媒体指令SSE2、SSE4等。
当然,上面某些步骤之间存在冲突,比如压缩结构体和结构体对齐,这就需要实际综合考虑。

B. linux gcc下结构体对齐模数的问题

16的话竖斗,大概是因为lz的gcc默认是64位的编余行磨译带宴吧,如果是64位的话,可以看一下int的字节数,应该是8吧,那当然是以8为模了
我的是32位的,所以对齐模数默认是4,12没有问题

C. 结构体内存字节对齐规律

结构体是由一批数据组合而成的一盯返种新的数据类租则歼型。组成结构型数据的每个数据称为结构型数据的“成员”。
结构体指针的大小是8字节,结构体的大小是根据结构体成员的大小累积计算。

内存存取粒度 :处理器并不是按字节块来存取内存的.它一般会以 双字节 , 四字节 , 8字节 , 16字节 甚至 32字节 为单位来存取内存.我们将上述这些存取单位称为内存存取粒度。

结构体的大小是结构体中最大元素的整数倍,Student结构体中, char 的长度是 8 ,故 Student 结构体的大小为 32 ;

结构体嵌套 ,最终的大小并不是 Student2 结构体内最大的元素的倍数,而是 Student1 和 Student2 所有子元素里,最大的弊冲那一个的倍数,所以 Student2 的大小是8的倍数,按照上面的排列规则,得出结果是 53 ,但是结果必须是 8 的倍数,故最终结果是 56 。

当我们在定义结构体时,如果数据成员的定义顺序安排的不合理就有可能会导致多余内存空间的占用和浪费。 为了达到 最佳内存空间占用 ,系统在计算内存大小的时候,会对结构体内的元素进行重排, 按照从小到大的原则重新排列 ,下图是 Student1 重排后的结果。

D. 操作系统中的结构体对齐,字节对齐

1.平台原因:不是所有的硬件平台都能访问任意地址上的任何数据;某些硬件平台智能在某些地址处取特定类型的数据,否则抛出硬件异常。

2.性能原因:数据结构(尤其是栈)应该尽可能在自然边界上对齐,原因在于,为了访问未对镇知齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。

规则:

数据成员对齐规则:结构(struct)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

结构(struct)的整体对齐规则:自数据成员完成各自对齐后,结构本身也要对齐,对齐将按照#pragma pack指定的数值和结构最大数据成员长度中,比较小的那个进行

结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储

假设CPU要读取一个4字节大小的数据到寄存器中(假设内存读取粒度是4)

1.数据从0字节开始(内存对齐)

2.数据从1开始(内存不对齐)

当数据从0字节开始的时候,直接将0-3四个字节完全读取到寄存器,结算完成

当数据从1开始的时候,问题很复杂,首先将前4个字节读到寄存器,再次读取4-7字节的数据进寄存器,接着把0字节,567字节的数据剔除,最后合并1234字节的数据进寄存器,对一个内存未对齐的寄存器进行了这么多额外操作,大大降低了CPU的性能。

这还卖颤属于乐观情况(性能原因),还有平台的移植原因,因为只有部分CPU肯干,其他部分CPU遇到未对齐边界就直接罢工了

字节对齐:

1.第一个成员在与结构体变量偏移量(offset)为0的地址处。

2.其他成员变量要对齐到对齐数的整数倍的地址处

    (1)对齐数=对齐系数与该成员大小的较小值。

    (2)如果有宏定义 #pragma pack(n); 则它中的n 就是对齐系数。

    (3)VS中默认的对齐系数为8,linux中的默认为4.

3.结构体总大小为最大对齐数(每个成员变量除了第一个都有对齐数)的整数倍。

4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,

  结构体中旅败的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

E. 结构体内存对齐

1.什么是内存对齐?
2.为什么要做内存对齐?
3.结构体内存对齐规则
4.源码内存对齐算法

计算机内存都是以字节为单位划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8的倍数),这就是所谓的内存对齐。内存对齐是一种在计算机内存中排列数据(表现为变量的地址) 、访问数据(表现为CPU读取数据)的一种方式。
内存对齐包含了两种相互独立又相互关联的部分:基本数据对齐和结构体数据对齐 。

1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存袜携访问仅需要一次访问。

当我们定义一个 struct 的时候,它在内存中是怎么存储的?占用了多少字节的内存空间呢?

从打印结果看出一个问题,结构体中变量相同,只是顺序不同,结果影响结构体占用内存大小,这就是iOS中内存对齐

1.数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第
一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要
从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,
结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存
储。 min(当前开始的位置m,n) m=9 n=4 9 10 11 12
2.结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从
其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b
里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3.收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大
成员的整数倍.不足的要补⻬。

拿上面的两个结构体举例说明

sizeof 最终得到的结果是该数据类型占用空间的大小
class_getInstanceSize 获取类的实例对象所占用的内存大小
malloc_size 获取系统实际分配的内存大小

算法原理:k + 15 >> 4 << 4 ,其中 右移4 + 左移4相当于告信伏将后4位抹零,跟 k/16 * 16一样 ,是16字节对齐算法,小于16就成0了

对于一个对象来说,其真正的对齐方式 是 8字节对齐,8字节对齐已经足够满足对象的需求了
apple系统为了防止一切的容错,采用的是16字节对齐的内存,主要是因为采用8字节对齐时,两个坦明对象的内存会紧挨着,显得比较紧凑,而16字节比较宽松,利于苹果以后的扩展。

F. linux内存分配默认是多少字节对齐

VC和GCC默认的都是4字节对齐,编程中可以使用#pragma pack(n)指定对齐模数。出现以上差异的原因在于,VC和GCC中对于double类型的对齐方式不同。
Win32平台下的微软VC编译器在默认情况下采用如下的对齐规则: 任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。比如对于double类型(8字节),就要求该类型数据的地址总是8的倍数,而char类型数据(1字节)则可以从任何一个地址开始。
Linux下的GCC奉行的是另外一套规则:任何2字节大小(包括单字节吗?)的数据类型(比如short)的对齐模数是2,而其它所有超过2字节的数据类型(比如long,double)都以4为对齐模数。

复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。
struct{char a;double b;}
在VC中,因为结构中存在double和char,按照最长数据类型对齐,char只占1B,但是加上后面的double所占空间超过8B,所以char独占8B;而double占8B,一共16Byte。
在GCC中,double长度超过4字节,按照4字节对齐,原理同上,不过char占4字节,double占两个4字节,一共12Byte。

G. 内存对齐详解

1、什么是内存对齐
假设我们声明两个变量:

2、结构体内存对齐规则

结构体所占用的内存与其成员在结构体中的声明顺序有关,其成员的内存对齐规则如下:

(1)每个成员分别按自己的对齐字节数和PPB(指定的对齐字节数,32位机默认为4)两个字节数最小的那个对齐,这样可以最小化长度。

(2)复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。

(3)结构体对齐后的长度必须是成员中最大的对齐参数(PPB)的整数倍,这样在处理数组时可以保证每一项都边界对齐。

(4)计算结构体的内存大小时,应该列出每个成员的偏移地址,则其长度=最后一个成员的偏移地址+最后一个成员数的长度+最后一个成员的调整参数(考虑PPB)。

3、案例讲解:

————————————————
最后输出的结果为:8

4、注意的问题
(1)字节对齐取决于编译器;
(2)一定要注意PPB大小,PPB大小由pragam pack(n)指定;
(3)结构体占用的字节数要能够被PPB整除。
(4)总结出一个公式:结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即:
sizeof( struct ) = offsetof( last item ) + sizeof( last item ) +sizeof( trailing padding )

————————————————
原文链接: https://blog.csdn.net/SzMinglove/java/article/details/8143056

H. 结构体内存对齐

1、数据成员对齐规则: 结构( struct )(或联合( union )的)数据成员,第一个数据成员放在 offset 为 0 的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说数组,结构体等)的整数倍开始(如int为4字节,要从4的整数倍地址开始存储)

2、结构体作为成员: 如果一个结构体有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a 里存有struct b, b里有 char, int ,double等元素,那b 应该从8的整数倍开始存储。)

3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是内部最大成员的整数倍,不足要补齐

( 注 :如自定义类或继承关系 属性的多少变化而变化,如单纯的继承NSObject并且没有任何属性 打印为8 因为 有隐藏 isa 万物皆对象,而对象的本质为 继承 objc_object 的结构体 它就是对象模板)

拓 : 我们知道alloc 流程 最重要的三部曲
cls->instanceSize:计算需要开辟的内存空间大小(这里有一个算法为16字节对齐)
calloc:申请内存,返回地址指针
obj->initInstanceIsa:将 类 与 isa 关联

首先创建 3个结构体 这三个结构体内部参数类型 都一样 只不过 是顺序不一样

根据内存规则 我们画图来分析

分析: ofsize 0 开始 存储 char 类型占 一个 字节的 a ; b 要存储了 首先分析 自己自身多大 我是 double 类型的 我需要占 8 个字节 可是 如果从 a 后面 开始 不满足自身 8 的倍数 的地方 那我只能往下数 看来只能到 8的位置开始 容纳我的身体 【8 - 15 】 ; 该 c 出厂 存储了 我自身占用 4字节 我是 int 类型的 我只能先从16 开始 我的天 真幸运 正好符合我 自身4字节的倍数 我可以 存储了 【16- 19】 d 出场了 我 自身占 2个字节 得需要从20 位开始找 能不能满足 我的倍数的规则要求 20 除 2 可以的 我也可以放下了 【20 - 21】
Mystruct1 结构体 承载了 21+1 个字节 突然 大喇叭喊出规则:结构体的 总大小 ,也就是sizeof的结果, 必须是内部最大成员的整数倍,不足要补齐 :好的 那我里面最大的是 8 字节 那么 我现在 算出来 22 那么 补齐就好了嘛 不要激动 心里默念: 二 八 16, 不满足 ; 三八 24 满足啦 结束了。所以 Mystruct1 占用 24个字节空间

分析: 从 0 的位置开始存 自身是double 类型的占用8个字节的 b【0-7】
a 是 占用一个字节的char类型 ,从 8的位置 满足自身1字节的倍数 所以 存储在8的位置【8】,c来了 首先 我自己是 int类型 我占用 4个字节 我只能从 9的位置开始存 可是 不满足 规则 必须是自己字节数的倍数,那我只能往下数了 9.10.11.12 咦!12是我的位置 那就从12开始【12-15】, d来了 首先想自己是 short类型的占用2个字节 我只能先从 16 的位置看 咦!这么巧 正好符合规则 是我的倍数 存这没毛病
Mystruct2 结构体 承载了 17+1 个字节 ,大喇叭又开始广播了:结构体的 总大小 ,也就是sizeof的结果, 必须是内部最大成员的整数倍,不足要补齐 ,好的村长 我补齐: 结构体最大的是 double占 用8个字节 我算出来18。 2*8 = 16 不满足 3 *8 = 24 村长 我满足了。所以 Mystruct2 占用 24个字节空间

分析:从0 开始 存入一个 char类型的a d 来了 首先分析自己 是short类型 占用2个字节 那我如果从1 开始 那不是我的倍数 不满足规则。所以 往下看 2 好像 是我字节的倍数 ,好的存入 【2-3】 c来了我是 int类型的 占4字节 我只能从4开始存储 好像也满足 是我的自身4字节的倍数 ,好的存入【4-7】,b来了 嗯我是double类型 我自身占用8字节 我只能从 8号位置 开始存储 好像 8也是我的倍数 好的 存入【8-15】
`Mystruct3 结构体 承载了 15+1个字节,行了大喇叭别广播了 我知道了 我内部成员最大 的为8字节 必须是 8字节的整数倍 不足要补齐。
2*8 = 16 我满足了 ,所以 Mystruct3占用 16个字节

我们已经对结构体内存很明晰了 那么来个嵌套的

上面我们分析了 Mystruct3 占用 16 个字节 那么 Mystruct4占用多少呢

分析:
a 是占用一个字节的char类型 放在【0】
b是 占4字节的int类型 找到最近的 自身的占用字节的倍数的位置 为【4-7】
sturct3 是一个占用 16字节 的结构体 我们首先分析它最大的成员占多少内存 这里 为double 类型 占8个字节 所以应该 从8 的倍数开始存放第一个元素 上面的b占了【4-7】故只能从8往后数 正好8的位置 是 sturct3里最大元素的倍数 故第一个元素位置为【8】第二个元素为short类型占2个字节 所以需要从8 往后数正好满足自身的倍数 也就是【10 11】 第三个元素为double类型 占8个字节 所以需要从11 往后数满足8 的倍数 也就是【16 - 23】 因为 sturct3 是个结构体 也满足内存对齐原则 也就是 最大元素的倍数 sturct3占了 16字节 正好满足 对齐原则 a 占1 个 空白占3 b 占4 个 加在一起占 24个
sturct4 内存对齐 为 最大成员的倍数 最大成员 为struct3 里double 8 字节 所以
又是大喇叭所说的 内部成员最大字节 的倍数
所以 sizeof(Mysturct4) 最终 为 24
注意:

I. linux下怎么设置内存对齐

内存对齐是有规则的:
a. 基本类型:所有的基本类型都有相应的对齐参数,编译器在编译时,会用全局的对齐参数和当前类型的对齐参数中较小的一个进行对齐。比如,编译时指定按8bytes对齐(用#pragma pack(8)实现之),可是由于一个char变量的大小为一个byte,所以最后还是按1byte对齐。

b. 复合类型:复合类型的对齐原则,就是取其成员变量数据类型的字节数的最大者和在编译时指定的对齐数两者之间较小的字节数进行对齐。如果没有用诸如#pragma pack指定全局对齐数,则该复合类型的对齐数就是其成员变量数据类型字节数之最大者。

阅读全文

与linux结构体对齐相关的资料

热点内容
服务器如何开权限设置密码 浏览:270
申请资料填写小程序源码 浏览:176
服务器怎么私信别人 浏览:993
phpxdebug关闭 浏览:689
能看的网址大家都懂的 浏览:287
安卓程序员开发经验 浏览:660
服务器返回超时是怎么回事 浏览:653
网关后dns服务器有什么用 浏览:607
line连接不上服务器怎么办 浏览:862
付费电影免费下载 浏览:607
反编译flv 浏览:939
python替换指定位置 浏览:337
有名txt下载宝书网 浏览:197
飞机app安卓怎么注册 浏览:801
电影院默认场区什么意思 浏览:657
韩国 禁忌 姐弟爱唯美电影 浏览:82
极限压缩方法 浏览:263
小苮儿清读210部有声下载网盘 浏览:382
qt程序编译成功后 浏览:616
富二代装穷的电影有哪些 浏览:394