首頁 > 軟體

詳解C語言結構體,列舉,聯合體的使用

2022-07-15 14:06:04

一、匿名結構體

struct
{
    char name[20];
    int age;
}s1;

匿名結構體物件s1過了這一行即銷燬。

二、結構體的自參照

1、宣告時不要自己參照自己

struct Node
{
 int data;
 struct Node next;//錯誤的,嚴禁自己參照自己
};
 
 
struct Node
{
 int data;
 struct Node* next;//正確的參照方式
};

2、結構體重新命名時不能使用重新命名

typedef struct
{
 int data;
 Node* next;//錯誤的,不要再重新命名中使用重新命名
}Node;
 
typedef struct Node
{
 int data;
 struct Node* next;//正確的
}Node;

博主在學資料結構的時候踩過這個坑,在結構體重新命名的時候成員變數的型別就使用了重新命名,導致整個程式不認識這個成員變數的型別(但是vs在typedef這裡不報錯,而是在每個使用這個型別的地方報錯!!!)。後來把這個成員變數的型別修改為重新命名之前的型別,整個程式就可以執行了。(如上圖的正確寫法)

三、結構體記憶體對齊規則

第一個成員在與結構體變數偏移量為0的地址處。

後續成員變數要放到各自的對齊數的倍數上。對齊數 = 編譯器預設對齊數與該成員型別大小的較小值。(vs中預設對齊數是8,gcc沒有預設對齊數)

結構體最終大小為最大對齊數的整數倍。

如果巢狀了結構體的情況,巢狀的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含巢狀結構體的對齊數)的整數倍。

1、結構體記憶體計算

struct S1
{
    char c1;
    int i;
    char c2;
};
int main()
{
    printf("%dn", sizeof(struct S1));
    return 0;
}

char c1在結構體變數的零偏移量處分配記憶體

int i的對齊數為4,所以跳過3個位元組,在4的整數倍地址處分配記憶體

char c2的對齊數為1,使用下一個位元組空間即可

目前已使用9位元組

由於該結構體中所有成員變數中最大的成員型別大小為4位元組,所以最大記憶體對齊數為4位元組,結構體總大小為最大對齊數的整數倍。所以該結構體記憶體為12位元組。

可以使用宏offsetof來觀察結構體成員在記憶體中的偏移量:

2、結構體巢狀

struct S3//16
{
 double d;
 char c;
 int i;
};
struct S4//32
{
 char c1;
 struct S3 s3;
 double d;
};

char c1在結構體變數的零偏移量處分配記憶體

struct S3 s3按照其最大記憶體對齊數(此處為8)進行對齊

double d按照其最大記憶體對齊數(8)進行對齊

S4的最大記憶體對齊數為8,所以結構體的最終大小為32

3、通過調整結構體成員順序,壓縮記憶體

通過上述例子可以發現,結構體成員之間有很大的空間浪費,哪怕是擁有相同結構體成員的兩個結構體型別,其在記憶體中所佔據的空間也不相同,所以為了空間的節省,在不影響資料結構的情況下,有目的的把位元組佔用小的成員變數放在一起,達到節省空間的目的。

四、存在記憶體對齊的原因

1. 平臺原因(移植原因)

不是所有的硬體平臺都能存取任意地址上的任意資料的;某些硬體平臺只能在某些地址處取某些特定型別的資料,否則丟擲硬體異常。

2. 效能原因(空間換時間)

資料結構(尤其是棧)應該儘可能地在自然邊界上對齊。 原因在於,為了存取未對齊的記憶體,處理器需要作兩次記憶體存取;而對齊的記憶體存取僅需要一次存取。

五、修改預設對齊數

#pragma pack(2)//把預設對齊數改成2
struct S
{
    char c1;
    int i;
    short c2;
};
#pragma pack()//恢復預設對齊數為8
int main()
{
    printf("%dn", sizeof(struct S));
    return 0;
}
#pragma pack(num)修改預設對齊數,該結構體的記憶體大小由12位元組降低為8位元組。

預設對齊數儘量為2的次方。

六、結構體傳參

結構體傳參要傳地址。

傳址呼叫優於傳值呼叫的原因是地址佔4/8個位元組。

但是傳值呼叫引數需要壓棧,當結構體過大時,引數壓棧的系統開銷較大。

七、位元欄

位元欄是在結構體中實現的。

位元欄的成員可以是 int、unsigned int、signed int或者是char型別

位元欄的空間增長方式為每次增長4個位元組(int)或1個位元組(char)

位元欄涉及很多不確定因素,位元欄是不跨平臺的,注重可移植的程式應該避免使用位元欄。

1、位元欄在記憶體中的儲存

1.1位元欄中char型別的儲存方式(vs中捨棄剩餘空間)

struct S//佔用3個位元組
{
    char _a : 3;
    char _b : 4;
    char _c : 5;
    char _d : 4;
};
int main()
{
    printf("%dn", sizeof(struct S));
    struct S s= { 0 };
    s._a = 10;//1010,截斷為010
    s._b = 12;//1100
    s._c = 3;//0011
    s._d = 4;//0100
    return 0;
}

通過呼叫記憶體發現,&s中儲存的16進位制數位為62 03 04,那麼可以發現s在記憶體中的儲存方式如下圖:

在vs環境中,char成員變數在單個位元組中是倒著儲存的(有截斷先發生截斷),當該位元組中剩餘的位元位不足以存放下一個完整的成員變數時,會將剩餘的位元位捨棄。

1.2位元欄中int型別的儲存方式(vs中利用剩餘空間)

struct A//佔4個位元組
{
    int a : 2;
    int b : 3;
    int c : 4;
};
int main()
{
    struct A s = { 0 };
    s.a = 12;//1100,截斷為00
    s.b = 13;//1101,截斷為101
    s.c = 14;//1110
    return 0;
}

通過呼叫記憶體發現,&s中儲存的16進位制數位為d4 01 00 00,那麼可以發現s在記憶體中的儲存方式如下圖:

在vs環境中,int成員變數在單個位元組中是倒著儲存的(有截斷先發生截斷),當該位元組中剩餘的位元位不足以存放下一個完整的成員變數時,會將繼續儲存,存不下的二進位制位將存放至下一個位元組的右側。

注意:位元欄冒號後面的數位只能小於等於型別大小(例如char a:9是錯誤的)

2、位元欄的跨平臺問題

1.int 位元欄被當成有符號數還是無符號數是不確定的。

2. 位元欄中最大位的數目不能確定。(16位元機器最大16,32位元機器最大32,寫成27,在16位元機器會出問題。

3. 位元欄中的成員在記憶體中從左向右分配,還是從右向左分配標準尚未定義。

4. 當一個結構包含兩個位元欄,第二個位元欄成員比較大,無法容納於第一個位元欄剩餘的位時,是捨棄剩餘的位還是利用,這是不確定的。

八、列舉

1、列舉的定義

enum color
{
    RED,//列舉常數
    BLUE,
    YELLOW
};

不賦值那麼預設從0開始,後續列舉元的值遞增1

enum color
{
    RED=1,
    BLUE,
    YELLOW
};

只需要對第一個成員進行賦值,後續列舉元的值遞增1

在寫列舉元的時候建議全大寫,博主在寫通訊錄列舉了exit,使用時vs提示該命名和exit函數衝突。

2、列舉的優點

增加程式碼的可讀性和可維護性

列舉使用時有型別檢查,#define定義的識別符號沒有

防止了命名汙染(封裝)

便於偵錯(#define定義宏在預處理時是直接替換)

使用方便,一次可以定義多個常數

九、聯合體(共用體)

聯合也是一種特殊的自定義型別 這種型別定義的變數也包含一系列的成員,特徵是這些成員共用同一塊空間(所以聯合也叫共用體)。

1、聯合體大小的計算

#include <stdio.h>
union un
{
    char arr[5];
    int a;
}u;
int main()
{
    printf("%d", sizeof(u));//8
    return 0;
}

聯合體的大小至少是最大成員的大小。

最大記憶體對齊數的整數倍要大於等於最大成員的大小。

(這裡最大成員是arr,佔5個位元組,最大記憶體對齊數是4,所以需要為祖國聯合體開闢8個位元組空間)

2、使用聯合體判斷計算機的大小端位元組序

#include <stdio.h>
union un
{
    int m;
    char n;
}u;
int check_sys()
{
    u.m = 1;
    return u.n;
}
 
int main()
{
    int a = check_sys();
    if (a == 1)
        printf("小端儲存n");
    else
        printf("大端儲存n");
    return 0;
}

以上就是詳解C語言結構體,列舉,聯合體的使用的詳細內容,更多關於C語言 結構體 列舉 聯合體的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com