图文并茂,一次搞定C语言结构体内存对齐!(包含完整源码)
面试官:你知道C语言的结构体对齐吗?
应聘者:听说过……平时很少关注 ……
面试官:好吧,那回去等通知吧
为什么会有结构体内存对齐? 结构体怎么对齐? 学习结构体对齐有什么用? 结构体对齐有没有实际应用?


#define BASE_TYPE_SIZE(t) printf('%12s : %2d Byte%s\n', #t, sizeof(t), (sizeof(t))>1?'s':'')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 Bytechar : 1 Byteshort : 2 Bytesint : 4 Byteslong : 4 Byteslong long : 8 Bytesfloat : 4 Bytesdouble : 8 Byteslong double : 12 Bytesvoid* : 4 Byteschar* : 4 Bytesint* : 4 BytesStructNull : 0 ByteStructNull* : 4 Bytes
void类型不是空的,占一个字节
long不一定比int大
C语言空结构体的大小为0(注意:C++的为1)
不管什么类型,指针都是相同大小的
#define offset(type, member) (size_t)&(((type *)0)->member)#define STRUCT_E_ADDR(s,e) printf('%5s size = %2d %16s addr: %p\n', #s, sizeof(s), #s'.'#e, &s.e)#define STRUCT_E_OFFSET(s,e) printf('%5s size = %2d %16s offset: %2d\n', #s, sizeof(s), #s'.'#e, offset(__typeof__(s),e))#define STRUCT_E_ADDR_OFFSET(s,e) printf('%5s size = %2d %16s addr: %p, offset: %2d\n', #s, sizeof(s), #s'.'#e, &s.e, offset(__typeof__(s),e))
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: 0s1 size = 8 s1.e_char addr: 0028FF2C, offset: 4s11 size = 16 s11.e_int addr: 0028FF18, offset: 0s11 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: 0s12 size = 16 s12.e_ld addr: 0028FF0C, offset: 4s13 size = 24 s13.e_ll addr: 0028FEF0, offset: 0s13 size = 24 s13.e_ld addr: 0028FEF8, offset: 8s14 size = 16 s14.e_char addr: 0028FEE0, offset: 0s14 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: 0s11 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 |
#pragma pack(1)typedef struct { char e_char; long double e_ld;}S14;#pragma pack()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' } };#define ENDIANNESS ((char)endian_test.l)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: 0028FF2Fsb2 size = 2 sb2 addr: 0028FF2Dsb3 size = 8 sb3 addr: 0028FF24sb4 size = 12 sb4 addr: 0028FF18ub1 size = 1 ub1.sb1 addr: 0028FF17, offset: 0ub1 size = 1 ub1.e_char addr: 0028FF17, offset: 0ub1 : 1 Byte, ub1.e_char=0xF5ub1 : 1 Byte, ub1.sb1.bf1=0x1ub1 : 1 Byte, ub1.sb1.bf2=0x0ub1 : 1 Byte, ub1.sb1.bf3=0x1ub1 : 1 Byte, ub1.sb1.bf4=0x6
有几个点需要注意下:
内存的计算单位是byte,不是bit
结构体内即使有bitfield元素,其对齐规则还是按照基本类型来
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;

#include <stdio.h>
#define offset(type, member) (size_t)&(((type *)0)->member)
#define STRUCT_E_ADDR(s,e) printf('%5s size = %2d %16s addr: %p\n', #s, sizeof(s), #s'.'#e, &s.e)#define STRUCT_E_OFFSET(s,e) printf('%5s size = %2d %16s offset: %2d\n', #s, sizeof(s), #s'.'#e, offset(__typeof__(s),e))#define STRUCT_E_ADDR_OFFSET(s,e) printf('%5s size = %2d %16s addr: %p, offset: %2d\n', #s, sizeof(s), #s'.'#e, &s.e, offset(__typeof__(s),e))#define VAR_ADDR(v) printf('%5s size = %2d %10s addr: %p\n', #v, sizeof(v), #v, &v)// #define BASE_TYPE_SIZE(t) printf('%18s = %d\n', 'sizeof('#t')', sizeof(t))#define BASE_TYPE_SIZE(t) printf('%12s : %2d Byte%s\n', #t, sizeof(t), (sizeof(t))>1?'s':'')#define BITFIELD_VAL(s,e) printf('%12s : %2d Byte%s, %10s=0x%X\n', #s, sizeof(s), (sizeof(s))>1?'s':'', #s'.'#e, s.e)
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*);}void struct_type_size(void){ typedef struct { }StructNull;
typedef struct { int e_int; char e_char; }S1; BASE_TYPE_SIZE(StructNull); BASE_TYPE_SIZE(StructNull*); 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;
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;
S11 s11; S12 s12; S13 s13; S14 s14; STRUCT_E_ADDR_OFFSET(s11, e_int); STRUCT_E_ADDR_OFFSET(s11, e_double); 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);
typedef struct { int e_int; char e_char1; char e_char2; }S2;
typedef struct { char e_char1; int e_int; char e_char2; }S3; typedef struct { char e_char1; short e_short; char e_char2; int e_int; char e_char3; }S4; typedef struct { long long e_ll; int e_int; }S5;
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;
char var1; S2 s2; char var2; S3 s3; VAR_ADDR(var1); STRUCT_E_ADDR_OFFSET(s2, e_int); STRUCT_E_ADDR_OFFSET(s2, e_char1); STRUCT_E_ADDR_OFFSET(s2, e_char2); VAR_ADDR(var2); STRUCT_E_ADDR_OFFSET(s3, e_char1); STRUCT_E_ADDR_OFFSET(s3, e_int); STRUCT_E_ADDR_OFFSET(s3, e_char2); 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); S5 s5; STRUCT_E_ADDR_OFFSET(s5, e_ll); STRUCT_E_ADDR_OFFSET(s5, e_int); 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);}
void union_type_size(void){ typedef union { char e_char; int e_int; }U1;
U1 u1; STRUCT_E_ADDR_OFFSET(u1, e_char); STRUCT_E_ADDR_OFFSET(u1, e_int);
typedef struct { short e_short; union { char ue_chars[9]; int ue_int; }u; }SU1;
typedef struct { int e_int1; union { char ue_chars[9]; int ue_int; }u; double e_double; int e_int2; }SU2;
SU1 su1; SU2 su2; STRUCT_E_ADDR_OFFSET(su1, e_short); STRUCT_E_ADDR_OFFSET(su1, u.ue_chars); STRUCT_E_ADDR_OFFSET(su1, u.ue_int); 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);}
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; // STRUCT_E_OFFSET(sb1, bf1); // STRUCT_E_OFFSET(sb1, bf2); // STRUCT_E_OFFSET(sb1, bf3); // STRUCT_E_OFFSET(sb1, bf4); 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);
static union { char c[4]; unsigned long l; } endian_test = { { 'l', '?', '?', 'b' } }; #define ENDIANNESS ((char)endian_test.l)
printf('ENDIANNESS: %c\n', ENDIANNESS);
}int main(void){ // base_type_size(); struct_type_size(); union_type_size(); bitfield_type_size();
return 0;}本文授权转载自公众号“嵌入式软件实战派”,作者实战派大师兄
