- 結(jié)構(gòu)體有名定義
- 無名定義
- 結(jié)構(gòu)體嵌套定義
- 結(jié)構(gòu)體內(nèi)存對(duì)齊
- 結(jié)構(gòu)體成員初始化
- 結(jié)構(gòu)體變量引用
- 結(jié)構(gòu)體的有名定義:直白點(diǎn)就是在struct 之后跟著結(jié)構(gòu)體的名稱吨些,如下:
struct str_test{
int a;
char b;
};
- 結(jié)構(gòu)體的無名定義:注意與有名定義的區(qū)別盯桦。這種方式不提倡,但是系統(tǒng)支持
struct {
int c旋圆;
char e;
}var1麸恍,var2灵巧; //結(jié)構(gòu)體定義完畢直接定義變量。
- 結(jié)構(gòu)體的嵌套定義:
struct str_test3{
int length;
int wide;
int high;
struct str_test4{
int id;
struct str_test3 var;
};
}孩等;//這種也可以艾君,但是不提倡使用。
struct str_test3{
int length;
int wide;
int high;
}肄方;
struct str_test4{
int id;
struct str_test3 var;
}冰垄;//是最常用方式,結(jié)構(gòu)體互相獨(dú)立权她;
- 結(jié)構(gòu)體內(nèi)存對(duì)齊
1)虹茶、平臺(tái)原因:不是所有的硬件平臺(tái)都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù)隅要,否則拋出硬件異常蝴罪。
2)、性能原因:數(shù)據(jù)結(jié)構(gòu)應(yīng)該盡可能地在自然邊界上對(duì)齊步清。原因在于要门,為了訪問未對(duì)齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問廓啊;而對(duì)齊的內(nèi)存訪問僅需要一次訪問欢搜。
這些問題既和軟件相關(guān)又和硬件相關(guān)。
- 所謂軟件相關(guān)主要是指和具體的編程語(yǔ)言的編譯器的特性相關(guān)谴轮,編譯器為了優(yōu)化CPU訪問內(nèi)存的效率炒瘟,在生成結(jié)構(gòu)體成員的起始地址時(shí)遵循著某種特定的規(guī)則,這就是所謂的 結(jié)構(gòu)體成員“對(duì)齊”第步;
- 所謂硬件相關(guān)主要是指CPU的“字節(jié)序”問題疮装,也就是大于一個(gè)字節(jié)類型的數(shù)據(jù)如int類型、short類型等粘都,在內(nèi)存中的存放順序廓推,即單個(gè)字節(jié)與高低地址的對(duì)應(yīng)關(guān)系。
字節(jié)序分為兩類:Big-Endian和Little-Endian驯杜,有的文章上稱之為“大端”和“小端”受啥,他們是這樣定義的:
Little-Endian就是低位字節(jié)排放在內(nèi)存的低地址端,高位字節(jié)排放在內(nèi)存的高地址端鸽心;
Big-Endian就是高位字節(jié)排放在內(nèi)存的低地址端滚局,低位字節(jié)排放在內(nèi)存的高地址端。
最本質(zhì)原因是基本數(shù)據(jù)類型的長(zhǎng)度有大有小顽频,在使用的時(shí)候不可能長(zhǎng)短一樣藤肢,但CPU為了提高效率需要大批量一次訪問多個(gè)內(nèi)存區(qū),可能存在同一個(gè)數(shù)據(jù)由于沒有字節(jié)對(duì)齊而出現(xiàn)跨字節(jié)訪問糯景,這樣效率會(huì)下降嘁圈,甚至有些CPU會(huì)異常省骂,比如一個(gè)大小固定的盒子如果要放進(jìn)去不整齊的筷子,就會(huì)很麻煩要么折斷要么換盒子最住,最好就是將筷子對(duì)齊同時(shí)放入盒子钞澳。
其中筷子也有大頭和小頭之分,具體怎么放那是一個(gè)約定涨缚,只要整體系統(tǒng)都采用統(tǒng)一的大頭或小頭就行轧粟。
我們?cè)谧龊凶臃趴曜拥脑囼?yàn)的時(shí)候,很多時(shí)候是我們?nèi)斯た勺R(shí)別的脓魏,能很智能完成復(fù)雜操作兰吟,那么計(jì)算機(jī)在處理數(shù)據(jù)的過程中的字節(jié)是怎么對(duì)齊的呢?
首先我們的程序在編譯階段茂翔,編譯器會(huì)稍微做些智能化的工作混蔼,將內(nèi)存的分配過程中按照一定的規(guī)則對(duì)齊,GCC 默認(rèn)的對(duì)齊字節(jié)是 4字節(jié)珊燎, vc 默認(rèn)的對(duì)齊字節(jié)是 8字節(jié)惭嚣。
在編譯過程中有個(gè)開關(guān)可以控制對(duì)齊的字節(jié)大小: #pragma pack(n) 這個(gè)宏就是來控制編譯過程中的字節(jié)對(duì)齊用的悔政,其中的 n 就是你可定制的對(duì)齊字節(jié)大小料按,如果不設(shè)置就按默認(rèn)值處理。
我們可以編寫程序來驗(yàn)證字節(jié)對(duì)齊的神奇:
struct A
{
char a;
int c;
};
問 如上結(jié)構(gòu)體的大小是多少卓箫?為什么? 這是一個(gè)企業(yè)面試題垄潮,類似的面試題多不勝數(shù)烹卒。
將這個(gè)結(jié)構(gòu)體編寫程序跑起來看看就知道了,在運(yùn)行之前我們按照以前學(xué)習(xí)的基本知識(shí)計(jì)算出 結(jié)果應(yīng)該是:char =1 int =4 所以結(jié)果應(yīng)該是5
但是非常抱歉弯洗,程序的運(yùn)行結(jié)果卻是:8 旅急,到底是我們計(jì)算錯(cuò)誤還是計(jì)算機(jī)出故障了?其實(shí)我們都沒錯(cuò)牡整,計(jì)算機(jī)也沒錯(cuò) 藐吮,就是字節(jié)對(duì)齊的起作用。
編譯器在編譯代碼的過程中逃贝,根據(jù)如下原則對(duì)數(shù)據(jù)對(duì)齊
- 數(shù)據(jù)成員對(duì)齊規(guī)則:結(jié)構(gòu)(struct或聯(lián)合union)的數(shù)據(jù)成員谣辞,第一個(gè)數(shù)據(jù)成員放在offset為0的地方,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置要從該成員大小的整數(shù)倍開始(比如int在32位機(jī)為4字節(jié)沐扳,則要從4的整數(shù)倍地址開始存儲(chǔ))泥从。
- 結(jié)構(gòu)體作為成員:如果一個(gè)結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲(chǔ)沪摄。(struct a里存有struct b躯嫉,b里有char纱烘,int,double等元素祈餐,那b應(yīng)該從8的整數(shù)倍開始存儲(chǔ)擂啥。)
原則- 收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果帆阳,必須是其內(nèi)部最大成員的整數(shù)倍哺壶,不足的要補(bǔ)齊。
- 結(jié)構(gòu)體中的字節(jié)對(duì)齊舱痘,如果成員的大小大于等于4字節(jié)变骡,則以4字節(jié)對(duì)齊,然后再排列下一個(gè)成員芭逝。如果兩個(gè)成員的大小加起來大于四字節(jié)塌碌,則這兩個(gè)相鄰成員仍分別按照四字節(jié)對(duì)齊。僅當(dāng)兩個(gè)相鄰成員的總大小在四字節(jié)內(nèi)的時(shí)候旬盯,才會(huì)一起按照四字節(jié)對(duì)齊台妆。
Demo:
typedef struct {
char a;
int b;
short c;
}Demo1;
Demo1 tmp;
sizeof(Demo1)=12
("%p", &tmp.a) = 0
("%p", &tmp.b) = 4
("%p", &tmp.c) = 8
typedef struct {
char a;
short b;
int c;
}Demo2;
Demo2 tmp;
sizeof(Demo2)=8
("%p", &tmp.a) = 0
("%p", &tmp.b) = 2
("%p", &tmp.c) = 4
typedef struct {
char a;
char b[4];
short c; //int c
}Demo3;
Demo3 tmp;
sizeof(Demo3)=8 // 12
("%p", &tmp.a) = 0
("%p", &tmp.b) = 1
("%p", &tmp.c) = 6
所以我們?cè)谑褂媒Y(jié)構(gòu)體的過程中一定要注意它的這個(gè)特性,尤其是在需要用到結(jié)構(gòu)體大小的地方胖翰,一定要考慮字節(jié)對(duì)齊產(chǎn)生的影響接剩。
1)聯(lián)合體是一個(gè)結(jié)構(gòu);
2)它的所有成員相對(duì)于基地址的偏移量都為0萨咳;
3)此結(jié)構(gòu)空間要大到足夠容納最"寬"的成員懊缺;
4)其對(duì)齊方式要適合其中所有的成員;
- 結(jié)構(gòu)體成員初始化
struct str_test
{
int a;
int b;
};
struct str_test var = {10,20}; ///結(jié)構(gòu)體定義與變量定義分離培他,變量的初始化一次性完成
struct str_test var2 = {.a=30}; ///結(jié)構(gòu)體變量的成員可以單獨(dú)指定
等價(jià)
struct str_test var2 ;
var2.a =30;
struct str_test2
{
int aa;
int bb;
struct str_test cc;
};
struct str_test2 var3 = {10,20,{30,40}}; ///嵌套結(jié)構(gòu)體初始化一次性完成
struct str_test2 var5 = {.cc.a =10}; ///嵌套結(jié)構(gòu)體成員變量的逐層賦值
struct str_test3
{
int a;
int b;
}var6,var7; ///定義結(jié)構(gòu)體的時(shí)候定義變量
struct str_test4
{
int a;
int b;
}var8 = {100,200}; ///定義結(jié)構(gòu)體的時(shí)候定義變量并初始化
- 結(jié)構(gòu)體變量的引用:
1鹃两、變量名.成員 ;
2舀凛、指針變量 ->成員俊扳;
struct str_test var;
var.a =10;
var.b =20;
struct str_test pvar = NULL; ///結(jié)構(gòu)體指針與基本數(shù)據(jù)類型的指針一樣,必須指向一個(gè)實(shí)際的內(nèi)存地址猛遍。
pvar = (strcut str_test *) malloc(sizeof(struct str_test));
或者 定義變量同時(shí)定義指針:
struct str_test var, *pvar; pvar = &var;