C語言超詳細結構體知識

1.自定義類型:結構體的介紹

在之前的博客中,我們簡單介紹過了關于結構體的基本知識,這里我們稍微復習一下。

結構體(struct)是C語言中一種重要的復合數據類型,它允許將不同類型的數據組合成一個整體。


1.1結構體的定義

結構體使用struct關鍵字定義,基本語法:

struct 結構體名 {數據類型 成員1;數據類型 成員2;// ...
};

?例如描述一個學生:

struct Stu {char name[20];int age;char sex[10];
};

1.2結構體的聲明和初始化

struct Student {int id;char name[20];float score;
};// 方式1: 先定義結構體類型,再聲明變量
struct Student stu1;// 方式2: 定義結構體類型的同時聲明變量
struct Student {int id;char name[20];float score;
} stu2, stu3;// 方式3: 使用typedef創建別名
typedef struct {int id;char name[20];float score;
} Student;
Student stu4;//方法4:特殊聲明,在聲明結構體的時候,可以不完全聲明
struct {int id;char name[20];float score;
}stu5;struct {int id;char name[20];float score;
}* stu6;stu6 = &stu5;

上述前三種聲明都沒有什么問題,而第四種聲明我們要格外注意,我們在聲明里省略了結構體標簽,那么stu6 = &stu5這個代碼能否能夠正確運行呢?我們測試一下:

我們可以看到編譯器報錯了,編譯器會把上面兩個聲明當成完全不同的類型。?

初始化結構體變量的方法一般有兩種,如下:

struct Student {int id;char name[20];float score;
};//按定義順序初始化
struct Student stu1 = { 20253265,"zhangsan",58.8 };//指定成員初始化
struct Student stu1 = { .id = 20251653,.name = "lisi",.score = 78.8 };

1.3結構體成員的訪問

1.使用點運算符?.?訪問結構體成員:

struct Student {int id;char name[20];float score;
};int main()
{struct Student stu1 = { 20253265,"zhangsan",58.8 };printf("%d\n", stu1.id);printf("%s\n", stu1.name);printf("%f\n", stu1.score);return 0;
}

?2.對于結構體指針,使用箭頭運算符?->訪問成員:

struct Student {int id;char name[20];float score;
};int main()
{struct Student stu1 = { 20253265,"zhangsan",58.8 };struct Student* ps = &stu1;printf("%d\n", ps->id);printf("%s\n", ps->name);printf("%d\n", ps->score);return 0;
}

2. 結構體內存對齊

2.1對齊規則

學習上文已經使我們掌握了結構體的基本使用,現在我們要來深入探討一個問題:計算結構體的大小。我們先來看一段代碼:

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

大家可以猜一下這段代碼的結果,會打印6,6嗎,我們運行看結果:

?我們看到結果打印和我們預料結果完全不同,這是否說明在結構體中內存分配和正常內存分配有很大差異呢?答案是肯定的,我們先來學習結構體內存分配規則:對齊規則。

對齊規則:

1. 結構體的第一個成員對齊到和結構體變量起始位置偏移量為0的地址處

2. 其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處。

? ? 對齊數 = 編譯器默認的?個對齊數 與 該成員變量大小的較小值。

? ??- VS 中默認的值為 8

? ??- Linux中 gcc 沒有默認對齊數,對齊數就是成員自身的大小

3. 結構體總大小為最大對齊數(結構體中每個成員變量都有?個對齊數,所有對齊數中最大的)的整數倍。

4. 如果嵌套了結構體的情況,嵌套的結構體成員對齊到自己的成員中最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體中成員的對齊數)的整數倍。

S1結構體的第一個變量c1對齊到和結構體變量起始位置偏移量為0的地址處,對應0字節空間,第二個成員變量為c2,對齊數為1(字符類型變量大小為1)和8(vs2022默認對齊數為8)的最小值,其實位置要對齊1的整數倍,對應1字節空間,第三個成員變量為i,對齊數為4(整型類型變量大小為4)和8(vs2022默認對齊數為8)的最小值,起始位置要對齊4的整數倍,對應4~7的字節空間。結構體總大小是最大對齊數的整數倍,S1結構體最大對齊數是4,要存入三個成員變量,至少需要8個字節,所以該結構體總大小為8個字節。如上圖所示。

S2結構體的第一個變量c1對齊到和結構體變量起始位置偏移量為0的地址處,對應0字節空間,第二個成員變量為i,對齊數為4(整型類型變量大小為4)和8(vs2022默認對齊數為8)的最小值,起始位置要對齊4的整數倍,對應4~7字節空間,第三個成員變量為c2,對齊數為1(字符類型變量大小為4)和8(vs2022默認對齊數為8)的最小值,起始位置要對齊1的整數倍,對應8的字節空間。結構體總大小是最大對齊數的整數倍,S1結構體最大對齊數是4,要存入三個成員變量,至少需要9個字節,所以該結構體總大小為12個字節。如上圖所示。

?解決了上述兩個問題,我們在看一個存在結構體嵌套求解結構體內存大小的問題。

?S3結構體的第一個變量c1對齊到和結構體變量起始位置偏移量為0的地址處,對應0字節空間,第二個成員變量為s2,嵌套的結構體成員對齊到自己的成員中最大對齊數的整數倍處,對齊數為4,起始位置要對齊4的整數倍,對應4~15字節空間,第三個成員變量為d,對齊數為8(雙精度浮點數類型變量大小為8)和8(vs2022默認對齊數為8)的最小值,起始位置要對齊8的整數倍,對應16~23的字節空間。結構體總大小是最大對齊數的整數倍,S3結構體最大對齊數是8,要存入三個成員變量,至少需要24個字節,所以該結構體總大小為24個字節。如上圖所示。

其實我們在劃分內存的時候,有些內存空間會被我們浪費掉,可不可以避免掉呢,其實是可以避免一部分的,在接下來的學習中我回講到。

上述三體都是根據對齊規則求出來的,大家要好好掌握。


2.2為什么回存在內存對齊

1. 平臺原因 (移植原因):
不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。
2. 性能原因:
數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。假設一個處理器總是從內存中取8個字節,則地址必須是8的倍數。如果我們能保證將所有的double類型的數據的地址都對齊成8的倍數,那么就可以用個內存操作來讀或者寫值了。否則,我們可能需要執行兩次內存訪問,因為對象可能被分放在兩個8字節內存塊中。
總體來說:結構體的內存對齊是拿空間來換取時間的做法。

?那在設計結構體的時候,我們既要滿足對齊,又要節省空間,如何做到:

//例如:
struct S1//8
{char c1;int i;char c2;
};struct S2//12
{char c1;char c2;int i;
};

?S1 S2 類型的成員?模?樣,但我們讓占有空間小的成員集中在一起,可以讓結構體所占空間大小變小一點。


?2.3修改默認對齊數

#pragma這個預處理指令,可以改變編譯器的默認對齊數。

?可以看到我們將默認對齊數改為1,結構體所占內存空間變為了6個字節,減少了很大一部分空間。

結構體在對齊方式不合適的時候,我們可以自己更改默認對齊數。


?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什么是位段

位段的聲明和結構式類似的,有兩個不同:

1. 位段的成員必須是 int unsigned int signed int ,在C99中位段成員的類型也可以
選擇其他類型。
2. 位段的成員名后邊有?個冒號和?個數字。

比如:

struct A
{int _a : 2;//代表該變量在內存空間中僅占2個bit位int _b : 5;//代表該變量在內存空間中僅占5個bit位int _c : 10;//代表該變量在內存空間中僅占10個bit位int _d : 30;//代表該變量在內存空間中僅占30個bit位
};

這就是一個典型的位段示例。它可以控制成員在內存中所占bit位的個數,那么他總共占的內存空間有多大呢?

位段的內存分配規則很大一部分取決于編譯器,以作者所用的vs2022環境下舉例,看以下代碼:

struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};int main()
{struct S s = { 0 };s.a = 10;s.b = 12;s.c = 3;s.d = 4;return 0;
}

從調試結果看,所占空間為3個字節。我們來分析過程:

a = 10,二進制為01010,而結構體位段規定a中只能存放3個bit位的數據,所以存010

b = 10,二進制為01100,而結構體位段規定a中只能存放4個bit位的數據,所以存1100

c = 10,二進制為00011,而結構體位段規定a中只能存放5個bit位的數據,所以存00011

a = 10,二進制為00100,而結構體位段規定a中只能存放4個bit位的數據,所以存0100

知道它們在內存中存的是什么之后,我們還有一個問題,它們是按什么順序,什么規則存進去呢??在vs2022環境下,在一個字節中,他要從高地址往低地址存放,第一個字節8個bit位存01100010,最高位之所以是0,是因為下一個數據占5個bit位的內存,1個bit位存不下,所以只能存放至下一個字節中(下同),第二個字節存00000011,第三個字節存00000100,最后在調試結果下從低地址到高地址分別為:0x62,0x03,0x04。

4.2位段的跨平臺問題

1.int 位段被當成有符號數還是無符號數是不確定的。不能確定最高位是否為符號位。

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

3.位段中的成員在內存中從左向右分配,還是從右向左分配標準尚未定義。(vs2022環境下,從右向左,也就是從高地址向低地址存)

4.當一個結構包含兩個位段,第二個位段成員比較大,無法容納于第一個位段剩余的位時,是舍棄剩余的位還是利用,這是不確定的。(vs2022環境下,舍棄)

跟結構相?,位段可以達到同樣的效果,并且可以很好的節省空間,但是有跨平臺的問題存在。

4.3位段的應用?

下圖是網絡協議中,IP數據報的格式,我們可以看到其中很多的屬性只需要幾個bit位就能描述,這里使用位段,能夠實現想要的效果,也節省了空間,這樣?絡傳輸的數據報大小也會較小?些,對網絡的暢通是有幫助的。

4.4位段使用的注意事項

位段的?個成員共有同?個字節,這樣有些成員的起始位置并不是某個字節的起始位置,那么這些位置處是沒有地址的。內存中每個字節分配?個地址,?個字節內部的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;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/77240.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/77240.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/77240.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++學習:六個月從基礎到就業——內存管理:new/delete操作符

C學習:六個月從基礎到就業——內存管理:new/delete操作符 本文是我C學習之旅系列的第十七篇技術文章,也是第二階段"C進階特性"的第二篇,主要介紹C中動態內存管理的核心操作符——new和delete。查看完整系列目錄了解更多…

15~30K,3年以上golang開發經驗

繼續分享最新的面經,前面發的兩篇大家也可以看看: 「坐標上海,20K的面試強度」「北京七貓,薪資25~35K,瞧瞧面試強度」 今天分享的是golang開發崗面經,要求是3年以上golang開發經驗,薪資為15~3…

Python爬蟲實戰:獲取優志愿專業數據

一、引言 在信息爆炸的當下,數據成為推動各領域發展的關鍵因素。優志愿網站匯聚了豐富的專業數據,對于教育研究、職業規劃等領域具有重要價值。然而,為保護自身數據和資源,許多網站設置了各類反爬機制。因此,如何高效、穩定地從優志愿網站獲取計算機專業數據成為一個具有…

ArcPy工具箱制作(下)

在上一篇博客中,我們已經初步了解了如何制作ArcPy工具箱,包括工具箱的基本概念、準備工作、腳本編寫以及將腳本轉換為工具箱的步驟。今天,我們將繼續深入探討ArcPy工具箱的制作,重點介紹一些進階技巧和優化方法. 一、優化工具箱的…

不一樣的flag 1(迷宮題)

題目 做法 下載壓縮包,解壓,把解壓后的文件拖進Exeinfo PE進行分析 32位,無殼 扔進IDA(32位),找到main,F5反編譯 沒啥關鍵詞,ShiftF12也找不到什么有用的點 從上往下分析吧 puts(…

工程化實踐:Flutter項目結構與規范

工程化實踐:Flutter項目結構與規范 在Flutter項目開發中,良好的工程化實踐對于提高開發效率、保證代碼質量和團隊協作至關重要。本文將從項目結構、代碼規范、CI/CD流程搭建以及包管理等方面,詳細介紹Flutter項目的工程化最佳實踐。 項目結…

[Java · 初窺門徑] Java 語言初識

🌟 想系統化學習 Java 編程?看看這個:[編程基礎] Java 學習手冊 0x01:Java 編程語言簡介 Java 是一種高級計算機編程語言,它是由 Sun Microsystems 公司(已被 Oracle 公司收購)于 1995 年 5 …

1187. 【動態規劃】競賽總分

題目描述 學生在我們USACO的競賽中的得分越多我們越高興。我們試著設計我們的競賽以便人們能盡可能的多得分。 現在要進行一次競賽,總時間T固定,有若干類型可選擇的題目,每種類型題目可選入的數量不限,每種類型題目有一個si(解答…

使用KeilAssistant代替keil的UI界面

目錄 一、keil Assistant的優勢和缺點 二、使用方法 (1)配置keil的路徑 (2)導入并使用工程 (3)默認使用keil自帶的ARM編譯器而非GUN工具鏈 一、keil Assistant的優勢和缺點 在日常學…

【React】通過 fetch 發起請求,設置 proxy 處理跨域

fetch 基本使用跨域處理 fetch 基本使用 在node使用原生ajax發請求:XMLHttpRequest()1.獲取xhr對象 2.注冊回調函數 3.設置參數,請求頭 4.發起連接原生ajax沒有帶異步處理 promise;原生ajax封裝一下,以便重復調用jQuery&#…

Redis(二) - Redis命令詳解

文章目錄 前言一、啟動Redis并進入客戶端1. 啟動Redis2. 進入Redis客戶端3. 使用IDEA連接Redis 二、查看命令幫助信息1. 查看所有命令2. 查看指定命令幫助 三、鍵操作命令1. set命令2. mset命令3. keys命令4. get命令5. mget命令6. dump命令7. exists命令8. type命令9. rename命…

【Qt】初識Qt(二)

目錄 一、顯示hello world1.1 圖形化界面1.2 寫代碼 二、對象樹三、使用輸入框顯示hello world四、使用按鈕顯示hello world 一、顯示hello world 有兩種方式實現hello world: 通過圖形化界面,在界面上創建出一個控件,顯示hello world通過寫…

空調制冷量和功率有什么關系?

空調的制冷量和功率是衡量空調性能的兩個核心參數,二者既有區別又緊密相關,以下是具體解析: 1. 基本定義 制冷量(Cooling Capacity)指空調在單位時間內從室內環境中移除的熱量,單位為 瓦特(W) 或 千卡/小時(kcal/h)。它直接反映空調的制冷能力,數值越大,制冷效果越…

【prometheus+Grafana篇】Prometheus與Grafana:深入了解監控架構與數據可視化分析平臺

💫《博主主頁》:奈斯DB-CSDN博客 🔥《擅長領域》:擅長阿里云AnalyticDB for MySQL(分布式數據倉庫)、Oracle、MySQL、Linux、prometheus監控;并對SQLserver、NoSQL(MongoDB)有了解 💖如果覺得文章對你有所幫…

基于n8n的AI應用工作流原理與技術解析

基于n8n的AI應用工作流原理與技術解析 在AI技術深度融入企業數字化轉型的今天,開源工作流自動化工具n8n憑借其靈活的架構和強大的集成能力,成為構建智能自動化流程的核心引擎。本文將從技術原理、AI融合機制、典型應用場景三個維度,解析n8n在…

經濟指標學習(二)

系列文章目錄 文章目錄 系列文章目錄1、市凈率**一、定義與計算****二、核心意義****三、應用場景****四、局限性****五、分類與衍生指標****總結** 2、市銷率**一、定義與計算****二、核心意義****三、優缺點分析****四、適用場景****五、與其他指標的對比****六、實際應用案例…

大語言模型減少幻覺的常見方案

什么是大語言模型的幻覺 大語言模型的幻覺(Hallucination)是指模型在生成文本時,輸出與輸入無關、不符合事實、邏輯錯誤或完全虛構的內容。這種現象主要源于模型基于概率生成文本的本質,其目標是生成語法合理、上下文連貫的文本&…

CSS 美化頁面(四)

一、浮動float屬性 ?屬性值??描述??適用場景?left元素向左浮動,騰出右側空間供其他元素使用,其他內容會圍繞在其右側?。橫向排列元素(如導航菜單)、圖文混排布局?。right元素向右浮動,騰出左側空間供其他元素使…

如何將 .txt 文件轉換成 .md 文件

一、因為有些軟件上傳文件的時候需要 .md 文件,首先在文件所在的目錄中,點擊“查看”,然后勾選上“文件擴展名”,這個時候該目錄下的所有文件都會顯示其文件類型了。 二、這時直接對目標的 .txt 文件進行重命名,把后綴…

C++ 迭代器失效詳解:如何避免 vector 操作中的陷阱

目錄 1. 什么是迭代器失效? 2. 哪些操作會導致迭代器失效? 2.1 vector 的插入操作(push_back, insert) 示例:push_back 導致迭代器失效 如何避免? 2.2 vector 的刪除操作(erase, pop_back&…