1. 程式人生 > >結構體(記憶體對齊)和共用體—C語言

結構體(記憶體對齊)和共用體—C語言

結構體

C語言學到現在,相信大家已經熟知了基本型別(整型、實型、字元型)的變數和一種構造型別資料(陣列),但是隻有這些資料型別是不夠的,因此我們接下來介紹C語言中可以將不同型別的定義自己的資料型別——結構體。

結構體與陣列的比較

由於結構體和陣列有很大的類似之處,所以我們首先來說說結構體與陣列異同:

  1. 都由多個元素組成
  2. 各個元素在記憶體中的儲存空間是連續的
  3. 陣列中各個元素的資料型別相同,而結構體中的各個元素的資料型別可以不相同

結構體的定義和使用

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]; /* 用別名定義結構體型別的普通變數、指標變數和陣列 */

再次給初學的童鞋說明一下:

  1. 型別和變數不要混淆,只能對變數賦值、存取或運算,並且在編譯時,對型別是不分配空間的,只對變數分配空間。
  2. 結構體的成員也可以是一個結構體變數
  3. 結構體中的成員名可以與程式中的變數名相同,二者不代表同一物件。(如變數num和struct student中的num是兩回事)

3. 結構陣列和結構指標:

  1. 結構陣列:該陣列的每一個元素都是相同的結構體型別
  2. 結構指標:是指向結構的指標。由一個加在結構變數名前的"*" 操作符來定義

4. 結構體變數成員的引用:

在定義了結構體變數以後,當然可以引用這個結構體,但不能將一個結構體變數作為一個整體進行操作,只能對結構體變數中的某個成員進行操作,引用方式如下:

  1. 結構體指標變數->成員名: ps->name
  2. (*結構體指標變數). 成員名: (*ps).name
  3. 結構體變數陣列名. 成員名: 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語言結構體對齊也是老生常談的話題了,也算是結構體中的一個小難點,內容雖然很基礎,但一不小心就會弄錯。
記憶體對齊的規則很簡單

  1. 起始地址為該變數型別所佔記憶體的整數倍,若不足則不足部分用資料填充至所佔記憶體的整數倍。
  2. 該結構體所佔總記憶體為結構體成員變數中最大資料型別的整數倍。

接下來我們看一個例子:

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,符合規則二。記憶體分配如圖:紅色區域為填充部分

--

總之結構體對齊可以總結為三個基本原則:

  1. 資料成員對齊規則:結構體的資料成員中,第一個成員從offset為0的地址開始,以後每一個成員儲存的起始位置為該成員大小的整數倍
  2. 結構體作為成員: 如果一個結構體1作為另一個結構體2的資料成員,則在結構體2中結構體1要從1內部成員最大的整數倍地址開始儲存。
  3. 結構體的總大小(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語言中還有一種將不同型別的幾種變數存放到同一段記憶體單元中。這幾個不同的變數共同佔用同一段記憶體結構的資料型別———共用體

  1. 共用體型別變數的定義
union   共用體名
{
成員列表;
}變數列表;

  1. 共用體變數的引用 和結構體變數一樣,共用體變數也不能被直接引用,而只能引用共用體變數中的成員。
  2. 共用體同一個記憶體段可以用來存放幾種不同型別的成員,但是在每一瞬間只能存放其中的一種,而不是同時存放幾種。換句話說,每一瞬間只有一個成員起作用,其他的成員不起作用,即不是同時都在存在和起作用。
  3. 共用體變數的地址和它的各個成員的地址都是同樣的
  4. 共用體變數中起作用的成員是最後一次被賦值的成員。
  5. 共用體型別可以出現在結構體型別的定義中,也可以定義共用體陣列。反之,結構體也可以出現在共用體型別的定義中,陣列也可以作為共用體的成員。

結構體與共用體並不難,結構體更加重要一些,用的也多一點,尤其要格外掌握結構體的記憶體對齊問題