色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

嵌入式C語言的高級用法

GReq_mcu168 ? 來源:玩轉(zhuǎn)單片機(jī) ? 作者:玩轉(zhuǎn)單片機(jī) ? 2021-02-20 14:25 ? 次閱讀

內(nèi)存管理

我們需要知道——變量,其實(shí)是內(nèi)存地址的一個抽像名字罷了。在靜態(tài)編譯的程序中,所有的變量名都會在編譯時被轉(zhuǎn)成內(nèi)存地址。機(jī)器是不知道我們?nèi)〉拿值模恢赖刂贰?/p>

內(nèi)存的使用時程序設(shè)計(jì)中需要考慮的重要因素之一,這不僅由于系統(tǒng)內(nèi)存是有限的(尤其在嵌入式系統(tǒng)中),而且內(nèi)存分配也會直接影響到程序的效率。因此,我們要對C語言中的內(nèi)存管理,有個系統(tǒng)的了解。

在C語言中,定義了4個內(nèi)存區(qū)間:代碼區(qū);全局變量和靜態(tài)變量區(qū);局部變量區(qū)即棧區(qū);動態(tài)存儲區(qū),即堆區(qū);具體如下:

1》棧區(qū)(stack)— 由編譯器自動分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。

2》堆區(qū)(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時可能由OS回收 。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表。

3》全局區(qū)(靜態(tài)區(qū))(static)—全局變量和靜態(tài)變量的存儲是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的 另一塊區(qū)域。- 程序結(jié)束后由系統(tǒng)釋放。

4》常量區(qū) —常量字符串就是放在這里的。程序結(jié)束后由系統(tǒng)釋放。

5》程序代碼區(qū)—存放函數(shù)體的二進(jìn)制代碼。

我們來看張圖:

0bf571ca-71b9-11eb-8b86-12bb97331649.jpg

圖1

首先我們要知道,源代碼編譯成程序,程序是放在硬盤上的,而非內(nèi)存里!只有執(zhí)行時才會被調(diào)用到內(nèi)存中!我們來看看程序結(jié)構(gòu),ELF是是Linux的主要可執(zhí)行文件格式。ELF文件由4部分組成,分別是ELF頭(ELF header)、程序頭表(Program header table)、節(jié)(Section)和節(jié)頭表(Section header table)。具體如下:

1》Program header描述的是一個段在文件中的位置、大小以及它被放進(jìn)內(nèi)存后所在的位置和大小。即要加載的信息

2》Sections保存著object 文件的信息,從連接角度看:包括指令,數(shù)據(jù),符號表,重定位信息等等。在圖中,我們可以看到Sections中包括:

text 文本結(jié) 存放指令;

rodata 數(shù)據(jù)結(jié) readonly;

data 數(shù)據(jù)結(jié) 可讀可寫;

3》Section頭表(section header table)包含了描述文件sections的信息。每個section在這個表中有一個入口;每個入口給出了該section的名字,大小,等等信息。相當(dāng)于 索引

而程序被加載到內(nèi)存里面,又是如何分布的呢?我們看看上圖中:

正文和初始化的數(shù)據(jù)和未初始化的數(shù)據(jù)就是我們所說的數(shù)據(jù)段,正文即代碼段;

2》正文段上面是常量區(qū),常量區(qū)上面是全局變量和靜態(tài)變量區(qū),二者占據(jù)的就是初始化的數(shù)據(jù)和未初始化的數(shù)據(jù)那部分;

3》再上面就是堆,動態(tài)存儲區(qū),這里是上增長;

4》堆上面是棧,存放的是局部變量,就是局部變量所在代碼塊執(zhí)行完畢后,這塊內(nèi)存會被釋放,這里棧區(qū)是下增長;

5》命令行參數(shù)就是001之類的,環(huán)境變量什么的前面的文章已經(jīng)講過,有興趣的可以去看看。

我們知道,內(nèi)存分為動態(tài)內(nèi)存和靜態(tài)內(nèi)存,我們先講靜態(tài)內(nèi)存。

靜態(tài)內(nèi)存

存儲模型決定了一個變量的內(nèi)存分配方式和訪問特性,在C語言中主要有三個維度來決定:存儲時期 、作用域 、鏈接。

1、存儲時期

存儲時期:變量在內(nèi)存中的保留時間(生命周期)

存儲時期分為兩種情況,關(guān)鍵是看變量在程序執(zhí)行過程中會不會被系統(tǒng)自動回收掉。

1) 靜態(tài)存儲時期 Static

在程序執(zhí)行過程中一旦分配就不會被自動回收。

通常來說,任何不在函數(shù)級別代碼塊內(nèi)定義的變量。

無論是否在代碼塊內(nèi),只要采用static關(guān)鍵字修飾的變量。

2) 自動存儲時期 Automatic

除了靜態(tài)存儲以外的變量都是自動存儲時期的,或者說只要是在代碼塊內(nèi)定義的非static的變量,系統(tǒng)會肚臍自動非配和釋放內(nèi)存;

2、作用域

作用域:一個變量在定義該變量的自身文件中的可見性(訪問或者引用)

在C語言中,一共有3中作用域:

1) 代碼塊作用域

在代碼塊中定義的變量都具有該代碼的作用域。從這個變量定義地方開始,到這個代碼塊結(jié)束,該變量是可見的;

2) 函數(shù)原型作用域

出現(xiàn)在函數(shù)原型中的變量,都具有函數(shù)原型作用域,函數(shù)原型作用域從變量定義處一直到原型聲明的末尾。

3) 文件作用域

一個在所有函數(shù)之外定義的變量具有文件作用域,具有文件作用域的變量從它的定義處到包含該定義的文件結(jié)尾處都是可見的;

3、鏈接

鏈接:一個變量在組成程序的所有文件中的可見性(訪問或者引用);

C語言中一共有三種不同的鏈接:

1) 外部鏈接

如果一個變量在組成一個程序的所有文件中的任何位置都可以被訪問,則稱該變量支持外部鏈接;

2) 內(nèi)部鏈接

如果一個變量只可以在定義其自身的文件中的任何位置被訪問,則稱該變量支持內(nèi)部鏈接。

3) 空鏈接

如果一個變量只是被定義其自身的當(dāng)前代碼塊所私有,不能被程序的其他部分所訪問,則成該變量支持空鏈接

我們來看一個代碼示例:

#include 《stdio.h》

int a = 0;// 全局初始化區(qū)

char *p1; //全局未初始化區(qū)

int main()

{

int b; //b在棧區(qū)

char s[] = “abc”; //棧

char *p2; //p2在棧區(qū)

char *p3 = “123456”; //123456在常量區(qū),p3在棧上。

static int c =0; //全局(靜態(tài))初始化區(qū)

p1 = (char *)malloc(10);

p2 = (char *)malloc(20); //分配得來得10和20字節(jié)的區(qū)域就在堆區(qū)。

strcpy(p1, “123456”); //123456放在常量區(qū),編譯器可能會將它與p3所指向的“123456”優(yōu)化成一個地方。

}

1.2動態(tài)內(nèi)存

當(dāng)程序運(yùn)行到需要一個動態(tài)分配的變量時,必須向系統(tǒng)申請取得堆中的一塊所需大小的存儲空間,用于存儲該變量。當(dāng)不在使用該變量時,也就是它的生命結(jié)束時,要顯示釋放它所占用的存儲空間,這樣系統(tǒng)就能對該空間 進(jìn)行再次分配,做到重復(fù)使用有線的資源。下面介紹動態(tài)內(nèi)存申請和釋放的函數(shù)。

1.2.1 malloc 函數(shù)

malloc函數(shù)原型:

0f20d74a-71b9-11eb-8b86-12bb97331649.png

size是需要動態(tài)申請的內(nèi)存的字節(jié)數(shù)。若申請成功,函數(shù)返回申請到的內(nèi)存的起始地址,若申請失敗,返回NULL。我們看下面這個例子:

0f36c97e-71b9-11eb-8b86-12bb97331649.png

使用該函數(shù)時,有下面幾點(diǎn)要注意:

1)只關(guān)心申請內(nèi)存的大小;

2)申請的是一塊連續(xù)的內(nèi)存。記得一定要寫出錯判斷;

3)顯示初始化。即我們不知這塊內(nèi)存中有什么東西,要對其清零;

1.2.2 free函數(shù)

在堆上分配的額內(nèi)存,需要用free函數(shù)顯示釋放,函數(shù)原型如下:

0f77ba4c-71b9-11eb-8b86-12bb97331649.png

使用free(),也有下面幾點(diǎn)要注意:

1)必須提供內(nèi)存的起始地址;

調(diào)用該函數(shù)時,必須提供內(nèi)存的起始地址,不能夠提供部分地址,釋放內(nèi)存中的一部分是不允許的。

2)malloc和free配對使用;

編譯器不負(fù)責(zé)動態(tài)內(nèi)存的釋放,需要程序員顯示釋放。因此,malloc與free是配對使用的,避免內(nèi)存泄漏。

0fbd9706-71b9-11eb-8b86-12bb97331649.png

p = NULL是必須的,因?yàn)殡m然這塊內(nèi)存被釋放了,但是p仍指向這塊內(nèi)存,避免下次對p的誤操作;

3)不允許重復(fù)釋放

因?yàn)檫@塊內(nèi)存被釋放后,可能已另分配,這塊區(qū)域被別人占用,如果再次釋放,會造成數(shù)據(jù)丟失;

1.2.3 其它相關(guān)函數(shù)

calloc函數(shù)分配內(nèi)存需要考慮存儲位置的類型。

realloc函數(shù)可以調(diào)整一段動態(tài)分配內(nèi)存的大小

1.3堆和棧比較

1)申請方式

stack: 由系統(tǒng)自動分配。例如,聲明在函數(shù)中一個局部變量 int b; 系統(tǒng)自動在棧中為b開辟空間

heap: 需要程序員自己申請,并指明大小,在c中malloc函數(shù) ,如p1 = (char *)malloc(10);

2)申請后系統(tǒng)的響應(yīng)

棧:只要棧的剩余空間大于所申請空間,系統(tǒng)將為程序提供內(nèi)存,否則將報異常提示棧溢出。

堆:首先應(yīng)該知道操作系統(tǒng)有一個記錄空閑內(nèi)存地址的鏈表,當(dāng)系統(tǒng)收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結(jié)點(diǎn),然后將該結(jié)點(diǎn)從空閑結(jié)點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序,另外,對于大多數(shù)系統(tǒng),會在這塊內(nèi)存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內(nèi)存空間。另外,由于找到的堆結(jié)點(diǎn)的大小不一定正好等于申請的大小,系統(tǒng)會自動的將多余的那部分重新放入空閑鏈表中。

3)申請大小的限制

棧:棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數(shù)),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。

堆:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來存儲的空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見,堆獲得的空間比較靈活,也比較大。

4)申請效率的比較

棧由系統(tǒng)自動分配,速度較快。但程序員是無法控制的。

堆是由new分配的內(nèi)存,一般速度比較慢,而且容易產(chǎn)生內(nèi)存碎片,不過用起來最方便。

5)堆和棧中的存儲內(nèi)容

棧:在函數(shù)調(diào)用時,第一個進(jìn)棧的是主函數(shù)中后的下一條指令(函數(shù)調(diào)用語句的下一條可執(zhí)行語句)的地址,然后是函數(shù)的各個參數(shù),在大多數(shù)的C編譯器中,參數(shù)是由右往左入棧的,然后是函數(shù)中的局部變量。注意靜態(tài)變量是不入棧的。當(dāng)本次函數(shù)調(diào)用結(jié)束后,局部變量先出棧,然后是參數(shù),最后棧頂指針指向最開始存的地址,也就是主函數(shù)中的下一條指令,程序由該點(diǎn)繼續(xù)運(yùn)行。

堆:一般是在堆的頭部用一個字節(jié)存放堆的大小。堆中的具體內(nèi)容由程序員安排。

6)存取效率的比較

char s1[] = “aaaaaaaaaaaaaaa”;

char *s2 = “bbbbbbbbbbbbbbbbb”;

aaaaaaaaaaa是在運(yùn)行時刻賦值的;

而bbbbbbbbbbb是在編譯時就確定的;

但是,在以后的存取中,在棧上的數(shù)組比指針?biāo)赶虻淖址ɡ缍眩┛臁?/p>

比如:

0fe88ab0-71b9-11eb-8b86-12bb97331649.png

對應(yīng)的匯編代碼

1032fad2-71b9-11eb-8b86-12bb97331649.png

第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,再根據(jù)edx讀取字符,顯然慢了。

7)最后總結(jié)

堆和棧的區(qū)別可以用如下的比喻來看出:

棧就像我們?nèi)ワ堭^里吃飯,只管點(diǎn)菜(發(fā)出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等準(zhǔn)備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。

堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。

2 內(nèi)存對齊

2.1 #pragma pack(n) 對齊用法詳解

1.什么是對齊,以及為什么要對齊

現(xiàn)代計(jì)算機(jī)中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實(shí)際情況是在訪問特定變量的時候經(jīng)常在特定的內(nèi)存地址訪問,這就需要各類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。

對齊的作用和原因:各個硬件平臺對存儲空間的處理上有很大的不同。一些平臺對某些特定類型的數(shù)據(jù)只能從某些特定地址開始存取。其他平臺可能沒有這種情況, 但是最常見的是如果不按照適合其平臺要求對數(shù)據(jù)存放進(jìn)行對齊,會在存取效率上帶來損失。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設(shè)為 32位系統(tǒng))如果存放在偶地址開始的地方,那么一個讀周期就可以讀出,而如果存放在奇地址開始的地方,就可能會需要2個讀周期,并對兩次讀出的結(jié)果的高低字節(jié)進(jìn)行拼湊才能得到該int數(shù)據(jù)。顯然在讀取效率上下降很多。這也是空間和時間的博弈。

2.對齊的實(shí)現(xiàn)

通常,我們寫程序的時候,不需要考慮對齊問題。編譯器會替我們選擇時候目標(biāo)平臺的對齊策略。當(dāng)然,我們也可以通知給編譯器傳遞預(yù)編譯指令而改變對指定數(shù)據(jù)的對齊方法。但是,正因?yàn)槲覀円话悴恍枰P(guān)心這個問題,所以因?yàn)榫庉嬈鲗?shù)據(jù)存放做了對齊,而我們不了解的話,常常會對一些問題感到迷惑。最常見的就是struct數(shù)據(jù)結(jié)構(gòu)的sizeof結(jié)果,出乎意料。為此,我們需要對對齊算法所了解。

作用:

指定結(jié)構(gòu)體、聯(lián)合以及類成員的packing alignment;

語法:

#pragma pack( [show] | [push | pop] [, identifier], n )

說明:

1》pack提供數(shù)據(jù)聲明級別的控制,對定義不起作用;

2》調(diào)用pack時不指定參數(shù),n將被設(shè)成默認(rèn)值;

3》一旦改變數(shù)據(jù)類型的alignment,直接效果就是占用memory的減少,但是performance會下降;

3.語法具體分析

1》show:可選參數(shù);顯示當(dāng)前packing aligment的字節(jié)數(shù),以warning message的形式被顯示;

2》push:可選參數(shù);將當(dāng)前指定的packing alignment數(shù)值進(jìn)行壓棧操作,這里的棧是the internal compiler stack,同時設(shè)置當(dāng)前的packing alignment為n;如果n沒有指定,則將當(dāng)前的packing alignment數(shù)值壓棧;

3》pop:可選參數(shù);從internal compiler stack中刪除最頂端的record;如果沒有指定n,則當(dāng)前棧頂record即為新的packing alignment數(shù)值;如果指定了n,則n將成為新的packing aligment數(shù)值;如果指定了identifier,則internal compiler stack中的record都將被pop直到identifier被找到,然后pop出identitier,同時設(shè)置packing alignment數(shù)值為當(dāng)前棧頂?shù)膔ecord;如果指定的identifier并不存在于internal compiler stack,則pop操作被忽略;

4》identifier:可選參數(shù);當(dāng)同push一起使用時,賦予當(dāng)前被壓入棧中的record一個名稱;當(dāng)同pop一起使用時,從internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier沒有被找到,則忽略pop操作;

5》n:可選參數(shù);指定packing的數(shù)值,以字節(jié)為單位;缺省數(shù)值是8,合法的數(shù)值分別是1、2、4、8、16。

4.重要規(guī)則

1》復(fù)雜類型中各個成員按照它們被聲明的順序在內(nèi)存中順序存儲,第一個成員的地址和整個類型的地址相同;

2》每個成員分別對齊,即每個成員按自己的方式對齊,并最小化長度;規(guī)則就是每個成員按其類型的對齊參數(shù)(通常是這個類型的大小)和指定對齊參數(shù)中較小的一個對齊;

3》結(jié)構(gòu)、聯(lián)合或者類的數(shù)據(jù)成員,第一個放在偏移為0的地方;以后每個數(shù)據(jù)成員的對齊,按照#pragma pack指定的數(shù)值和這個數(shù)據(jù)成員自身長度兩個中比較小的那個進(jìn)行;也就是說,當(dāng)#pragma pack指定的值等于或者超過所有數(shù)據(jù)成員長度的時候,這個指定值的大小將不產(chǎn)生任何效果;

4》復(fù)雜類型(如結(jié)構(gòu))整體的對齊《注意是“整體”》是按照結(jié)構(gòu)體中長度最大的數(shù)據(jù)成員和#pragma pack指定值之間較小的那個值進(jìn)行;這樣在成員是復(fù)雜類型時,可以最小化長度;

5》結(jié)構(gòu)整體長度的計(jì)算必須取所用過的所有對齊參數(shù)的整數(shù)倍,不夠補(bǔ)空字節(jié);也就是取所用過的所有對齊參數(shù)中最大的那個值的整數(shù)倍,因?yàn)閷R參數(shù)都是2的n次方;這樣在處理數(shù)組時可以保證每一項(xiàng)都邊界對齊;

5.對齊的算法

由于各個平臺和編譯器的不同,現(xiàn)以本人使用的gcc version 3.2.2編譯器(32位x86平臺)為例子,來討論編譯器對struct數(shù)據(jù)結(jié)構(gòu)中的各成員如何進(jìn)行對齊的。

在相同的對齊方式下,結(jié)構(gòu)體內(nèi)部數(shù)據(jù)定義的順序不同,結(jié)構(gòu)體整體占據(jù)內(nèi)存空間也不同,如下:

設(shè)結(jié)構(gòu)體如下定義:

104d2358-71b9-11eb-8b86-12bb97331649.png

結(jié)構(gòu)體A中包含了4字節(jié)長度的int一個,1字節(jié)長度的char一個和2字節(jié)長度的short型數(shù)據(jù)一個。所以A用到的空間應(yīng)該是7字節(jié)。但是因?yàn)榫幾g器要對數(shù)據(jù)成員在空間上進(jìn)行對齊。所以使用sizeof(strcut A)值為8。

現(xiàn)在把該結(jié)構(gòu)體調(diào)整成員變量的順序。

1077c360-71b9-11eb-8b86-12bb97331649.png

這時候同樣是總共7個字節(jié)的變量,但是sizeof(struct B)的值卻是12。

下面我們使用預(yù)編譯指令#progma pack (value)來告訴編譯器,使用我們指定的對齊值來取代缺省的。

10addb30-71b9-11eb-8b86-12bb97331649.png

sizeof(struct C)值是8。

修改對齊值為1:

10f86b8c-71b9-11eb-8b86-12bb97331649.png

sizeof(struct D)值為7。

對于char型數(shù)據(jù),其自身對齊值為1,對于short型為2,對于int,float,double類型,其自身對齊值為4,單位字節(jié)。

6.四個概念值

1》數(shù)據(jù)類型自身的對齊值:就是上面交代的基本數(shù)據(jù)類型的自身對齊值。

2》指定對齊值:#progma pack (value)時的指定對齊值value。

3》結(jié)構(gòu)體或者類的自身對齊值:其數(shù)據(jù)成員中自身對齊值最大的那個值。

4》數(shù)據(jù)成員、結(jié)構(gòu)體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。有了這些值,我們就可以很方便的來討論具體數(shù)據(jù)結(jié)構(gòu)的成員和其自身的對齊方式。有效對齊值N是最終用來決定數(shù)據(jù)存放地址方式的值,最重要。有效對齊N,就是表示“對齊在N上”,也就是說該數(shù)據(jù)的”存放起始地址%N=0”。 而數(shù)據(jù)結(jié)構(gòu)中的數(shù)據(jù)變量都是按定義的先后順序來排放的。第一個數(shù)據(jù)變量的起始地址就是數(shù)據(jù)結(jié)構(gòu)的起始地址。結(jié)構(gòu)體的成員變量要對齊排放,結(jié)構(gòu)體本身也要根 據(jù)自身的有效對齊值圓整(就是結(jié)構(gòu)體成員變量占用總長度需要是對結(jié)構(gòu)體有效對齊值的整數(shù)倍,結(jié)合下面例子理解)。這樣就不能理解上面的幾個例子的值了。

例子分析:

分析例子B;

112ed21c-71b9-11eb-8b86-12bb97331649.png

假設(shè)B從地址空間0x0000開始排放。該例子中沒有定義指定對齊值,在筆者環(huán)境下,該值默認(rèn)為4。

第一個成員變量b的自身對齊值是1,比指定或者默認(rèn)指定對齊值4小,所以其有效對齊值為1,所以其存放地址0x0000符合0x0000%1=0.

第二個成員變量a,其自身對齊值為4,所以有效對齊值也為4,所以只能存放在起始地址為0x0004到0x0007這四個連續(xù)的字節(jié)空間中,符合0x0004%4=0, 且緊靠第一個變量。

第三個變量c,自身對齊值為2,所以有效對齊值也是2,可以存放在0x0008到0x0009 這兩個字節(jié)空間中,符合0x0008%2=0。所以從0x0000到0x0009存放的都是B內(nèi)容。再看數(shù)據(jù)結(jié)構(gòu)B的自身對齊值為其變量中最大對齊值(這里是b)所以就是4,所以結(jié)構(gòu)體的有效對齊值也是4。根據(jù)結(jié)構(gòu)體圓整的要求,0x0009到0x0000=10字節(jié),(10+2)%4=0。所以0x0000A到0x000B也為結(jié)構(gòu)體B所占用。故B從0x0000到0x000B共有12個字節(jié),sizeof(struct B)=12;

同理,分析上面例子C:

115df6d2-71b9-11eb-8b86-12bb97331649.png

第一個變量b的自身對齊值為1,指定對齊值為2,所以,其有效對齊值為1,假設(shè)C從0x0000開始,那么b存放在0x0000,符合0x0000%1=0;

第二個變量,自身對齊值為4,指定對齊值為2,所以有效對齊值為2,所以順序存放在0x0002、0x0003、0x0004、0x0005四個連續(xù)字節(jié)中,符合0x0002%2=0。

第三個變量c的自身對齊值為2,所以有效對齊值為2,順序存放在0x0006、0x0007中,符合0x0006%2=0。所以從0x0000到0x00007共八字節(jié)存放的是C的變量。

又C的自身對齊值為4,所以C的有效對齊值為2。又8%2=0,C只占用0x0000到0x0007的八個字節(jié)。所以sizeof(struct C)=8.

字節(jié)對齊對程序的影響

先讓我們看幾個例子吧(32bit,x86環(huán)境,gcc編譯器):

設(shè)結(jié)構(gòu)體如下定義:

118f5790-71b9-11eb-8b86-12bb97331649.png

現(xiàn)在已知32位機(jī)器上各種數(shù)據(jù)類型的長度如下:

char:1(有符號無符號同)

short:2(有符號無符號同)

int:4(有符號無符號同)

long:4(有符號無符號同)

float:4 double:8

那么上面兩個結(jié)構(gòu)大小如何呢?

結(jié)果是:

sizeof(strcut A)值為8

sizeof(struct B)的值卻是12

結(jié)構(gòu)體A中包含了4字節(jié)長度的int一個,1字節(jié)長度的char一個和2字節(jié)長度的short型數(shù)據(jù)一個,B也一樣;按理說A,B大小應(yīng)該都是7字節(jié)。之所以出現(xiàn)上面的結(jié)果是因?yàn)榫幾g器要對數(shù)據(jù)成員在空間上進(jìn)行對齊。上面是按照編譯器的默認(rèn)設(shè)置進(jìn)行對齊的結(jié)果,那么我們是不是可以改變編譯器的這種默認(rèn)對齊設(shè)置呢,當(dāng)然可以。例如:

11a30420-71b9-11eb-8b86-12bb97331649.png

sizeof(struct C)值是8。

修改對齊值為1:

11e78208-71b9-11eb-8b86-12bb97331649.png

sizeof(struct D)值為7。

后面我們再講解#pragma pack()的作用。

2.3修改編譯器的默認(rèn)對齊值

1》在VC IDE中,可以這樣修改:[Project]|[Settings],c/c++選項(xiàng)卡Category的Code Generation選項(xiàng)的Struct Member Alignment中修改,默認(rèn)是8字節(jié)。

2》在編碼時,可以這樣動態(tài)修改:#pragma pack 。注意:是pragma而不是progma.

如果在編程的時候要考慮節(jié)約空間的話,那么我們只需要假定結(jié)構(gòu)的首地址是0,然后各個變量按照上面的原則進(jìn)行排列即可,基本的原則就是把結(jié)構(gòu)中的變量按照 類型大小從小到大聲明,盡量減少中間的填補(bǔ)空間。還有一種就是為了以空間換取時間的效率,我們顯示的進(jìn)行填補(bǔ)空間進(jìn)行對齊,比如:有一種使用空間換時間做 法是顯式的插入reserved成員:

11fb8e2e-71b9-11eb-8b86-12bb97331649.png

reserved成員對我們的程序沒有什么意義,它只是起到填補(bǔ)空間以達(dá)到字節(jié)對齊的目的,當(dāng)然即使不加這個成員通常編譯器也會給我們自動填補(bǔ)對齊,我們自己加上它只是起到顯式的提醒作用。

2.4字節(jié)對齊可能帶來的隱患

代碼中關(guān)于對齊的隱患,很多是隱式的。比如在強(qiáng)制類型轉(zhuǎn)換的時候。例如:

124fc908-71b9-11eb-8b86-12bb97331649.png

最后兩句代碼,從奇數(shù)邊界去訪問unsignedshort型變量,顯然不符合對齊的規(guī)定。

在x86上,類似的操作只會影響效率,但是在MIPS或者sparc上,可能就是一個error,因?yàn)樗鼈円蟊仨氉止?jié)對齊。

如果出現(xiàn)對齊或者賦值問題首先查看

1)。 編譯器的big little端設(shè)置

2)。 看這種體系本身是否支持非對齊訪問

3)。 如果支持看設(shè)置了對齊與否,如果沒有則看訪問時需要加某些特殊的修飾來標(biāo)志其特殊訪問操作。

ARM下的對齊處理

from DUI0067D_ADS1_2_CompLib type qulifiers

有部分摘自ARM編譯器文檔對齊部分對齊的使用:

1.__align(num)

這個用于修改最高級別對象的字節(jié)邊界。在匯編中使用LDRD或者STRD時就要用到此命令__align(8)進(jìn)行修飾限制。來保證數(shù)據(jù)對象是相應(yīng)對齊。這個修飾對象的命令最大是8個字節(jié)限制,可以讓2字節(jié)的對象進(jìn)行4字節(jié)對齊,但是不能讓4字節(jié)的對象2字節(jié)對齊。__align是存儲類修改,他只修飾最高級類型對象不能用于結(jié)構(gòu)或者函數(shù)對象。

2.__packed

__packed是進(jìn)行一字節(jié)對齊

l 不能對packed的對象進(jìn)行對齊

l 所有對象的讀寫訪問都進(jìn)行非對齊訪問

l float及包含float的結(jié)構(gòu)聯(lián)合及未用__packed的對象將不能字節(jié)對齊

l __packed對局部整形變量無影響

l 強(qiáng)制由unpacked對象向packed對象轉(zhuǎn)化是未定義,整形指針可以合法定

義為packed。

__packed int* p; //__packed int 則沒有意義

2.5對齊或非對齊讀寫訪問帶來問題

__packed struct STRUCT_TEST

{char a;int b;char c;

} ;

//定義如下結(jié)構(gòu)此時b的起始地址一定是不對齊的,在棧中訪問b可能有問題,因?yàn)闂I蠑?shù)據(jù)肯定是對齊訪問[from CL]

//將下面變量定義成全局靜態(tài)不在棧上

static char* p;static struct STRUCT_TEST a;void Main()

{

__packed int* q; //此時定義成__packed來修飾當(dāng)前q指向?yàn)榉菍R的數(shù)據(jù)地址下面的訪問則可以

p = (char*)&a;

q = (int*)(p+1);

*q = 0x87654321; /*

得到賦值的匯編指令很清楚

ldr r5,0x20001590 ; = #0x12345678

[0xe1a00005] mov r0,r5

[0xeb0000b0] bl __rt_uwrite4 //在此處調(diào)用一個寫4byte的操作函數(shù)

[0xe5c10000] strb r0,[r1,#0] //函數(shù)進(jìn)行4次strb操作然后返回保證了數(shù)據(jù)正確的訪問

[0xe1a02420] mov r2,r0,lsr #8

[0xe5c12001] strb r2,[r1,#1]

[0xe1a02820] mov r2,r0,lsr #16

[0xe5c12002] strb r2,[r1,#2]

[0xe1a02c20] mov r2,r0,lsr #24

[0xe5c12003] strb r2,[r1,#3]

[0xe1a0f00e] mov pc,r14

*/ /*

如果q沒有加__packed修飾則匯編出來指令是這樣直接會導(dǎo)致奇地址處訪問失敗

[0xe59f2018] ldr r2,0x20001594 ; = #0x87654321

[0xe5812000] str r2,[r1,#0]

*/

//這樣可以很清楚的看到非對齊訪問是如何產(chǎn)生錯誤的

//以及如何消除非對齊訪問帶來問題

//也可以看到非對齊訪問和對齊訪問的指令差異導(dǎo)致效率問題

}

好了,今天就講到這里了,大家還有哪些更“高級”的用法歡迎留言分享!

原文標(biāo)題:大牛談嵌入式C語言的高級用法

文章出處:【微信公眾號:玩轉(zhuǎn)單片機(jī)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3020

    瀏覽量

    74008
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7604

    瀏覽量

    136707

原文標(biāo)題:大牛談嵌入式C語言的高級用法

文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    新手怎么學(xué)嵌入式?

    的運(yùn)行機(jī)制。例如,了解數(shù)據(jù)結(jié)構(gòu)中的鏈表、棧和隊(duì)列,對于在嵌入式編程中管理數(shù)據(jù)非常有幫助。 2. 選擇合適的編程語言 嵌入式開發(fā)中常用的編程語言
    發(fā)表于 12-12 10:51

    嵌入式學(xué)習(xí)建議

    原理的嵌入式操作系統(tǒng)進(jìn)行學(xué)習(xí)。不要一開始就學(xué)習(xí)幾種操作系統(tǒng),理解了基本原理,實(shí)踐中確有實(shí)際需要再學(xué)習(xí)也不遲。人總是要不斷學(xué)習(xí)的。 ⑨關(guān)于匯編語言C語言的取舍。隨著:MCU對
    發(fā)表于 10-22 11:41

    嵌入式系統(tǒng)的未來趨勢有哪些?

    (ML)技術(shù)的快速發(fā)展,嵌入式系統(tǒng)將更多地整合這些先進(jìn)技術(shù),以支持智能決策和自動化。在設(shè)備上直接運(yùn)行AI和ML模型,進(jìn)行圖像識別、自然語言處理、預(yù)測分析等任務(wù),將極大提升嵌入式系統(tǒng)的智能化水平。比如說在
    發(fā)表于 09-12 15:42

    七大嵌入式GUI盤點(diǎn)

    LCD設(shè)計(jì)提供高級支持,極大簡化了LCD設(shè)計(jì)。它是使用比較廣泛的一款GUI,配合GUI Builder或App Wizard上位機(jī)軟件,用起來也比較方便。emWin以C語言源代碼提供,使其成為
    發(fā)表于 09-02 10:58

    學(xué)習(xí)hypervisor嵌入式產(chǎn)品安全設(shè)計(jì)

    、PRTOS的配套工具等內(nèi)容。第三部分(第13~15章)介紹嵌入式Hypervisor的高級應(yīng)用和未來規(guī)劃,涵蓋μC/OS-II與Linux內(nèi)核的虛擬化過程,以及PRTOS社區(qū)的愿景等內(nèi)容。
    發(fā)表于 08-25 09:11

    嵌入式系統(tǒng)怎么學(xué)?

    一系列課程和技術(shù),包括但不限于以下內(nèi)容: 1、基礎(chǔ)知識:學(xué)習(xí)計(jì)算機(jī)組成原理、數(shù)字電路、模擬電路等基礎(chǔ)知識,建立對計(jì)算機(jī)硬件的認(rèn)知與理解。 2、編程語言:掌握至少一種嵌入式系統(tǒng)常用的編程語言,如
    發(fā)表于 07-02 10:10

    C語言嵌入式開發(fā)中的關(guān)鍵編譯器角色

    嵌入式程序開發(fā)跟硬件密切相關(guān),需要使用C語言來讀寫底層寄存器、存取數(shù)據(jù)、控制硬件等,C語言和硬件之間由編譯器來聯(lián)系,一些
    發(fā)表于 04-26 14:53 ?611次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b>:<b class='flag-5'>嵌入式</b>開發(fā)中的關(guān)鍵編譯器角色

    如何成為一名嵌入式C語言高手?

    如何成為一名嵌入式C語言高手? 嵌入式系統(tǒng)是當(dāng)今科技領(lǐng)域的核心,而C語言則是
    發(fā)表于 04-07 16:03

    如何成為一名嵌入式C語言高手?

    如何成為一名嵌入式C語言高手? 嵌入式系統(tǒng)是當(dāng)今科技領(lǐng)域的核心,而C語言則是
    發(fā)表于 03-25 14:12

    嵌入式人工智能的就業(yè)方向有哪些?

    的發(fā)展方向有很多,門檻高低不一樣。下面給大家列舉一些相應(yīng)崗位所需具備的技能: 一:嵌入式Linux,C語言開發(fā)工程師,Linux軟件開發(fā)工程師 LinuxC語言開發(fā) LinuxC
    發(fā)表于 02-26 10:17

    c語言,c++,java,python區(qū)別

    操作系統(tǒng)、嵌入式系統(tǒng)等對性能要求較高的場景。C語言的語法相對簡單,學(xué)習(xí)曲線較平緩,也是學(xué)習(xí)其他高級語言的入門
    的頭像 發(fā)表于 02-05 14:11 ?2370次閱讀

    嵌入式學(xué)習(xí)步驟

    開發(fā)。 嵌入式學(xué)習(xí)步驟總結(jié)如下: (1).確定目標(biāo)平臺:選擇適合您要開發(fā)的嵌入式系統(tǒng)的硬件平臺。這取決于您要控制的設(shè)備以及您需要執(zhí)行的任務(wù)。 (2).選擇編程語言嵌入式系統(tǒng)通常使用
    發(fā)表于 02-02 15:24

    聊一聊嵌入式C語言

    作為一名嵌入式軟件開發(fā)者,熟練掌握嵌入式C語言對我的日常工作至關(guān)重要。
    的頭像 發(fā)表于 01-22 09:28 ?544次閱讀

    嵌入式自學(xué)好書推薦

    嵌入式自學(xué)好書推薦 在數(shù)字時代的浪潮中,嵌入式系統(tǒng)一直是數(shù)字電子產(chǎn)品中的重要組成部分。無論是家用電器、工業(yè)控制、汽車電子、醫(yī)療保健、軍事應(yīng)用還是物聯(lián)網(wǎng),嵌入式系統(tǒng)都無處不在,展現(xiàn)了廣闊的前景。隨著
    發(fā)表于 01-11 15:13

    嵌入式開發(fā)常見的C語言技巧與方法分享

    嵌入式開發(fā)中,常常要操作寄存器,對寄存器進(jìn)行寫入,讀出等等操作。每個寄存器都有自己固有的地址,通過C語言訪問這些地址就變得尤為重要。
    的頭像 發(fā)表于 12-26 09:55 ?1095次閱讀
    主站蜘蛛池模板: 午夜福利电影网站鲁片大全| 99久久香蕉国产线看观看| 草莓视频免费在线观看| 九九在线精品亚洲国产| 日本熟妇多毛XXXXX视频| 在线观看免费国产成人软件| 国产精品久久久久久AV免费不卡 | 皮皮色狼网| 伊人久久大香线蕉电影院| 国产 亚洲 另类 欧美 在线| 蜜芽无码亚洲资源网站| 亚洲人成在线播放网站岛国| 丰满艳妇亲伦| 嫩草视频在线观看免费| 在线毛片片免费观看| 国产亚洲日韩另类在线播放| 色爱AV综合区| 99re5.久久热在线视频| 久久精品美女| 亚洲VA欧美VA天堂V国产综合| 大学生高潮无套内谢视频| 免费看欧美xxx片| 伊人久久青青草| 国产亚洲精品线视频在线| 色窝窝亚洲AV在线观看| NANANA在线观看高清影院| 乱码AV午夜噜噜噜噜| 一本色道久久88综合日韩精品 | 午夜精品久久久久久久爽牛战| chinese情侣自拍啪hd| 两个人的视频hd全免费 | 最近中文字幕2019国语4| 好男人在线观看视频观看高清视频免费 | 在线观看qvod| 黄页网站18以下勿看免费| 视频一区视频二区在线观看| 办公室激情在线观看| 欧美人成人亚洲专区中文字幕| 97精品国产自产在线观看永久| 久久久久国产精品美女毛片| 一个人的HD高清在线观看|