C語言中union與struct的區(qū)別
在C語言中結(jié)構(gòu)體和聯(lián)合具有細微差別,特別是使用sizeof()對其求大小時,許多剛剛接觸C語言不久的朋友對此非常困惑,下面我將簡單談一下自己對union與struct之間的區(qū)別
聯(lián) 合(union)
1. 聯(lián)合說明和聯(lián)合變量定義
聯(lián)合也是一種新的數(shù)據(jù)類型, 它是一種特殊形式的變量。
聯(lián)合說明和聯(lián)合變量定義與結(jié)構(gòu)十分相似。其形式為:
union 聯(lián)合名{
數(shù)據(jù)類型 成員名;
數(shù)據(jù)類型 成員名;
...
} 聯(lián)合變量名;
聯(lián)合表示幾個變量共用一個內(nèi)存位置, 在不同的時間保存不同的數(shù)據(jù)類型 和不同長度的變量。
union A
{
int a[5];
char b;
double c;
}Air;
表示聲明了一個名稱為A的聯(lián)合,可以使用A variable 來定義聯(lián)合變量。
在聯(lián)合變量 variable中 整型和字符型以及double型共用同一內(nèi)存位置。
當(dāng)一個聯(lián)合被說明時, 編譯程序自動地產(chǎn)生一個變量, 其長度為聯(lián)合中最大的變量長度。
聯(lián)合訪問其成員的方法與結(jié)構(gòu)相同。同樣聯(lián)合變量也可以定義成數(shù)組或指針,但定義為指針時, 也要用'->'符號, 此時聯(lián)合訪問成員可表示成:
聯(lián)合名->成員名
另外, 聯(lián)合既可以出現(xiàn)在結(jié)構(gòu)內(nèi), 它的成員也可以是結(jié)構(gòu)。
union
{
int i;
struct
{
char first;
char second;
}half;
}number;
2.結(jié)構(gòu)的定義
定義一個結(jié)構(gòu)的一般形式為:
struct 結(jié)構(gòu)名
{
成員表列
};
成員表由若干個成員組成, 每個成員都是該結(jié)構(gòu)的一個組成部分。對每個成員也必須作類型說明,其形式為:
類型說明符 成員名;
成員名的命名應(yīng)符合標識符的書寫規(guī)定。例如:
struct stu
{
int num;
char name[20];
char sex;
float score;
};
在這個結(jié)構(gòu)定義中,結(jié)構(gòu)名為stu,該結(jié)構(gòu)由4個成員組成。第一個成員為num,整型變量;第二個成員為name,字符數(shù)組;第三個成員為 sex,字符變量;第四個成員為score,實型變量。應(yīng)注意在括號后的分號是不可少的。結(jié)構(gòu)定義之后,即可進行變量說明。凡說明為結(jié)構(gòu)stu的變量都由上述4個成員組成。由此可見, 結(jié)構(gòu)是一種復(fù)雜的數(shù)據(jù)類型,是數(shù)目固定,類型不同的若干有序變量的集合。
在介紹了結(jié)構(gòu)和聯(lián)合的基本知識后,我們開始今天要講的關(guān)于sizeof對結(jié)構(gòu)體和聯(lián)合求值問題:
讓我們來看一道華為的面試題:
例:設(shè)有以下說明和定義:
typedef union {long i; int k[5]; char c;} DATE;
struct data { int cat; DATE cow; double dog;} too;
DATE max;
則語句 printf( '%d',sizeof(struct date)+sizeof(max));的執(zhí)行結(jié)果是:____
需要說明的是typedef只是一個簡單的名字替換,方便系統(tǒng)在不同的平臺上移植,請參考以下例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
typedef int Example;
Example a=10;//Equals to int a=10;
printf('%d\n',a);
system('pause');
}
解釋:假設(shè)為32位機器,那么DATE是一個union, 變量公用空間. 里面最大的變量類型是int[5], 占用20個字節(jié). 所以它的大小是20,data是一個struct,每個變量分開占用空間.依次為int4 + DATE20 + double8 = 32. 所以結(jié)果是 20 + 32 = 52.
再看下例:
設(shè)有以下說明和定義:
typedef union {double i; int k[5]; char c;} DATE;
struct data { int cat; DATE cow; double dog;} too;
DATE max;
則語句 printf( '%d',sizeof(struct date)+sizeof(max));的執(zhí)行結(jié)果是:____
在草率回答這一問題之前我們先看一個小例子:
#include <stdio.h>
#include <stdlib.h>
union A
{
int a[5];
char b;
double c;
};
int main()
{
printf('%d\n',sizeof(A));
system('pause');
}
對A用sizeof輸出卻得到的是24!union中變量共用內(nèi)存,應(yīng)以最長的為準,可是結(jié)果卻不是我們預(yù)想的20,這是因為在聯(lián)合內(nèi)變量的默認內(nèi)存對其方式,必須以最長的double8字節(jié)對齊,也就是說故應(yīng)該是sizeof(A)=24;所以我們將聯(lián)合中的int a[5] 修改成 int a[6] 結(jié)果仍然不變,但如果我們將int a[5]修改成 int a[7],結(jié)果就變成了 32了。
C語言sizeOf() 結(jié)構(gòu)體對齊策略
struct AA
{
int num,*a;
char d;
double e;
};
int i1 = sizeof(int);//占用4個字節(jié)
int i2 = sizeof(int*);//占用4個字節(jié)
int i3 = sizeof(char);//占用1個字節(jié)
int i4 = sizeof(double);//占用8個字節(jié)
int i5 = sizeof(AA);//占用24字節(jié)
為什么結(jié)構(gòu)體AA不是只用其成員的總和值4+4+1+8呢?
因為根據(jù)Win32平臺下的微軟 編譯器(cl.exe for 80×86)的對齊策略:
1) 結(jié)構(gòu)體變量的首地址能夠被其最寬基本類型成員的大小所整除;
備注:編譯器在給結(jié)構(gòu)體開辟空間時,首先找到結(jié)構(gòu)體中最寬的基本數(shù)據(jù)類型,然后尋找內(nèi)存地址能被該基本數(shù)據(jù)類型所整除的位置,作為結(jié)構(gòu)體的首地址。將這個最寬的基本數(shù)據(jù)類型的大小作為上面介紹的對齊模數(shù)。
2) 結(jié)構(gòu)體每個成員相對于結(jié)構(gòu)體首地址的偏移量(offset)都是成員大小的整數(shù)倍,如有需要編譯器會在成員之間加上填充字節(jié)(internal adding);
備注:為結(jié)構(gòu)體的一個成員開辟空間之前,編譯器首先檢查預(yù)開辟空間的首地址相對于結(jié)構(gòu)體首地址的偏移是否是本成員的整數(shù)倍,若是,則存放本成員,反之,則在本成員和上一個成員之間填充一定的字節(jié),以達到整數(shù)倍的要求,也就是將預(yù)開辟空間的首地址后移幾個字節(jié)。
3) 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要,編譯器會在最末一個成員之后加上填充字節(jié)(trailing padding)。
備注:結(jié)構(gòu)體總大小是包括填充字節(jié),最后一個成員滿足上面兩條以外,還必須滿足第三條,否則就必須在最后填充幾個字節(jié)以達到本條要求。
是不是有點云里霧里呢?那讓我們一起來遵循規(guī)律看下來吧
其中int i1 = sizeof(int)//偏移量為0 占用4個字節(jié)的長度
int i2 = sizeof(int*)//下一個偏移量為4(滿足規(guī)則2)是i2=4 的整數(shù)倍
int i3 = sizeof(char);//下一個偏移量為8(滿足規(guī)則2)是i=1 的整數(shù)倍
int i4 = sizeof(double);//下一個偏移量為9(不滿足規(guī)則1)不能被 i4 = 8所整除,所以應(yīng)當(dāng)9+7 = 16
所以總數(shù)為16+i4(8)= 24(滿足條件3)
所以為24
是否已經(jīng)明白呢?
不凡再看以下代碼
struct MyStruct
{
char dda;
//偏移量為0,滿足對齊方式,dda占用1個字節(jié); double dda1;
//下一個可用的地址的偏移量為1,不是sizeof(double)=8
//的倍數(shù),需要補足7個字節(jié)才能使偏移量變?yōu)?(滿足對齊
//方式),因此VC自動填充7個字節(jié),dda1存放在偏移量為8
//的地址上,它占用8個字節(jié)。 int type;
//下一個可用的地址的偏移量為16,是sizeof(int)=4的倍
//數(shù),滿足int的對齊方式,所以不需要VC自動填充,type存
//放在偏移量為16的地址上,它占用4個字節(jié)。
};
//所有成員變量都分配了空間,空間總的大小為1+7+8+4=20,不是結(jié)構(gòu)
//的節(jié)邊界數(shù)(即結(jié)構(gòu)中占用最大空間的類型所占用的字節(jié)數(shù)sizeof
//(double)=8)的倍數(shù),所以需要填充4個字節(jié),以滿足結(jié)構(gòu)的大小為
//sizeof(double)=8的倍數(shù)。
其中要是不想遵循結(jié)構(gòu)體對齊策略也可以用 #pragma pack 去掉內(nèi)存對齊,去掉的話,應(yīng)該會影響到效率吧。萬一在嵌入式下的話,有可能導(dǎo)致程序出錯。