printf输出顺序、结构体字节对齐、偏移量

今天看到printf输出顺序,如下图结果惊呆了我,经过复习终于明白:

 原因就是:

原则1:printf语句是从右往左计算会改变变量本身的表达式,计算完毕叫做假结束(但后自增/后自减的表达式叫做真结束)
原则2:再从左往右将变量值代入之前所有假结束的表达式中计算完毕才变成真结束,但不可对变量进行任何改变。

故对于第一次的printf:

z=4;
printf("%d,%d,%d,%d,%d,%d\n",z--,z++,z=100,z++,++z,--z);
//简单表示为printf("xxxxxxxxxx",A,B,C,D,E,F);

过程如下:

根据原则1:从右往左:
F=--z 即 z=z-1=3,F=z=3
E=++z 即 z=z+1=4,E=z=4
D=z++ 即 D=z=4,z=z+1=5 真结束
C是表达式100,z=100
B=z++ 即 B=z=100,z=z+1=101 真结束
A=z-- 即 A=z=101,z=z-1=100 真结束
即最终z=100

根据原则2:从左往右
A由原则一开始就是真结束,所以A表达式输出101不变
B由原则一开始就是真结束,所以B表达式输出100不变
C=最终的z即100
D由原则一开始就是真结束,所以D表达式输出4不变
E=最终的z即100  本来有z=z+1但根据原则二最后一句话知不可执行
F=最终的z即100  本来有z=z-1但根据原则二最后一句话知不可执行

所以最后的输出是 101,100,100,4,100,100

对于第二次的printf:

z=4;
printf("%d,%d,%d,%d,%d,%d,%d\n",--z,z--,z++,z=100,z++,++z,--z);
//简单表示为printf("xxxxxxxxxx",A,B,C,D,E,F,G);

过程如下:

根据原则1:从右往左:
G=--z 即 z=z-1=3,G=z=3
F=++z 即 z=z+1=4,F=z=4
E=z++ 即 E=z=4,z=z+1=5 真结束
D是表达式100,z=100
C=z++ 即 C=z=100,z=z+1=101 真结束
B=z-- 即 B=z=101,z=z-1=100 真结束
A=--z 即 z=z-1=99,A=z=99

最终z=99

根据原则2:从左往右
A=z=99  
B由原则一开始就是真结束,所以B表达式输出101不变
C由原则一开始就是真结束,所以C表达式输出100不变
D=z=99
E由原则一开始就是真结束,所以E表达式输出4不变
F=z=99  本来有z=z+1但根据原则二最后一句话知不可执行
F=z=99  本来有z=z-1但根据原则二最后一句话知不可执行

所以最后的输出是 99,101,100,99,4,99,99

后面我又陆续验证了很多表达式,发现都遵循上面2条黄金原则,无一例外。

今天复习下struct的大小、成员偏移量offsetof,说下我的理解:

64位下默认对齐数default=8
原则1:struct中每一个成员变量tmp的对齐数real=min{default,tmp}

struct Student {
    int num;//0
    char name[8];
    double score;
} stu;

这个结构体stu中,第一个成员num为int的对齐数real_num=min{8,4}=4。
第二个成员name为char类型,对齐数real_name=min{8,1}=1。
第三个成员score为double类型,对齐数real_score=min{8,8}=8。
原则2:偏移量offsetof必须是real的倍数
1,所以第一个成员num偏移量startid=0*real_num=0*4=0,从startid开始size1=4即4个bytes被用掉即[id=0~3已用]。

2,因为上面size1=4已用4个bytes。第二个成员name偏移量是startid=real_name*倍数=1*倍数>3,故此时倍数取4,所以偏移量startid=4,而name的size2=8,所以id=4~11已用。


3,因为上面size1+size2=12已用12个bytes。而第三个成员score的偏移量startid=real_score*倍数=8*倍数,必需>11,所以倍数不能取0或1,只能取2,所以startid=8*2=16,即从16开始放score,而score的大小size3=8,此时id=16~23已用。所以stu这个结构体在内存中就是如下的样子:

所以stu的size等于id=0一直到id=23即size=24。所以逻辑是先计算偏移量,再自然计算得到struct整体的大小。

struct Test1 {
    int x;
    double y;
} t1;
struct Test2 {
    int x;
    char y[8];
} t2;

在t1中,对x来说 real_x=min{8,4}=4 偏移量startid=real_x*倍数=4*0=0,size1=4,id=0~3被使用。
在t1中,对y来说 real_y=min{8,8}=8偏移量startid=real_y*倍数=8*倍数,同时必需>3,所以倍数就是1,startid=8。

当你换成char y[8]后,如结构体t2所示:对y来说,real_y=min{8,1}=1 偏移量startid=real_y*倍数=1*倍数,同时必需>3,所以倍数就是4,startid=4。

由此可见,t1和t2偏移量不一样,所以struct的size是不一样的。虽然它们的成员变量都是4bytes、8bytes,但终究偏移量不同导致了结构体大小的不同。

原则3:struct的对齐数等于其所有成员real对齐数的最大值。

所以对于结构体嵌套结构体的情况,我们易知:

struct Student {
    int num;
    char name[8];
    double score;
};

struct CombineStudent {
    short memb;
    Student originstruct[2];
    char lastmem[3];
} comstu;

现在结构体Student(由之前分析知结构体的size为24字节)作为新结构体comstu的成员,且个数为2的数组originstruct。
根据原则3,结构体Student的对齐数real_stu=max(real_num,real_name,real_score)=max(4,1,8)=8

对于结构体comstu:
1,第一个成员memb为short的对齐数real_mem=min{8,2}=2。偏移量startid=0*real_mem=0*2=0 而short占2字节即[id=0~1已用]。

2,第二个成员originstruct为Student类型,对齐数real_originstruct=min{8,8}=8,偏移量startid=倍数*real_originstruct=倍数*8,必须>1,故倍数=1,即偏移量startid=1*8=8。而每个Student占24字节现在有2个Student即[id=8~8+24*2-1也就是8~55已用]。
3,第三个成员lastmem为char类型,对齐数real_lastmem=min{8,1}=1,偏移量startid=倍数*real_lastmem=倍数*1,必须>55,故倍数取56即可,startid=56,而每个char占1字节现在共3个,共id=56~58已用。
但你以为结构体comstu的大小就是id=0~58=59吗?!天真,还有原则4!

原则4:结构体的总大小size必须是对齐数real的整数倍

所以虽然上面的结构体comstu的id=0~58=59,但结构体CombineStudent的real_comstu=max(real_memb,real_originstruct,real_lastmem)=max(2,8,1)=8!但是我们之前算好的59并不是real_comstu的整数倍!所以后面必须要补几位直到占到id=63,即id=0~63总size=64才是CombineStudent的大小!

所以大家这下明白为啥C++建议我们定义变量时按从一定的顺序去定,如换个位置:

struct Combine2Student {
    short memb;
    char lastmem[3];
    Student originstruct[2];
} ;

刚刚的结构体CombineStudent如果换成这样,结果就不一样了,可以节约内存。大家可以算算:

real_memb=min{8,2}=2,size1=2,startid=0是real_memb的倍数,占id=0~1;

real_lastmem=min{8,1}=1,size2=3,startid=2是real_lastmem的倍数,占id=2~4;

real_originstruct=min{8,8}=8,size3=24*2=48,startid=8是real_originstruct的倍数,占id=8~55;

算到这里,总id=0~55=56已经是real_Combine2Student=max(real_memb,real_lastmem,real_originstruct)=max(2,8,1)=8的倍数了,所以不需再补!所以结构体Combine2Student的size=56!

调换位置后命名相同的三个成员,但结构体Combine2Student就是比ComebineStudent占内存少!!!

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

元气少女缘结神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值