图文并茂,一文讲透C语言结构体内存对齐
(以下有约5000字内容,建议收藏再读,推荐下载源码自行测试以加深理解。)
面试官:你知道C语言的结构体对齐吗?
应聘者:听说过……平时很少关注
……
面试官:好吧,那回去等通知吧
-
为什么会有结构体内存对齐?
-
结构体怎么对齐?
-
学习结构体对齐有什么用?
-
结构体对齐有没有实际应用?
void base_type_size(void)
{
BASE_TYPE_SIZE(void);
BASE_TYPE_SIZE(char);
BASE_TYPE_SIZE(short);
BASE_TYPE_SIZE(int);
BASE_TYPE_SIZE(long);
BASE_TYPE_SIZE(long long);
BASE_TYPE_SIZE(float);
BASE_TYPE_SIZE(double);
BASE_TYPE_SIZE(long double);
BASE_TYPE_SIZE(void*);
BASE_TYPE_SIZE(char*);
BASE_TYPE_SIZE(int*);
typedef struct
{
}StructNull;
BASE_TYPE_SIZE(StructNull);
BASE_TYPE_SIZE(StructNull*);
}
void : 1 Byte
char : 1 Byte
short : 2 Bytes
int : 4 Bytes
long : 4 Bytes
long long : 8 Bytes
float : 4 Bytes
double : 8 Bytes
long double : 12 Bytes
void* : 4 Bytes
char* : 4 Bytes
int* : 4 Bytes
StructNull : 0 Byte
StructNull* : 4 Bytes
void类型不是空的,占一个字节
long不一定比int大
C语言空结构体的大小为0(注意:C++的为1)
不管什么类型,指针都是相同大小的
typedef struct
{
int e_int;
char e_char;
}S1;
S1 s1;
STRUCT_E_ADDR_OFFSET(s1, e_int);
STRUCT_E_ADDR_OFFSET(s1, e_char);
typedef struct
{
int e_int;
double e_double;
}S11;
S11 s11;
STRUCT_E_ADDR_OFFSET(s11, e_int);
STRUCT_E_ADDR_OFFSET(s11, e_double);
s1 size = 8 s1.e_int addr: 0028FF28, offset: 0
s1 size = 8 s1.e_char addr: 0028FF2C, offset: 4
s11 size = 16 s11.e_int addr: 0028FF18, offset: 0
s11 size = 16 s11.e_double addr: 0028FF20, offset: 8
typedef struct
{
int e_int;
long double e_ld;
}S12;
typedef struct
{
long long e_ll;
long double e_ld;
}S13;
typedef struct
{
char e_char;
long double e_ld;
}S14;
S12 s12;
S13 s13;
S14 s14;
STRUCT_E_ADDR_OFFSET(s12, e_int);
STRUCT_E_ADDR_OFFSET(s12, e_ld);
STRUCT_E_ADDR_OFFSET(s13, e_ll);
STRUCT_E_ADDR_OFFSET(s13, e_ld);
STRUCT_E_ADDR_OFFSET(s14, e_char);
STRUCT_E_ADDR_OFFSET(s14, e_ld);
s12 size = 16 s12.e_int addr: 0028FF08, offset: 0
s12 size = 16 s12.e_ld addr: 0028FF0C, offset: 4
s13 size = 24 s13.e_ll addr: 0028FEF0, offset: 0
s13 size = 24 s13.e_ld addr: 0028FEF8, offset: 8
s14 size = 16 s14.e_char addr: 0028FEE0, offset: 0
s14 size = 16 s14.e_ld addr: 0028FEE4, offset: 4
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。
网上流传一个表:
平台 |
长度/模数 |
char |
short |
int |
long |
float |
double |
long long |
long double |
Win-32 |
长度 |
1 |
2 |
4 |
4 |
4 |
8 |
8 |
8 |
模数 |
1 |
2 |
4 |
4 |
4 |
8 |
8 |
8 |
|
Linux-32 |
长度 |
1 |
2 |
4 |
4 |
4 |
8 |
8 |
12 |
模数 |
1 |
2 |
4 |
4 |
4 |
4 |
4 |
4 |
|
Linux-64 |
长度 |
1 |
2 |
4 |
8 |
4 |
8 |
8 |
16 |
模数 |
1 |
2 |
4 |
8 |
4 |
8 |
8 |
16 |
typedef struct
{
int e_int;
double e_double;
}S11;
S11 s11;
STRUCT_E_ADDR_OFFSET(s11, e_int);
STRUCT_E_ADDR_OFFSET(s11, e_double);
s11 size = 16 s11.e_int addr: 0028FF18, offset: 0
s11 size = 16 s11.e_double addr: 0028FF20, offset: 8
长度/模数 |
char |
short |
int |
long |
float |
double |
long long |
long double |
长度 |
1 |
2 |
4 |
4 |
4 |
8 |
8 |
12 |
模数 |
1 |
2 |
4 |
4 |
4 |
8 |
8 |
8 |
typedef struct
{
char e_char;
long double e_ld;
}S14;
typedef struct
{
int e_int;
char e_char1;
char e_char2;
}S2;
typedef struct
{
char e_char1;
int e_int;
char e_char2;
}S3;
S2 s2;
S3 s3;
s2 size = 8 s2.e_int addr: 0028FED4, offset: 0
s2 size = 8 s2.e_char1 addr: 0028FED8, offset: 4
s2 size = 8 s2.e_char2 addr: 0028FED9, offset: 5
s3 size = 12 s3.e_char1 addr: 0028FEC4, offset: 0
s3 size = 12 s3.e_int addr: 0028FEC8, offset: 4
s3 size = 12 s3.e_char2 addr: 0028FECC, offset: 8
typedef struct
{
char e_char1;
short e_short;
char e_char2;
int e_int;
char e_char3;
}S4;
S4 s4;
STRUCT_E_ADDR_OFFSET(s4, e_char1);
STRUCT_E_ADDR_OFFSET(s4, e_short);
STRUCT_E_ADDR_OFFSET(s4, e_char2);
STRUCT_E_ADDR_OFFSET(s4, e_int);
STRUCT_E_ADDR_OFFSET(s4, e_char3);
s4 size = 16 s4.e_char1 addr: 0028FEB4, offset: 0
s4 size = 16 s4.e_short addr: 0028FEB6, offset: 2
s4 size = 16 s4.e_char2 addr: 0028FEB8, offset: 4
s4 size = 16 s4.e_int addr: 0028FEBC, offset: 8
s4 size = 16 s4.e_char3 addr: 0028FEC0, offset: 12
typedef struct
{
int e_int;
char e_char;
}S1;
typedef struct
{
S1 e_s;
char e_char;
}SS1;
typedef struct
{
short e_short;
char e_char;
}S6;
typedef struct
{
S6 e_s;
char e_char;
}SS2;
SS1 ss1;
STRUCT_E_ADDR_OFFSET(ss1, e_s);
STRUCT_E_ADDR_OFFSET(ss1, e_char);
SS2 ss2;
STRUCT_E_ADDR_OFFSET(ss2, e_s);
STRUCT_E_ADDR_OFFSET(ss2, e_char);
ss1 size = 12 ss1.e_s addr: 0028FE94, offset: 0
ss1 size = 12 ss1.e_char addr: 0028FE9C, offset: 8
ss2 size = 6 ss2.e_s addr: 0028FE8E, offset: 0
ss2 size = 6 ss2.e_char addr: 0028FE92, offset: 4
typedef union
{
char e_char;
int e_int;
}U1;
U1 u1;
STRUCT_E_ADDR(u1, e_char);
STRUCT_E_ADDR(u1, e_int);
u1 size = 4 u1.e_char addr: 0028FF2C
u1 size = 4 u1.e_int addr: 0028FF2C
那么,union跟struct结合呢?
typedef struct
{
int e_int1;
union
{
char ue_chars[9];
int ue_int;
}u;
double e_double;
int e_int2;
}SU2;
SU2 su2;
STRUCT_E_ADDR_OFFSET(su2, e_int1);
STRUCT_E_ADDR_OFFSET(su2, u.ue_chars);
STRUCT_E_ADDR_OFFSET(su2, u.ue_int);
STRUCT_E_ADDR_OFFSET(su2, e_double);
STRUCT_E_ADDR_OFFSET(su2, e_int2)
输出:
su2 size = 32 su2.e_int1 addr: 0028FEF8, offset: 0
su2 size = 32 su2.u.ue_chars addr: 0028FEFC, offset: 4
su2 size = 32 su2.u.ue_int addr: 0028FEFC, offset: 4
su2 size = 32 su2.e_double addr: 0028FF08, offset: 16
su2 size = 32 su2.e_int2 addr: 0028FF10, offset: 24
实际上跟结构体类似,也没有特别的规则。
顺便提一下,使用union时,要留意平台的大小端问题。
百度百科——大小端模式
怎么获知自己使用的平台的大小端?Linux有个方法:
static union {
char c[4];
unsigned long l;
} endian_test = { { 'l', '?', '?', 'b' } };
printf("ENDIANNESS: %c\n", ENDIANNESS);
4. 位域(Bitfield)的相关
位域在本文没什么好探讨的,在结构体对齐方面没什么特别的地方。
直接看个测试代码,就可以明白:
void bitfield_type_size(void)
{
typedef struct
{
char bf1:1;
char bf2:1;
char bf3:1;
char bf4:3;
}SB1;
typedef struct
{
char bf1:1;
char bf2:1;
char bf3:1;
char bf4:7;
}SB2;
typedef struct
{
char bf1:1;
char bf2:1;
char bf3:1;
int bfint:1;
}SB3;
typedef struct
{
char bf1:1;
char bf2:1;
int bfint:1;
char bf3:1;
}SB4;
SB1 sb1;
SB2 sb2;
SB3 sb3;
SB4 sb4;
VAR_ADDR(sb1);
VAR_ADDR(sb2);
VAR_ADDR(sb3);
VAR_ADDR(sb4);
typedef struct
{
unsigned char bf1:1;
unsigned char bf2:1;
unsigned char bf3:1;
unsigned char bf4:3;
}SB11;
typedef union
{
SB11 sb1;
unsigned char e_char;
}UB1;
UB1 ub1;
STRUCT_E_ADDR_OFFSET(ub1, sb1);
STRUCT_E_ADDR_OFFSET(ub1, e_char);
ub1.e_char = 0xF5;
BITFIELD_VAL(ub1, e_char);
BITFIELD_VAL(ub1, sb1.bf1);
BITFIELD_VAL(ub1, sb1.bf2);
BITFIELD_VAL(ub1, sb1.bf3);
BITFIELD_VAL(ub1, sb1.bf4);
}
输出结果是:
sb1 size = 1 sb1 addr: 0028FF2F
sb2 size = 2 sb2 addr: 0028FF2D
sb3 size = 8 sb3 addr: 0028FF24
sb4 size = 12 sb4 addr: 0028FF18
ub1 size = 1 ub1.sb1 addr: 0028FF17, offset: 0
ub1 size = 1 ub1.e_char addr: 0028FF17, offset: 0
ub1 : 1 Byte, ub1.e_char=0xF5
ub1 : 1 Byte, ub1.sb1.bf1=0x1
ub1 : 1 Byte, ub1.sb1.bf2=0x0
ub1 : 1 Byte, ub1.sb1.bf3=0x1
ub1 : 1 Byte, ub1.sb1.bf4=0x6
有几个点需要注意下:
内存的计算单位是byte,不是bit
结构体内即使有bitfield元素,其对齐规则还是按照基本类型来
-
结构体的内存大小,并非其内部元素大小之和;
-
结构体变量的起始地址,可以被最大元素基本类型大小或者模数整除; -
结构体的内存对齐,按照其内部最大元素基本类型或者模数大小对齐; -
模数在不同平台值不一样,也可通过#pragma pack(n)方式去改变; -
如果空间地址允许,结构体内部元素会拼凑一起放在同一个对齐空间; -
结构体内有结构体变量元素,其结构体并非展开后再对齐; -
union和bitfield变量也遵循结构体内存对齐原则。
typedef struct
{
int e_int;
char e_char1;
char e_char2;
}S2;
typedef struct
{
char e_char1;
int e_int;
char e_char2;
}S3;
S2 s2[1024] = {0};
S3 s3[1024] = {0};
有时候,我们在通信数据接收处理时候,往往遇到,数组和结构体的搭配。
即,通信时候,通常使用数组参数形式接收,而处理的时候,按照预定义格式去访问处理。例如:
U8 comm_data[10];
typedef struct
{
U8 id;
U16 len;
U8 data[6];
}FRAME;
FRAME* pFram = (FRAME*)comm_data;
篇幅有限,此处不贴完整的源码了。
往期精彩内容推荐>>>