結構體(記憶體對齊)和共用體—C語言
結構體
C語言學到現在,相信大家已經熟知了基本型別(整型、實型、字元型)的變數和一種構造型別資料(陣列),但是隻有這些資料型別是不夠的,因此我們接下來介紹C語言中可以將不同型別的定義自己的資料型別——結構體。
結構體與陣列的比較
由於結構體和陣列有很大的類似之處,所以我們首先來說說結構體與陣列異同:
- 都由多個元素組成
- 各個元素在記憶體中的儲存空間是連續的
- 陣列中各個元素的資料型別相同,而結構體中的各個元素的資料型別可以不相同
結構體的定義和使用
1. 結構體的定義語法:
struct 結構體名
{
型別名1 成員名1;
型別名2 成員名2;
...
型別名 n 成員名n;
};
剛開始學的童鞋一定要注意,結構體是一種資料型別而不是一個變數,既然是一種資料型別,我們下來就講講定義結構體型別變數的幾種方法。
2. 定義結構體型別變數的幾種方法:
- 定義結構體型別時,同時定義該型別的變數
struct [student] /* [ ]表示結構體名是可選的 */
{
char name[10];
char sex;
int age;
float score;
}stu1, *ps, stu[5]; /* 定義結構體型別的普通變數、指標變數和陣列 */
- 先定義結構體型別,再定義該型別的變數
struct student
{
char name[10];
char sex;
int age;
float score;
};
struct student stu1, *ps, stu[5]; /* 定義結構體型別的普通變數、指標變數和陣列 */
- 用型別定義符typedef先給結構體型別命別名,再用別名定義變數
typedef struct [student]
{
char name[10];
char sex;
int age;
float score;
}STU;
STU stu1, *ps, stu[5]; /* 用別名定義結構體型別的普通變數、指標變數和陣列 */
再次給初學的童鞋說明一下:
- 型別和變數不要混淆,只能對變數賦值、存取或運算,並且在編譯時,對型別是不分配空間的,只對變數分配空間。
- 結構體的成員也可以是一個結構體變數
- 結構體中的成員名可以與程式中的變數名相同,二者不代表同一物件。(如變數num和struct student中的num是兩回事)
3. 結構陣列和結構指標:
- 結構陣列:該陣列的每一個元素都是相同的結構體型別
- 結構指標:是指向結構的指標。由一個加在結構變數名前的"*" 操作符來定義
4. 結構體變數成員的引用:
在定義了結構體變數以後,當然可以引用這個結構體,但不能將一個結構體變數作為一個整體進行操作,只能對結構體變數中的某個成員進行操作,引用方式如下:
- 結構體指標變數->成員名: ps->name
- (*結構體指標變數). 成員名: (*ps).name
- 結構體變數陣列名. 成員名: stu[0].name
5. 結構體變數的初始化
和其他型別一樣,對結構體變數可以在定義時指定初始值:
struct [student]
{
char name[10];
char sex;
int age;
float score;
}stu[2]={{"SAN", 'F', 22, 90.5}, {"GI", 'M', 20, 88.5}};
結構體記憶體對齊問題
C語言結構體對齊也是老生常談的話題了,也算是結構體中的一個小難點,內容雖然很基礎,但一不小心就會弄錯。
記憶體對齊的規則很簡單:
- 起始地址為該變數型別所佔記憶體的整數倍,若不足則不足部分用資料填充至所佔記憶體的整數倍。
- 該結構體所佔總記憶體為結構體成員變數中最大資料型別的整數倍。
接下來我們看一個例子:
struct str1
{
char a;
int b;
float c;
double d;
};
str1這個結構體佔用的記憶體是多少呢?如果用變數型別直接想加,得到的結果是17,但顯然不是這樣的。這個程式執行的正確結果是24。為什麼呢?
分析如下:
char型變數佔一個位元組,所以它的起始地址為0,
而int型別佔4個位元組,它的起始地址應該是4(的整數倍),那麼記憶體地址1、2、3就需要被填充。
float佔用4個位元組,而結構體中a,b兩個成員變數佔了0到7記憶體地址,c的地址從8開始,符合規則一,佔用記憶體地址為8到11。
double型別佔8個位元組,所以d的起始地址就應該從16開始,那麼12、13、14、15記憶體地址就需要被填充。d從16地址開始,佔用8個位元組。
整個結構體佔用位元組數為24,符合規則二。記憶體分配如圖:紅色區域為填充部分
--
總之結構體對齊可以總結為三個基本原則:
- 資料成員對齊規則:結構體的資料成員中,第一個成員從offset為0的地址開始,以後每一個成員儲存的起始位置為該成員大小的整數倍
- 結構體作為成員: 如果一個結構體1作為另一個結構體2的資料成員,則在結構體2中結構體1要從1內部成員最大的整數倍地址開始儲存。
- 結構體的總大小(sizeof): 為該結構體內部最大基本型別的整數倍,不足的要補齊,而不是簡單的所有成員的大小總和。
再舉一個例子:
struct str2
{
double a;
int b;
char c;
double d;
};
str2這個結構體佔用的記憶體空間是多少呢?是24!
分析如下:
首先double型別的a佔用記憶體地址為0~7
int型別的b起始地址為8,符合規則一,佔用地址為8~11
char型別的c佔一個位元組,地址為12
那麼double型別的d,起始地址為13嗎?顯然不是,滿足規則一的地址是16,所以d起始地址為16,佔用16到23。
結構體總共24個位元組,滿足規則二。
但是如果這個結構體最後再加一個成員變數 char e,那這個結構體佔用的記憶體是多少?
分析:
char型別的e起始地址為24,佔用地址為24,但是結構體一共有25個位元組,就不滿足規則二了,怎麼辦呢?
為了滿足規則二,我們將25~31進行填充,因此整個結構體佔用32個位元組。
共用體
C語言中還有一種將不同型別的幾種變數存放到同一段記憶體單元中。這幾個不同的變數共同佔用同一段記憶體結構的資料型別———共用體
- 共用體型別變數的定義
union 共用體名
{
成員列表;
}變數列表;
- 共用體變數的引用 和結構體變數一樣,共用體變數也不能被直接引用,而只能引用共用體變數中的成員。
- 共用體同一個記憶體段可以用來存放幾種不同型別的成員,但是在每一瞬間只能存放其中的一種,而不是同時存放幾種。換句話說,每一瞬間只有一個成員起作用,其他的成員不起作用,即不是同時都在存在和起作用。
- 共用體變數的地址和它的各個成員的地址都是同樣的
- 共用體變數中起作用的成員是最後一次被賦值的成員。
- 共用體型別可以出現在結構體型別的定義中,也可以定義共用體陣列。反之,結構體也可以出現在共用體型別的定義中,陣列也可以作為共用體的成員。
結構體與共用體並不難,結構體更加重要一些,用的也多一點,尤其要格外掌握結構體的記憶體對齊問題