這個(gè)結(jié)構(gòu)體對(duì)齊輸出有意思

這個(gè)題目是我在群里看到大家討論的,既然是討論的了,那我就拿出來(lái)說(shuō)說(shuō),因?yàn)楣P試面試的時(shí)候,可能就會(huì)遇到這樣的題目。
實(shí)例代碼
#include?"stdio.h"
#include?"stdint.h"
struct?Obj?{
????char?a;?//1
????uint32_t?b;//4
????uint8_t?c;//1
????uint64_t?d[0];//8
};
int?main()
{
?struct?Obj?Op;
?printf("%d?%d\n",sizeof(Op),sizeof(Op.d));
?return?(0);
}?
程序輸出
16?0
--------------------------------
Process?exited?after?0.03048?seconds?with?return?value?0
請(qǐng)按任意鍵繼續(xù).?.?.
這里比較令我們疑惑的是,d 的大小明明是 0,為甚結(jié)構(gòu)體的大小會(huì)是 16呢?
調(diào)戲一下
看看下面代碼的輸出
#include?"stdio.h"
#include?"stdint.h"
#define?PRINT_D(intValue)?????printf(#intValue"?is?%d\n",?(intValue));
#define?OFFSET(struct,member)??((char?*)&((struct?*)0)->member?-?(char?*)0)
#pragma?pack(1)
struct?Obj?{
????char?a;?
????uint32_t?b;
????uint8_t?c;
????uint64_t?d[0];
};
int?main()
{
?struct?Obj?Op;
?PRINT_D(OFFSET(struct?Obj,a));?
?PRINT_D(OFFSET(struct?Obj,b));?
?PRINT_D(OFFSET(struct?Obj,c));?
?PRINT_D(OFFSET(struct?Obj,d));?
?printf("%d?%d\n",sizeof(Op),sizeof(Op.d));
?return?(0);
}?
程序輸出
OFFSET(struct?Obj,a)?is?0
OFFSET(struct?Obj,b)?is?1
OFFSET(struct?Obj,c)?is?5
OFFSET(struct?Obj,d)?is?6
6?0
--------------------------------
Process?exited?after?0.0108?seconds?with?return?value?0
請(qǐng)按任意鍵繼續(xù).?.?.
這里的輸出剛好是我們的結(jié)構(gòu)體里內(nèi)容的大小
解釋下這段調(diào)試代碼的作用
#define?PRINT_D(intValue)?????printf(#intValue"?is?%d\n",?(intValue));
#define?OFFSET(struct,member)??((char?*)&((struct?*)0)->member?-?(char?*)0)
前面哪個(gè)比較簡(jiǎn)單了,就是使用 「#」這個(gè)符號(hào)把字符串帶過(guò)來(lái)打印。下面的OFFSET 比較有意思,先是把 0 這個(gè)地址強(qiáng)制轉(zhuǎn)換成我們需要的strunct ,然后呢,再讀取地址減去0地址,這樣就可以得出它的偏移大小了。
調(diào)戲一下2
我們改一下代碼
#include?"stdio.h"
#include?"stdint.h"
#define?PRINT_D(intValue)?????printf(#intValue"?is?%d\n",?(intValue));
#define?OFFSET(struct,member)??((char?*)&((struct?*)0)->member?-?(char?*)0)
#pragma?pack(4)
struct?Obj?{
????char?a;?
????uint32_t?b;
????uint8_t?c;
????uint64_t?d[0];
};
int?main()
{
?struct?Obj?Op;
?PRINT_D(OFFSET(struct?Obj,a));?
?PRINT_D(OFFSET(struct?Obj,b));?
?PRINT_D(OFFSET(struct?Obj,c));?
?PRINT_D(OFFSET(struct?Obj,d));?
?printf("%d?%d\n",sizeof(Op),sizeof(Op.d));
?return?(0);
}?
程序輸出
OFFSET(struct?Obj,a)?is?0
OFFSET(struct?Obj,b)?is?4
OFFSET(struct?Obj,c)?is?8
OFFSET(struct?Obj,d)?is?12
12?0
--------------------------------
Process?exited?after?0.01165?seconds?with?return?value?0
請(qǐng)按任意鍵繼續(xù).?.?.
調(diào)戲代碼3
#include?"stdio.h"
#include?"stdint.h"
#define?PRINT_D(intValue)?????printf(#intValue"?is?%d\n",?(intValue));
#define?OFFSET(struct,member)??((char?*)&((struct?*)0)->member?-?(char?*)0)
#pragma?pack(8)
struct?Obj?{
????char?a;?
????uint32_t?b;
????uint8_t?c;
????uint64_t?d[0];
};
int?main()
{
?struct?Obj?Op;
?PRINT_D(OFFSET(struct?Obj,a));?
?PRINT_D(OFFSET(struct?Obj,b));?
?PRINT_D(OFFSET(struct?Obj,c));?
?PRINT_D(OFFSET(struct?Obj,d));?
?printf("%d?%d\n",sizeof(Op),sizeof(Op.d));
?return?(0);
}?
程序輸出
OFFSET(struct?Obj,a)?is?0
OFFSET(struct?Obj,b)?is?4
OFFSET(struct?Obj,c)?is?8
OFFSET(struct?Obj,d)?is?16
16?0
--------------------------------
Process?exited?after?0.01219?seconds?with?return?value?0
請(qǐng)按任意鍵繼續(xù).?.?.
結(jié)構(gòu)體對(duì)齊大小的方式,這個(gè)背下,不背下就存下
原則A:struct或者union的成員,第一個(gè)成員在偏移0的位置,之后的每個(gè)成員的起始位置必須是當(dāng)前成員大小的整數(shù)倍;
原則B:如果結(jié)構(gòu)體A含有結(jié)構(gòu)體成員B,那么B的起始位置必須是B中最大元素大小整數(shù)倍地址;
原則C:結(jié)構(gòu)體的總大小,必須是內(nèi)部最大成員的整數(shù)倍;
這幾個(gè)原則是在 沒(méi)有 #pragma pack 的時(shí)候才起作用的,有了 #pragma pack,就按照 #pragma pack 的方式去對(duì)齊。
分析一下
基于上面的實(shí)驗(yàn)和理論,我們可以知道,這個(gè)筆試題的輸出結(jié)果是因?yàn)?uint64_t d[] 這個(gè)搞鬼了,就是因?yàn)檫@個(gè)搞鬼了,我們結(jié)構(gòu)體的最終大小才是 16。因?yàn)?uint64_t d 是 8個(gè)字節(jié),這樣結(jié)構(gòu)體就是以 8 字節(jié)的方式對(duì)齊了。
雖然d 的不占用內(nèi)存的,但是這個(gè)家伙的存在讓結(jié)構(gòu)體的對(duì)齊方式產(chǎn)生了改變,就是這么神奇。
為了驗(yàn)證我們的想法,我們改下程序
實(shí)例代碼
#include?"stdio.h"
#include?"stdint.h"
#define?PRINT_D(intValue)?????printf(#intValue"?is?%d\n",?(intValue));
#define?OFFSET(struct,member)??((char?*)&((struct?*)0)->member?-?(char?*)0)
struct?Obj?{
????char?a;?
????uint32_t?b;
????uint8_t?c;
????uint32_t?d[0];
};
int?main()
{
?struct?Obj?Op;
?PRINT_D(OFFSET(struct?Obj,a));?
?PRINT_D(OFFSET(struct?Obj,b));?
?PRINT_D(OFFSET(struct?Obj,c));?
?PRINT_D(OFFSET(struct?Obj,d));?
?printf("%d?%d\n",sizeof(Op),sizeof(Op.d));
?return?(0);
}?
程序輸出
OFFSET(struct?Obj,a)?is?0
OFFSET(struct?Obj,b)?is?4
OFFSET(struct?Obj,c)?is?8
OFFSET(struct?Obj,d)?is?12
12?0
--------------------------------
Process?exited?after?0.01002?seconds?with?return?value?0
請(qǐng)按任意鍵繼續(xù).?.?.
看到?jīng)]?
看到?jīng)]?
看到?jīng)]?
這樣之后,程序的輸出就是 12 了,也就是說(shuō),現(xiàn)在的程序是按照 4字節(jié)對(duì)齊方式來(lái)對(duì)齊了。

