C语言结构体内存布局是一个老生常谈的问题,网上也看了一些资料,有些说的比较模糊,有些是错误的。本人借鉴了前人的文章,通过实践,总结了一些规则,若有错误,但愿指正,不胜感激。html
macOS Sierra(10.12.4)
Xcode(8.3)
影响结构体内存布局有位域和**#pragma pack预处理宏**两个状况,下面分状况说明。数组
结构体字节对齐的细节和具体的编译器实现相关,但通常来讲遵循3个准则:数据结构
sizeof
)所整除。offset
都是成员大小的整数倍,若有须要编译器会在成员之间加上填充字节。sizeof
为结构体最宽基本成员大小的整数倍,若有须要编译器会在最末一个成员以后加上填充字节。下面的demo会为你们解释以上规则:函数
struct student {
char name[5];
double weight;
int age;
};
复制代码
struct school {
short age;
char name[7];
struct student lilei;
};
复制代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
struct student lilei = {"lilei",112.33,20};
printf("size of struct student: %lu\n",sizeof(lilei));
printf("address of student name: %u\n",lilei.name);
printf("address of student weight: %u\n",&lilei.weight);
printf("address of student age: %u\n",&lilei.age);
struct school shengli = {70,"shengli",lilei};
printf("size of struct school: %lu\n",sizeof(shengli));
printf("address of school age: %u\n",&shengli.age);
printf("address of school name: %u\n",shengli.name);
printf("address of school student: %u\n",&shengli.lilei);
}
return 0;
}
复制代码
struct school
包含 struct student
,因此最宽的基本数据类型为double
,sizeof(double)
为8
,1606416152/8 = 200802019
,1606416112/8 = 200802014
)。struct student weight
成员的首地址是1606416160
而不是1606416157
,**但有很重要的一点要注意,这里的成员为基本数据类型,不包括char类型数组和结构体成员,char类型数组按1字节对齐,结构体成员存储的起始位置要从自身内部最大成员大小的整数倍地址开始存储,**好比struct a
里有struct b
成员,b
里有char,int,double
等成员,那b
存储的起始位置应该从8的整数倍开始。经过struct school
成员内存分布能够看出来,school.name
的首地址是1606416114
,而不是1606416119
,school.student
的首地址是1606416128
,能被8
整除,不能被24
整除)。struct student
占用字节数为24
而不是20
的缘由)。细心的朋友可能发现&shengli.lilei
(等效于shengli.lilei.name
)的数值并不等于lilei.name
,也就是说struct school shengli
里的成员struct student lilei
和 struct student lilei
并非指向同一块内存空间,是值拷贝开辟的一块新的内存空间,也就是说struct是值类型而不是引用类型数据结构。还有经过内存地址能够发现两个结构体变量的内存空间是在内存栈上连续分配的。布局
结构体使用位域的主要目的是压缩存储,位域成员不能单独被取sizeof
值。C99规定int,unsigned int,bool
能够做为位域类型,但编译器几乎都对此作了扩展,容许其它类型存在。结构体中含有位域字段,除了要遵循上面3个准则,还要遵循如下4个规则:ui
sizeof
大小,则后一个字段将紧邻前一个字段存储,直到不能容纳为止。sizeof
大小,则后一个字段将重新的存储单元开始,其偏移量为其类型大小的整数倍。下面的demo会为你们解释以上规则:spa
typedef struct A {
char f1:3;
char f2:4;
char f3:5;
char f4:4;
}a;
复制代码
typedef struct B {
char f1:3;
short f2:13;
}b;
复制代码
typedef struct C {
char f1:3;
char f2;
char f3:5;
}c;
复制代码
typedef struct D {
char f1:3;
char :0;
char :4;
char f3:5;
}d;
复制代码
typedef struct E {
int f1:3;
}e;
复制代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
printf("size of struct A: %lu\n",sizeof(a));
printf("size of struct B: %lu\n",sizeof(b));
printf("size of struct C: %lu\n",sizeof(c));
printf("size of struct D: %lu\n",sizeof(d));
printf("size of struct E: %lu\n",sizeof(e));
}
return 0;
}
复制代码
struct A
中全部位域成员类型都为char
,第一个字节只能容纳f1
和f2
,f3
从下一个字节开始存储,第二个字节不能容纳f4
,因此f4
也要从下一个字节开始存储,所以sizeof(a)
结果为3
。struct B
中位域成员类型不一样,进行了压缩,所以sizeof(b)
结果为2
(不压缩方式没有进行验证,很抱歉)。struct C
中位域成员之间有非位域类型成员,不进行压缩,所以sizeof(c)
结果为3。struct D
中有无名位域成员,char f1:3
占3
个bit
,char :0
移到下1
个字节(移动单位和具体位域类型有关,short
移到下2
个字节,int
移到下4
个字节),char :4
占4
个bit
,而后不能容纳char f3:5
,因此要存到下1
个字节,所以sizeof(d)
结果为3
。sizeof(e)
结果为4
,不该该是只占用1
个字节么?不要忘了上面提到的准则3。int a:33
是不被容许的。char:0
表示整个位域向后推一个字节,即该无名位域后的下一个位域从下一个字节开始存放,同理short:0
和int:0
分别表明整个位域向后推两个和四个字节。当空位域的长度为具体数值N时(例如 int:2
),该变量仅用来占N位。编译器的#pragma pack
指令也是用来调整结构体对齐方式的,不一样编译器名称和用法略有不一样。使用伪指令#pragma pack(n)
,编译器将按照n个字节对齐,其取值为一、二、四、八、16,默认是8,使用伪指令#pragma pack()
,取消自定义字节对齐方式。若是设置#pragma pack(1)
,就是让结构体没有填充字节,实现空间“无缝存储”,这对跨平台传输数据来讲是友好和兼容的。结构体中含有#pragma pack预处理宏,除了要遵循上面3个准则,还要遵循如下2个规则:.net
offsetof(item) = min(n, sizeof(item))
#pragma pack(push) //packing stack入栈,设置当前对齐方式
#pragma pack(pop) //packing stack出栈,取消当前对齐方式
#pragma pack(n) //n=1,2,4,8,16保存当前对齐方式,设置按n字节对齐
#pragma pack() //等效于pack(pop)
#pragma pack(push,n)//等效于pack(push) + pack(n)
复制代码
#pragma pack(4)
typedef struct F {
int f1;
double f2;
char f3;
}f;
#pragma pack()
复制代码
#pragma pack(16)
typedef struct G {
int f1;
double f2;
char f3;
}g;
复制代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
printf("size of struct D: %lu\n",sizeof(f));
printf("size of struct E: %lu\n",sizeof(g));
}
return 0;
}
复制代码
struct F
设置的对齐方式为4
,min(4, sizeof(int)) = 4
,f1
占4
个字节,偏移量为0
,min(4, sizeof(double)) = 4
,f2
占4
个字节,偏移量为4
,min(4, sizeof(char)) = 1
,f3
占1
个字节,偏移量为12
,最后整个结构体知足准则3,sizeof(f) = 16
。struct G
设置的对齐方式为16
,比结构体中全部成员类型都要大,至关于没有生效,所以sizeof(f) = 24
。位域和**#pragma pack预处理宏的结构体在遵循3个准则**的前提下,有本身的相应规则也要遵照。结构体成员在排列时数据类型要遵循从小到大排列,这样能尽量的节省空间。指针
blog.sina.cn/dpool/blog/… c.biancheng.net/cpp/html/46… hubingforever.blog.163.com/blog/static…code