C語言在嵌入式學(xué)習(xí)中是必備的知識,審核大部分操作都要圍繞C語言進行,而其中有三塊“難啃的硬骨頭”幾乎是公認級別的。
01
指針
指針公認最難理解的概念,也是讓很多初學(xué)者選擇放棄的直接原因
指針之所以難理解,因為指針本身就是一個變量,是一個非常特殊的變量,專門存放地址的變量,這個地址需要給申請空間才能裝東西,而且因為是個變量可以中間賦值,這么一倒騰很多人就開始犯暈了,繞不開彎了。C語言之所以被很多高手所喜歡,就是指針的魅力,中間可以靈活的切換,執(zhí)行效率超高,這點也是讓小白暈菜的地方。
指針是學(xué)習(xí)繞不過去的知識點,而且學(xué)完C語言,下一步緊接著切換到數(shù)據(jù)結(jié)構(gòu)和算法,指針是切換的重點,指針搞不定下一步進行起來就很難,會讓很多人放棄繼續(xù)學(xué)習(xí)的勇氣。
指針直接對接內(nèi)存結(jié)構(gòu),常見的C語言里面的指針亂指,數(shù)組越界根本原因就是內(nèi)存問題。在指針這個點有無窮無盡的發(fā)揮空間。很多編程的技巧都在此集結(jié)。
指針還涉及如何申請釋放內(nèi)存,如果釋放不及時就會出現(xiàn)內(nèi)存泄露的情況,指針是高效好用,但不徹底搞明白對于有些人來說簡直就是噩夢。
在概念方面問題可以參見此前推文《對于C語言指針最詳盡的講解》,那么在指針方面可以參見一下大神的經(jīng)驗:
▎復(fù)雜類型說明
要了解指針,多多少少會出現(xiàn)一些比較復(fù)雜的類型。所以先介紹一下如何完全理解一個復(fù)雜類型。
要理解復(fù)雜類型其實很簡單,一個類型里會出現(xiàn)很多運算符,他們也像普通的表達式一樣,有優(yōu)先級,其優(yōu)先級和運算優(yōu)先級一樣。
所以筆者總結(jié)了一下其原則:從變量名處起,根據(jù)運算符優(yōu)先級結(jié)合,一步一步分析。
下面讓我們先從簡單的類型開始慢慢分析吧。
int p;
這是一個普通的整型變量
int p;
首先從P處開始,先與結(jié)合,所以說明P是一個指針。然后再與int結(jié)合,說明指針所指向的內(nèi)容的類型為int型,所以P是一個返回整型數(shù)據(jù)的指針
int p[3];
首先從P處開始,先與[]結(jié)合,說明P是一個數(shù)組。然后與int結(jié)合,說明數(shù)組里的元素是整型的,所以P是一個由整型數(shù)據(jù)組成的數(shù)組。
int *p[3];
首先從P處開始,先與[]結(jié)合,因為其優(yōu)先級比高,所以P是一個數(shù)組。然后再與結(jié)合,說明數(shù)組里的元素是指針類型。之后再與int結(jié)合,說明指針所指向的內(nèi)容的類型是整型的,所以P是一個由返回整型數(shù)據(jù)的指針所組成的數(shù)組。
int (*p)[3];
首先從P處開始,先與結(jié)合,說明P是一個指針。然后再與[]結(jié)合(與"()"這步可以忽略,只是為了改變優(yōu)先級),說明指針所指向的內(nèi)容是一個數(shù)組。之后再與int結(jié)合,說明數(shù)組里的元素是整型的。所以P是一個指向由整型數(shù)據(jù)組成3個整數(shù)的指針。
int **p;
首先從P開始,先與*結(jié)合,說明P是一個指針。然后再與*結(jié)合,說明指針所指向的元素是指針。之后再與int結(jié)合,說明該指針所指向的元素是整型數(shù)據(jù)。由于二級指針以及更高級的指針極少用在復(fù)雜的類型中,所以后面更復(fù)雜的類型我們就不考慮多級指針了,最多只考慮一級指針。
int p(int);
從P處起,先與()結(jié)合,說明P是一個函數(shù)。然后進入()里分析,說明該函數(shù)有一個整型變量的參數(shù),之后再與外面的int結(jié)合,說明函數(shù)的返回值是一個整型數(shù)據(jù)。
Int(*p)(int);
從P處開始,先與指針結(jié)合,說明P是一個指針。然后與()結(jié)合,說明指針指向的是一個函數(shù)。之后再與()里的int結(jié)合,說明函數(shù)有一個int型的參數(shù),再與最外層的int結(jié)合,說明函數(shù)的返回類型是整型,所以P是一個指向有一個整型參數(shù)且返回類型為整型的函數(shù)的指針。
int (p(int))[3];
可以先跳過,不看這個類型,過于復(fù)雜。從P開始,先與()結(jié)合,說明P是一個函數(shù)。然后進入()里面,與int結(jié)合,說明函數(shù)有一個整型變量參數(shù)。然后再與外面的結(jié)合,說明函數(shù)返回的是一個指針。之后到最外面一層,先與[]結(jié)合,說明返回的指針指向的是一個數(shù)組。接著再與結(jié)合,說明數(shù)組里的元素是指針,最后再與int結(jié)合,說明指針指向的內(nèi)容是整型數(shù)據(jù)。所以P是一個參數(shù)為一個整數(shù)據(jù)且返回一個指向由整型指針變量組成的數(shù)組的指針變量的函數(shù)。
說到這里也就差不多了。理解了這幾個類型,其它的類型對我們來說也是小菜了。不過一般不會用太復(fù)雜的類型,那樣會大大減小程序的可讀性,請慎用。這上面的幾種類型已經(jīng)足夠我們用了。
▎細說指針
指針是一個特殊的變量,它里面存儲的數(shù)值被解釋成為內(nèi)存里的一個地址。
要搞清一個指針需要搞清指針的四方面的內(nèi)容:指針的類型、指針所指向的類型、指針的值或者叫指針所指向的內(nèi)存區(qū)、指針本身所占據(jù)的內(nèi)存區(qū)。讓我們分別說明。
先聲明幾個指針放著做例子:
(1)int*ptr;
(2)char*ptr;
(3)int**ptr;
(4)int(*ptr)[3];
(5)int*(*ptr)[4];
▎指針的類型
從語法的角度看,小伙伴們只要把指針聲明語句里的指針名字去掉,剩下的部分就是這個指針的類型。這是指針本身所具有的類型。
讓我們看看上述例子中各個指針的類型:
(1)intptr;//指針的類型是int
(2)charptr;//指針的類型是char
(3)intptr;//指針的類型是int
(4)int(ptr)[3];//指針的類型是int()[3]
(5)int*(ptr)[4];//指針的類型是int(*)[4]
怎么樣?找出指針的類型的方法是不是很簡單?
▎指針所指向的類型
當通過指針來訪問指針所指向的內(nèi)存區(qū)時,指針所指向的類型決定了編譯器將把那片內(nèi)存區(qū)里的內(nèi)容當做什么來看待。
從語法上看,小伙伴們只需把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉,剩下的就是指針所指向的類型。
上述例子中各個指針所指向的類型:
(1)intptr; //指針所指向的類型是int
(2)char*ptr; //指針所指向的的類型是char*
(3)int*ptr; //指針所指向的的類型是int*
(4)int(*ptr)[3]; //指針所指向的的類型是int(*)[3]
(5)int*(*ptr)[4]; //指針所指向的的類型是int*(*)[4]
在指針的算術(shù)運算中,指針所指向的類型有很大的作用。
指針的類型(即指針本身的類型)和指針所指向的類型是兩個概念。當小伙伴們對C 越來越熟悉時,就會發(fā)現(xiàn),把與指針攪和在一起的"類型"這個概念分成"指針的類型"和"指針所指向的類型"兩個概念,是精通指針的關(guān)鍵點之一。
筆者看了不少書,發(fā)現(xiàn)有些寫得差的書中,就把指針的這兩個概念攪在一起了,所以看起書來前后矛盾,越看越糊涂。
▎指針的值
即指針所指向的內(nèi)存區(qū)或地址。
指針的值是指針本身存儲的數(shù)值,這個值將被編譯器當作一個地址,而不是一個一般的數(shù)值。
在32位程序里,所有類型的指針的值都是一個32位整數(shù),因為32位程序里內(nèi)存地址全都是32位長。指針所指向的內(nèi)存區(qū)就是從指針的值所代表的那個內(nèi)存地址開始,長度為si zeof(指針所指向的類型)的一片內(nèi)存區(qū)。
以后,我們說一個指針的值是XX,就相當于說該指針指向了以XX為首地址的一片內(nèi)存區(qū)域;我們說一個指針指向了某塊內(nèi)存區(qū)域,就相當于說該指針的值是這塊內(nèi)存區(qū)域的首地址。
指針所指向的內(nèi)存區(qū)和指針所指向的類型是兩個完全不同的概念。在例一中,指針所指向的類型已經(jīng)有了,但由于指針還未初始化,所以它所指向的內(nèi)存區(qū)是不存在的,或者說是無意義的。
以后,每遇到一個指針,都應(yīng)該問問:這個指針的類型是什么?指針指的類型是什么?該指針指向了哪里?
▎指針本身所占據(jù)的內(nèi)存區(qū)
指針本身占了多大的內(nèi)存?只要用函數(shù)sizeof(指針的類型)測一下就知道了。在32位平臺里,指針本身占據(jù)4個字節(jié)的長度。指針本身占據(jù)的內(nèi)存這個概念在判斷一個指針表達式是否是左值時很有用。
02
函數(shù)概念
面向過程對象模塊的基本單位,以及對應(yīng)各種組合,函數(shù)指針,指針函數(shù)
一個函數(shù)就是一個業(yè)務(wù)邏輯塊,是面向過程,單元模塊的最小單元,而且在函數(shù)的執(zhí)行過程中,形參,實參如何交換數(shù)據(jù),如何將數(shù)據(jù)傳遞出去,如何設(shè)計一個合理的函數(shù),不單單是解決一個功能,還要看是不是能夠復(fù)用,避免重復(fù)造輪子。
函數(shù)指針和指針函數(shù),表面是兩個字面意思的互換實際上含義截然不同,指針函數(shù)比較好理解,就是返回指針的一個函數(shù),函數(shù)指針這個主要用在回調(diào)函數(shù),很多人覺得函數(shù)都沒還搞明白,回調(diào)函數(shù)更暈菜了。其實可以通俗的理解指向函數(shù)的指針,本身是一個指針變量,只不過在初始化的時候指向了函數(shù),這又回到了指針層面。沒搞明白指針再次深入的向前走特別難。
C語言的開發(fā)者們?yōu)楹髞淼拈_發(fā)者做了一些省力氣的事情,他們編寫了大量代碼,將常見的基本功能都完成了,可以讓別人直接拿來使用。但是那么多代碼,如何從中找到自己需要的呢?將所有代碼都拿來顯然是不太現(xiàn)實。
但是這些代碼,早已被早期的開發(fā)者們分門別類地放在了不同的文件中,并且每一段代碼都有唯一的名字。所以其實學(xué)習(xí)C語言并沒有那么難,尤其是可以在動手鍛煉做項目中進行。使用代碼時,只要在對應(yīng)的名字后面加上( )就可以。這樣的一段代碼就是函數(shù),函數(shù)能夠獨立地完成某個功能,一次編寫完成后可以多次使用。
很多初學(xué)者可能都會把C語言中的函數(shù)和數(shù)學(xué)中的函數(shù)概念搞混淆。其實真相并沒有那么復(fù)雜,C語言中的函數(shù)是有規(guī)律可循跡的,只要搞清楚了概念你會發(fā)現(xiàn)還挺有意思的。
函數(shù)的英文名稱是 Function,對應(yīng)翻譯過來的中文還有“功能”的意思。C語言中的函數(shù)也跟功能有著密切的關(guān)系。
我們來看一小段C語言代碼:
#include
把目光放在第4行代碼上,這行代碼會在顯示器上輸出“Hello World”。前面我們已經(jīng)講過,puts 后面要帶( ),字符串也要放在( )中。
在C語言中,有的語句使用時不能帶括號,有的語句必須帶括號。帶括號的就是函數(shù)(Function)。
C語言提供了很多功能,我們只需要一句簡單的代碼就能夠使用。但是這些功能的底層都比較復(fù)雜,通常是軟件和硬件的結(jié)合,還要要考慮很多細節(jié)和邊界,如果將這些功能都交給程序員去完成,那將極大增加程序員的學(xué)習(xí)成本,降低編程效率。
有了函數(shù)之后,C語言的編程效率就好像有了神器一樣,開發(fā)者們只需要隨時調(diào)用就可以了,像進程函數(shù)、操作函數(shù)、時間日期函數(shù)等都可以幫助我們直接實現(xiàn)C語言本身的功能。
C語言函數(shù)是可以重復(fù)使用的。
函數(shù)的一個明顯特征就是使用時必須帶括號( ),必要的話,括號中還可以包含待處理的數(shù)據(jù)。例如puts("尚觀科技")就使用了一段具有輸出功能的代碼,這段代碼的名字是 puts,"尚觀科技" 是要交給這段代碼處理的數(shù)據(jù)。使用函數(shù)在編程中有專業(yè)的稱呼,叫做函數(shù)調(diào)用(Function Call)。
如果函數(shù)需要處理多個數(shù)據(jù),那么它們之間使用逗號,分隔,例如:
pow(10, 2);
該函數(shù)用來求10的2次方。
好了,看到這里你有沒有覺得其實C語言函數(shù)還是比較有意思的,而且并沒有那么復(fù)雜困難。以后再遇到菜鳥小白的時候,你一口一個C語言的函數(shù),說不定就能當場引來無數(shù)膜拜的目光。
03
結(jié)構(gòu)體,遞歸
很多在大學(xué)學(xué)習(xí)C語言的,很多課程都沒學(xué)完,結(jié)構(gòu)體都沒學(xué)到,因為從章節(jié)的安排來看好像,結(jié)構(gòu)體學(xué)習(xí)放在教材的后半部分了,弄得很多學(xué)生覺得結(jié)構(gòu)體不重要,如果只是應(yīng)付學(xué)校的考試,或者就是為了混個畢業(yè)證,的確學(xué)的意義不大。
如果想從事編程這個行業(yè),對這個概念還不了解,基本上無法構(gòu)造數(shù)據(jù)模型,沒有一個業(yè)務(wù)體是完全使用原生數(shù)據(jù)類型來完成的,很多高手在設(shè)計數(shù)據(jù)模型的時候,一般先把頭文件中的結(jié)構(gòu)體數(shù)據(jù)整理出來。然后設(shè)計好功能函數(shù)的參數(shù),以及名字,然后才真正開始寫c源碼。
如果從節(jié)省空間考慮結(jié)構(gòu)體里面的數(shù)據(jù)放的順序不一樣在內(nèi)存中占用的空間也不一樣,結(jié)構(gòu)體與結(jié)構(gòu)體之間賦值,結(jié)構(gòu)體存在指針那么賦值要特別注意,需要進行深度的賦值。
遞歸一般用于從頭到位統(tǒng)計或者羅列一些數(shù)據(jù),在使用的時候很多初學(xué)者都覺得別扭,怎么還能自己調(diào)用自己?而且在使用的時候,一定設(shè)置好跳出的條件,不然無休止的進行下去,真就成無線死循環(huán)了。
對于結(jié)構(gòu)體方面的知識,可以參見此前推送的文章《C語言結(jié)構(gòu)體(struct)最全的講解(萬字干貨)》。具體也可以參見大佬的經(jīng)驗:
相信大家對于結(jié)構(gòu)體都不陌生。在此,分享出本人對C語言結(jié)構(gòu)體的研究和學(xué)習(xí)的總結(jié)。如果你發(fā)現(xiàn)這個總結(jié)中有你以前所未掌握的,那本文也算是有點價值了。當然,水平有限,若發(fā)現(xiàn)不足之處懇請指出。代碼文件test.c我放在下面。 在此,我會圍繞以下2個問題來分析和應(yīng)用C語言結(jié)構(gòu)體:
1.C語言中的結(jié)構(gòu)體有何作用2.結(jié)構(gòu)體成員變量內(nèi)存對齊有何講究(重點)
對于一些概念的說明,我就不把C語言教材上的定義搬上來。我們坐下來慢慢聊吧。
1. 結(jié)構(gòu)體有何作用
三個月前,教研室里一個學(xué)長在華為南京研究院的面試中就遇到這個問題。當然,這只是面試中最基礎(chǔ)的問題。如果問你你怎么回答? 我的理解是這樣的,C語言中結(jié)構(gòu)體至少有以下三個作用:
(1) 有機地組織了對象的屬性。
比如,在STM32的RTC開發(fā)中,我們需要數(shù)據(jù)來表示日期和時間,這些數(shù)據(jù)通常是年、月、日、時、分、秒。如果我們不用結(jié)構(gòu)體,那么就需要定義6個變量來表示。這樣的話程序的數(shù)據(jù)結(jié)構(gòu)是松散的,我們的數(shù)據(jù)結(jié)構(gòu)最好是“高內(nèi)聚,低耦合”的。所以,用一個結(jié)構(gòu)體來表示更好,無論是從程序的可讀性還是可移植性還是可維護性皆是:
typedef struct //公歷日期和時間結(jié)構(gòu)體{vu16 year;vu8 month;vu8 date;vu8 hour;vu8 min;vu8 sec;}_calendar_obj;_calendar_obj calendar; //定義結(jié)構(gòu)體變量
(2) 以修改結(jié)構(gòu)體成員變量的方法代替了函數(shù)(入口參數(shù))的重新定義。
如果說結(jié)構(gòu)體有機地組織了對象的屬性表示結(jié)構(gòu)體“中看”,那么以修改結(jié)構(gòu)體成員變量的方法代替函數(shù)(入口參數(shù))的重新定義就表示了結(jié)構(gòu)體“中用”。繼續(xù)以上面的結(jié)構(gòu)體為例子,我們來分析。假如現(xiàn)在我有如下函數(shù)來顯示日期和時間:
void DsipDateTime( _calendar_obj DateTimeVal)
那么我們只要將一個_calendar_obj這個結(jié)構(gòu)體類型的變量作為實參調(diào)用DsipDateTime()即可,DsipDateTime()通過DateTimeVal的成變量來實現(xiàn)內(nèi)容的顯示。如果不用結(jié)構(gòu)體,我們很可能需要寫這樣的一個函數(shù):
voidDsipDateTime(vu16year,vu8month,vu8date,vu8hour,vu8min,vu8sec) 顯然這樣的形參很不可觀,數(shù)據(jù)結(jié)構(gòu)管理起來也很繁瑣。如果某個函數(shù)的返回值得是一個表示日期和時間的數(shù)據(jù),那就更復(fù)雜了。這只是一方面。 另一方面,如果用戶需要表示日期和時間的數(shù)據(jù)中還要包含星期(周),這個時候,如果之前沒有用機構(gòu)體,那么應(yīng)該在DsipDateTime()函數(shù)中在增加一個形參vu8 week:
void DsipDateTime( vu16 year,vu8 month,vu8 date,vu8 week,vu8 hour,vu8 min,vu8 sec)
可見這種方法來傳遞參數(shù)非常繁瑣。所以以結(jié)構(gòu)體作為函數(shù)的入口參數(shù)的好處之一就是函數(shù)的聲明void DsipDateTime( _calendar_obj DateTimeVal)不需要改變,只需要增加結(jié)構(gòu)體的成員變量,然后在函數(shù)的內(nèi)部實現(xiàn)上對calendar.week作相應(yīng)的處理即可。這樣,在程序的修改、維護方面作用顯著。
typedef struct //公歷日期和時間結(jié)構(gòu)體{vu16 year;vu8 month;vu8 date;vu8 week;vu8 hour;vu8 min;vu8 sec;}_calendar_obj;_calendar_obj calendar; //定義結(jié)構(gòu)體變量
(3) 結(jié)構(gòu)體的內(nèi)存對齊原則可以提高CPU對內(nèi)存的訪問速度(以空間換取時間)。
并且,結(jié)構(gòu)體成員變量的地址可以根據(jù)基地址(以偏移量offset)計算。我們先來看看下面的一段簡單的程序,對于此程序的分析會在第2部分結(jié)構(gòu)體成員變量內(nèi)存對齊中詳細說明。
#include
2. 結(jié)構(gòu)體成員變量內(nèi)存對齊
首先,我們來分析一下上面程序的運行結(jié)果。前三行說明在我的程序中,char型占1個字節(jié),short型占2個字節(jié),long型占4個字節(jié)。char_short_long、long_short_char和char_long_short是三個結(jié)構(gòu)體成員相同但是成員變量的排列順序不同。并且從程序的運行結(jié)果來看,
Size of char_short_long = 8 bytesSize of long_short_char = 8 bytesSize of char_long_short = 12 bytes //比前兩種情況大4 byte !
并且,還要注意到,1 byte (char)+ 2 byte (short)+ 4 byte (long) = 7 byte,而不是8 byte。
所以,結(jié)構(gòu)體成員變量的放置順序影響著結(jié)構(gòu)體所占的內(nèi)存空間的大小。一個結(jié)構(gòu)體變量所占內(nèi)存的大小不一定等于其成員變量所占空間之和。如果一個用戶程序或者操作系統(tǒng)(比如uC/OS-II)中存在大量結(jié)構(gòu)體變量時,這種內(nèi)存占用必須要進行優(yōu)化,也就是說,結(jié)構(gòu)體內(nèi)部成員變量的排列次序是有講究的。 結(jié)構(gòu)體成員變量到底是如何存放的呢? 在這里,我就不賣關(guān)子了,直接給出如下結(jié)論,在沒有#pragma pack宏的情況下:
原則1結(jié)構(gòu)(struct或聯(lián)合union)的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員存儲的起始位置要從該成員大小的整數(shù)倍開始(比如int在32位機為4字節(jié),則要從4的整數(shù)倍地址開始存儲)。
原則2結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,必須是其內(nèi)部最大成員的整數(shù)倍,不足的要補齊。
*原則3結(jié)構(gòu)體作為成員時,結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存儲。(struct a里存有struct b,b里有char,int,double等元素時,那么b應(yīng)該從8的整數(shù)倍地址處開始存儲,因為sizeof(double) = 8 bytes)
這里,我們結(jié)合上面的程序來分析(暫時不討論原則3)。
先看看char_short_long和long_short_char這兩個結(jié)構(gòu)體,從它們的成員變量的地址可以看出來,這兩個結(jié)構(gòu)體符合原則1和原則2。注意,在 char_short_long的成員變量的地址中,char_short_long.s的地址是1244994,也就是說,1244993是“空的”,只是被“占位”了!
再看看char_long_short這個結(jié)構(gòu)體,char_long_short的地址分布情況如下表:
成員變量 | 成員變量十六進制地址 | 成員變量十進制地址 |
char_long_short.c | 0x0012FF2C | 1244972 |
char_long_short.l | 0x0012FF30 | 1244976 |
char_long_short.s | 0x0012FF34 | 1244980 |
可見,其內(nèi)存分布圖如下,共12 bytes:
地址 | 1244972 | 1244973 | 1244974 | 1244975 | 1244976 | 1244977 | 1244978 | 1244979 | 1244980 | 1244981 | 1244982 | 1244983 |
成員 | .c | .l | .s |
首先,1244972能被1整除,所以char_long_short.c放在1244972處沒有問題(其實,就char型成員變量自身來說,其放在任何地址單元處都沒有問題),根據(jù)原則1,在之后的1244973~1244975中都沒有能被4(因為sizeof(long)=4bytes)整除的,1244976能被4整除,所以char_long_short.l應(yīng)該放在1244976處,那么同理,最后一個.s(sizeof(short)=2 bytes)是應(yīng)該放在1244980處。
是不是這樣就結(jié)束了?不是,還有原則2。根據(jù)原則2的要求,char_long_short這個結(jié)構(gòu)體所占的空間大小應(yīng)該是其占內(nèi)存空間最大的成員變量的大小的整數(shù)倍。如果我們到此就結(jié)束了,那么char_long_short所占的內(nèi)存空間是1244972~1244981共計10bytes,不符合原則2,所以,必須在最后補齊2個 bytes(1244982~1244983)。
至此,一個結(jié)構(gòu)體的內(nèi)存布局完成了。
下面我們按照上述原則,來驗證這樣的分析是不是正確。按上面的分析,地址單元1244973、1244974、1244975以及1244982、1244983都是空的(至少char_long_short未用到,只是“占位”了)。如果我們的分析是正確的,那么,定義這樣一個結(jié)構(gòu)體,其所占內(nèi)存也應(yīng)該是12 bytes:
struct //聲明結(jié)構(gòu)體char_long_short_new{charc;char add1; //補齊空間char add2; //補齊空間charadd3;//補齊空間long l;shorts;char add4; //補齊空間charadd5;//補齊空間}char_long_short_new; 運行結(jié)果如下:
可見,我們的分析是正確的。至于原則3,大家可以自己編程驗證,這里就不再討論了。
所以,無論你是在VC6.0還是Keil C51,還是Keil MDK中,當你需要定義一個結(jié)構(gòu)體時,只要你稍微留心結(jié)構(gòu)體成員變量內(nèi)存對齊這一現(xiàn)象,就可以在很大程度上節(jié)約MCU的RAM。這一點不僅僅應(yīng)用于實際編程,在很多大型公司,比如IBM、微軟、百度、華為的筆試和面試中,也是常見的。
這三大塊硬骨頭是學(xué)習(xí)C語言的絆腳石,下功夫拿掉基本上C語言的大動脈就打通了,那么再去學(xué)習(xí)別的內(nèi)容就相對比較簡單了。編程學(xué)習(xí)過程中越是痛苦的時候,學(xué)到的東西就會越多,克服過去就會自己的技能,放棄了前面的付出的時間都將清零。越是難學(xué)的語言在入門之后,在入門之后越覺得過癮,而且還容易上癮。你上癮了沒?還是放棄了?
-
嵌入式
+關(guān)注
關(guān)注
5085文章
19138瀏覽量
305705 -
C語言
+關(guān)注
關(guān)注
180文章
7605瀏覽量
136994 -
指針
+關(guān)注
關(guān)注
1文章
480瀏覽量
70573
原文標題:C語言中三塊“難啃的硬骨頭”
文章出處:【微信號:WW_CGQJS,微信公眾號:傳感器技術(shù)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論