目錄
- 1.結構體類型的聲明
- 1.1 結構體回顧
- 1.1.1 結構的聲明
- 特殊的結構聲明
- 1.3 結構的?引?
- 2. 結構體內存的對齊
- 2.2 為什么存在內存對??
- 2.3 修改默認對?數
- 3. 結構體傳參
- 4. 結構體實現位段
- 4.1 什么是位段
- 4.2 位段的內存分配
- 4.3 位段的跨平臺問題
- 4.5 位段使?的注意事項
正文開始
1.結構體類型的聲明
前面我們在學習操作符的時候,已經學習了結構體的知識,這里稍微復習一下。
1.1 結構體回顧
結構是一些值的集合,這些值稱為成員變量。結構的每個成員可以是不同類型的變量。
1.1.1 結構的聲明
struct tag
{member-list;
}variable-list;
例如描述一個學生:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct Stu
{char name[20];//名字int age;//年齡char sex[5];//性別char id[20];//學號
};int main()
{//按照結構體成員的初始化struct Stu s = { "張三", 20, "男", "20230818001" };printf("name:%s\n", s.name);printf("age:%d\n", s.age);printf("sex:%s\n", s.sex);printf("id:%s\n", s.id);//按照指定的順序初始化struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "女" };printf("name:%s\n", s2.name);printf("age:%d\n", s2.age);printf("id:%s\n", s2.id);printf("sex:%s\n", s2.sex);return 0;
}
特殊的結構聲明
在聲明結構的時候,可以不完全的聲明。比如:
//匿名結構體類型
struct
{int a;char b;float c;
}x;
struct
{int a;char b;float c;
}a[20], *p;
上?的兩個結構在聲明的時候省略掉了結構體標簽(tag)。那么問題來了?
//在上?代碼的基礎上,下?的代碼合法嗎?
p = &x;
警告:
編譯器會把上?的兩個聲明當成完全不同的兩個類型,所以是?法的。匿名的結構體類型,如果沒有對結構體類型重命名的話,基本上只能使??次。
1.3 結構的?引?
在結構中包含?個類型為該結構本?的成員是否可以呢?
?如,定義?個鏈表的節點:
struct Node
{int data;struct Node next;
};
上述代碼正確嗎?如果正確,那 sizeof(struct Node) 是多少?
仔細分析,其實是不?的,因為?個結構體中再包含?個同類型的結構體變量,這樣結構體變量的??就會?窮的?,是不合理的。正確的?引??式:
struct Node
{int data;struct Node* next;
};
在結構體?引?使?的過程中,夾雜了 typedef 對匿名結構體類型重命名,也容易引?問題,看看下?的代碼,可?嗎?
typedef struct
{int data;Node* next;
}Node;
答案是不?的,因為Node是對前?的匿名結構體類型的重命名產?的,但是在匿名結構體內部提前使?Node類型來創建成員變量,這是不?的。
解決?案如下:定義結構體不要使?匿名結構體了
typedef struct Node
{int data;struct Node* next;
}Node;
2. 結構體內存的對齊
我們已經掌握了結構體的基本使?了。
現在我們深?討論?個問題:計算結構體的??。
這也是?個特別熱?的考點: 結構體內存對?
2.1 對?規則
?先得掌握結構體的對?規則:
- 結構體的第?個成員對?到和結構體變量起始位置偏移量為0的地址處
- 其他成員變量要對?到某個數字(對?數)的整數倍的地址處。
對?數 = 編譯器默認的?個對?數 與 該成員變量??的較?值。
- VS 中默認的值為 8
- Linux中 gcc 沒有默認對?數,對?數就是成員??的??
- 結構體總??為最?對?數(結構體中每個成員變量都有?個對?數,所有對?數中最?的)的
整數倍。 - 如果嵌套了結構體的情況,嵌套的結構體成員對?到??的成員中最?對?數的整數倍處,結構
體的整體??就是所有最?對?數(含嵌套結構體中成員的對?數)的整數倍。
//練習1
struct S1
{char c1;int i;char c2;
};
printf("%d\n", sizeof(struct S1));
//練習2
struct S2
{char c1;char c2;int i;
};
printf("%d\n", sizeof(struct S2));
//練習3
struct S3
{double d;char c;int i;
};
printf("%d\n", sizeof(struct S3));
//練習4-結構體嵌套問題
struct S4
{char c1;struct S3 s3;double d;
};
printf("%d\n", sizeof(struct S4));
2.2 為什么存在內存對??
?部分的參考資料都是這樣說的:
1. 平臺原因 (移植原因):
不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。
2. 性能原因:
數據結構(尤其是棧)應該盡可能地在?然邊界上對?。原因在于,為了訪問未對?的內存,處理器需要作兩次內存訪問;?對?的內存訪問僅需要?次訪問。假設?個處理器總是從內存中取8個字節,則地址必須是8的倍數。如果我們能保證將所有的double類型的數據的地址都對?成8的倍數,那么就可以
??個內存操作來讀或者寫值了。否則,我們可能需要執?兩次內存訪問,因為對象可能被分放在兩個8字節內存塊中。
總體來說:結構體的內存對?是拿空間來換取時間的做法。
那在設計結構體的時候,我們既要滿?對?,?要節省空間,如何做到:
讓占?空間?的成員盡量集中在?起
//例如:
struct S1
{char c1;int i;char c2;
};struct S2
{char c1;char c2;int i;
};
S1 和 S2 類型的成員?模?樣,但是 S1 和 S2 所占空間的??有了?些區別。
2.3 修改默認對?數
#pragma 這個預處理指令,可以改變編譯器的默認對?數。
#include <stdio.h>
#pragma pack(1)//設置默認對?數為1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消設置的對?數,還原為默認
int main()
{//輸出的結果是什么?printf("%d\n", sizeof(struct S));return 0;
}
結構體在對??式不合適的時候,我們可以??更改默認對齊數。
3. 結構體傳參
struct S
{int data[1000];int num;
};
struct S s = {{1,2,3,4}, 1000};
//結構體傳參
void print1(struct S s)
{printf("%d\n", s.num);
}
//結構體地址傳參
void print2(struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{print1(s); //傳結構體print2(&s); //傳地址return 0;
}
上?的 print1 和 print2 函數哪個好些?
答案是:?選print2函數。
原因:函數傳參的時候,參數是需要壓棧,會有時間和空間上的系統開銷。
如果傳遞?個結構體對象的時候,結構體過?,參數壓棧的的系統開銷?較?,所以會導致性能的下降。
結論:結構體傳參的時候,要傳結構體的地址。
4. 結構體實現位段
結構體講完就得講講結構體實現 位段 的能?。
4.1 什么是位段
位段的聲明和結構是類似的,有兩個不同:
- 位段的成員必須是 int、unsigned int 或signed int ,在C99中位段成員的類型也可以選擇其他類型。
- 位段的成員名后邊有?個冒號和?個數字。
?如:
struct A
{int _a:2;int _b:5;int _c:10;int _d:30;
};
A就是?個位段類型。
那位段A所占內存的??是多少?
printf("%d\n", sizeof(struct A));
4.2 位段的內存分配
- 位段的成員可以是 int unsigned int signed int 或者是 char 等類型
- 位段的空間上是按照需要以4個字節( int )或者1個字節( char )的?式來開辟的。
- 位段涉及很多不確定因素,位段是不跨平臺的,注重可移植的程序應該避免使?位段。
//?個例?
struct S
{char a:3;char b:4;char c:5;char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空間是如何開辟的?
4.3 位段的跨平臺問題
- int 位段被當成有符號數還是?符號數是不確定的。
- 位段中最?位的數?不能確定。(16位機器最?16,32位機器最?32,寫成27,在16位機器會出問題。
- 位段中的成員在內存中從左向右分配,還是從右向左分配標準尚未定義。
- 當?個結構包含兩個位段,第?個位段成員?較?,?法容納于第?個位段剩余的位時,是舍棄剩余的位還是利?,這是不確定的。
總結:
跟結構相?,位段可以達到同樣的效果,并且可以很好的節省空間,但是有跨平臺的問題存在。
4.4 位段的應?
下圖是?絡協議中,IP數據報的格式,我們可以看到其中很多的屬性只需要?個bit位就能描述,這?使?位段,能夠實現想要的效果,也節省了空間,這樣?絡傳輸的數據報??也會較??些,對?絡的暢通是有幫助的。
4.5 位段使?的注意事項
位段的?個成員共有同?個字節,這樣有些成員的起始位置并不是某個字節的起始位置,那么這些位置處是沒有地址的。內存中每個字節分配?個地址,?個字節內部的bit位是沒有地址的。所以不能對位段的成員使?&操作符,這樣就不能使?scanf直接給位段的成員輸?值,只能是先輸?放在?個變量中,然后賦值給位段的成員。
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{struct A sa = {0};scanf("%d", &sa._b);//這是錯誤的//正確的?范int b = 0;scanf("%d", &b);sa._b = b;return 0;
}
完。