联合体和结构体字节对齐

时间:2022-09-05 21:05:35
对于联合体/结构体:
1.                   各元素的首地址(实践中可用偏移量)需被元素大小整除;
2.                   结构体大小(各元素大小之和)或联合体大小(最大元素的大小)被结构体对齐字节数(复合体元素要拆开来辨识结构体对齐字节数)整除
结构体对齐字节数 = Min(8,max(结构体各元素大小));
 
重要:默认8字节,可用pack()来改变,如pack(4),则对齐字节数为4,并且各元素偏移量被
min(4,元素的对齐字节数)整除,否则扩充。
3.                   按1,2规则从最里层计算复合体元素,从里到外,直至计算最外层结构体或联合体
举例1:
struct a
{
char no[10]; //对齐1字节
int p; //对齐4字节
long int pp; //对齐4字节
unsigned int ppp; //对齐4字节
char x;         //对齐1字节
float y;        //对齐4字节
double h;       //对齐8字节
}xy;
char no[10]:
偏移量=0,能被sizeof(char)整除,注意是基本类型而不是数组,并且数组是连续存储的
int p:
偏移量=0 + sizeof(char)x10 =10,不能被sizeof(int) = 4整除,需补充字节直到能被4整除,故偏移量变为12;char no[10]由10字节变为12字节,即sizeof(char no[10]) = 12;
long int pp:
偏移量=12 + sizeof(int) = 16,能被sizeof(long int) = 4整除;
unsigned int ppp:
偏移量=16 + sizeof(long int) = 20,能被sizeof(unsigned int) = 4整除;
char x:
偏移量=20 + sizeof(unsigned int) = 24,能被sizeof(char)=1整除;
float y:
偏移量=24 + sizeof(char) = 25,不能被sizeof(float)=4整除,偏移量变为28;而sizeof(char x) = 4;
double h:
偏移量=28 + sizeof(float) = 32,能被sizeof(double)=8整除
故,sizeof(a) = sizeof(char no[10]) + sizeof(int) + sizeof(long int) + sizeof(unsigned int) + sizeof(char x) + sizeof(float) + sizeof(double)= 12 + 4 + 4 + 4 + 4 + 4+ 8 = 40,能被结构体对齐字节数8整除,即求得结构体的大小为40。
结构体对齐字节数=min(8, max(1,4,4,4,1,4,8))=8;
举例2:
typedef struct A 

    char c;  //1个字节
    int d;   //4个字节
    short e;  //2个字节
 }; 
结构体对齐字节数:min(8,max(1,4,2)) = 4,默认为8
char c:偏移量=0,能被sizeof(char c)=1整除;
int d:偏移量=0 + sizeof(char c),不能被sizeof(int d)整除,故补充字节数,变sizeof(char c)=4,偏移量变为4;
short e:偏移量=4 + sizeof(int d)=8,能被sizeof(short e)整除
故sizeof(struct A) = sizeof(char c) + sizeof(int d) + sizeof(short e) = 4 + 4 + 2 = 10,但不能被结构体对齐字节数:min(8,max(1,4,2)) = 4整除,故补充字节数,sizeof(struct A)=12
typedef struct B 

    char c;  //1个字节
    __int64 d;  //8个字节
    int e;  //4个字节
short f; //2个字节
A g;  //结构体长为12字节,结构体对齐字节为4字节
    char h;   //1个字节
    int i;   //4个字节
}; 
char c:偏移量=0,能被sizeof(char c)整除;
__int64 d:偏移量=0 + sizeof(char c)=1,不能被sizeof(__int64 d),故扩充char c为占8字节,sizeof(char c) = 8,偏移量=0 + 8 = 8;
int e:偏移量=8 + sizeof(__int64 d) = 16,能被sizeof(int e)=4整除;
short f:偏移量=16 + sizeof(int e) = 20,能被sizeof(short f)=2整除;
A g:偏移量=20 + sizeof(short f)=22,不能被结构体A的对齐字节数4整除,故扩充short f,sizeof(short f) = 4,偏移量=20 + 4 = 24;
char h:偏移量= 24 + sizeof(A g)= 24 + 12 = 36,能被sizeof(char h)整除;
int i:偏移量=36 + sizeof(char h) = 37,不能被sizeof(int i)=4整除,故扩充char h,sizeof(char h)=4,偏移量=36 + 4 = 40;
故sizeof(struct B)= sizeof(char c) + sizeof(__int64 d)+ sizeof(int e)+ sizeof(short f)+ sizeof(A g)+ sizeof(char h)+ sizeof(int i)=8 + 8 + 4 + 4 + 12 + 4 + 4 = 44,不能被结构体B的对齐字节数8整除(min(8,max(1,8,4,2,4,1,4))),对末尾元素进行扩充,得sizeof(struct B) = 48,能被8整除。
 
具体的验证用例(转)
http://bibei1234.iteye.com/blog/1645745
在c++中字节对齐主要存在符合类型中:union,struct和class中
先介绍四个概念:
1)数据类型自身的对齐值:基本数据类型的自身对齐值,等于sizeof(基本数据类型)。
2)指定对齐值:#pragma pack (value)时的指定对齐值value。
3)结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小的那个值。
        有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是 数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整 数倍)
#pragma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。
如#pragma pack (1)  
#pragma pack () 
 
1 union
Union就是取整个成员中大的内存块作为整个共用体的内存大小
对齐方式为成员中对齐方式最大的成员的对齐方式, 对界取编译器对界方式与自身大小中较小的一个
测试程序如下:
#include <iostream>
#include <stdio.h>
using std::cout;
using std::endl;
union u1
{
double a;
int b;
};
union u2
{
char a[13];
int b;
}; 
union u3
{
char a[13];
char b;
}; 
#pragma pack(2)//对界方式2字节¨²
union u4
{
char a[13];
int b;
}; 
union u5
{
char a[13];
char b;
};
 
#pragma pack()  //恢复默认对界方式
void main()
{
cout<<sizeof(u1)<<endl;
cout<<sizeof(u2)<<endl;
cout<<sizeof(u3)<<endl;
cout<<sizeof(u4)<<endl;
cout<<sizeof(u5)<<endl;
system("pause");
}
测试结果如下:
8
16
13
14
13
结论:由于默认是8字节对齐, 在u1 中a为8字节,很明显u1的大小就为8
在u2中由于int为4字节,char为1 个字节,所以min(8,max(4,1))=4,以4字节对齐,u2理论为13个字节,要以4字节对齐的话所以为16字节。同理 u3 :min(max(1,1),8)=1,sizeof(u3)=13, u4:  min(max(1,4),2)=2,sizeof(u4)=14,u5: min(max(1,1),2)=1,sizeof(u5)=13.
2   struct/class:都是对整个成员所占内存大小的求和。大小仅和成员变量的类型有关还和定义的先后顺序有关
举个例子,一个结构体如下:
typedef struct T

    char c; //本身长度1字节 
    __int64 d;  //本身长度8字节
    int e;  //本身长度4字节
    short f;  //本身长度2字节
    char g;  //本身长度1字节
    short h;  //本身长度2字节
}; 
    假设定义了一个结构体变量C,在内存中分配到了0x00的位置,显然:
    对于成员C.c  无论如何,也是一次寄存器读入,所以先占一个字节。
    对于成员C.d  是个64位的变量,如果紧跟着C.c存储,则读入寄存器至少需要3次,为了实现最少的2次读入,至少需要以4字节对齐;同时对于8字节的原始变量,为了在寻址单位上统一,则需要按8字节对齐,所以,应该分配到0x08-0xF的位置。
    对于成员C.e  是个32位的变量,自然只需满足分配起始为整数个32位即可,所以分配至0x10-0x13。
    对于成员C.f  是个16位的变量,直接分配在0x14-0x16上,这样,反正只需一次读入寄存器后加工,边界也与16位对齐。
    对于成员C.g  是个8位的变量,本身也得一次读入寄存器后加工,同时对于1个字节的变量,存储在任何字节开始都是对齐,所以,分配到0x17的位置。
    对于成员C.h  是个16位的变量,为了保证与16位边界对齐,所以,分配到0x18-0x1A的位置。
    分配图如下(还不正确,耐心读下去):


    结构体C的占用空间到h结束就可以了吗?我们找个示例:如果定义一个结构体数组 CA[2],按变量分配的原则,这2个结构体应该是在内存中连续存储的,分配应该如下图:
  
 
    分析一下上图,明显可知,CA[1]的很多成员都不再对齐了,究其原因,是结构体的开始边界不对齐。
    那结构体的开始偏移满足什么条件才可以使其成员全部对齐呢。想一想就明白了:很简单,保证结构体长度是原始成员最长分配的整数倍即可。
    上述结构体应该按最长的.d成员对齐,即与8字节对齐,这样正确的分配图如下:


    当然结构体T的长度:sizeof(T)==0x20;
     再举个例子,看看在默认对齐规则下,各结构体成员的对齐规则:
typedef struct A 

    char c;  //1个字节
    int d;  //4个字节,要与4字节对齐,所以分配至第4个字节处
    short e;  //2个字节, 上述两个成员过后,本身就是与2对齐的,所以之前无填充
 }; //整个结构体,最长的成员为4个字节,需要总长度与4字节对齐,所以, sizeof(A)==12 
typedef struct B 

    char c;  //1个字节
    __int64 d;  //8个字节,位置要与8字节对齐,所以分配到第8个字节处
    int e;  //4个字节,成员d结束于15字节,紧跟的16字节对齐于4字节,所以分配到16-19
    short f;  //2个字节,成员e结束于19字节,紧跟的20字节对齐于2字节,所以分配到20-21
    A g;  //结构体长为12字节,最长成员为4字节,需按4字节对齐,所以前面跳过2个字节, 
//到24-35字节处
    char h;   //1个字节,分配到36字节处
    int i;   //4个字节,要对齐4字节,跳过3字节,分配到40-43 字节
}; //整个结构体的最大分配成员为8字节,所以结构体后面加5字节填充,被到48字节。故:
//sizeof(B)==48;