C語言高級部分總結

C語言高級部分總結

嵌入式ARM 3天前

信息來源于網(wǎng)絡

一束凑、內(nèi)存大話題

1.0、內(nèi)存就是程序的立足之地,體現(xiàn)內(nèi)存重要性拼窥。

1.1抛人、內(nèi)存理解:

內(nèi)存物理看是有很多個Bank(就是行列陣式的存儲芯片),每一個Bank的列就是位寬 糜芳,每一行就是Words飒货,則存儲單元數(shù)量=行數(shù)(words)×列數(shù)(位寬)×Bank的數(shù)量;通常也用M×W的方式來表示芯片的容量(或者說是芯片的規(guī)格/組織結構)峭竣。

M是以位寬為單位的總?cè)萘刻粮ǎ瑔挝皇钦?,W代表位寬邪驮, 單位是bit莫辨。計算出來的芯片容量也是以bit為單位,但用戶可以采用除以8的方法換算為字節(jié)(Byte)毅访。比如8M×8沮榜,這是一個8bit位寬芯片,有8M個存儲單元喻粹,總?cè)萘渴?4Mbit(8MB)蟆融。

1.2、c語言中其實沒有bool類型:以0表示假守呜,非0表示真型酥,則在內(nèi)存存儲是以int型存放的山憨。如果想要表示真假,可以用int/char型做替換弥喉,在c++中就有bool x=true/false;

1.3郁竟、內(nèi)存對齊:內(nèi)存對齊(提高訪問效率速度,編譯器一般默認是4字節(jié)對齊)

1.4由境、char/int/short/long/float/double型:放在內(nèi)存的長度和解析作用棚亩。(int *)0,使0地址指向一個int型虏杰。又比如0000111010101可以解析成int型也可以解析成float型讥蟆。

1.5、Linux內(nèi)核是面向?qū)ο蟮姆睦鴆語言是面向過程的瘸彤,但可以用結構體內(nèi)嵌指針變成面向?qū)ο蟆H?/p>

struct student{
int age; //變量
int lenth; //將相當于一個類笛钝,有變量有函數(shù)
char *name;
void (*eat)(void); //函數(shù)指針
}

1.6质况、棧的理解:

(1) 運行時自動分配&自動回收:棧是自動管理的,程序員不需要手工干預婆翔。方便簡單拯杠。(表現(xiàn)在匯編代碼,編譯時啃奴,會自動編譯成匯編碼實現(xiàn)函數(shù)調(diào)用完立即改變棧頂)

(2) 反復使用:棧內(nèi)存在程序中其實就是那一塊空間潭陪,程序反復使用這一塊空間。(硬件上有個寄存器最蕾,用來存放棧的棧頂?shù)刂芬浪荩瑮J怯写笮〉目臻g)

(3) 臟內(nèi)存:棧內(nèi)存由于反復使用,每次使用后程序不會去清理瘟则,因此分配到時保留原來的值黎炉。

(4) 臨時性:(函數(shù)不能返回棧變量的指針,因為這個空間是臨時的)

(5) 棧會溢出:因為操作系統(tǒng)事先給定了棧的大小醋拧,如果在函數(shù)中無窮盡的分配棧內(nèi)存總能用完慷嗜。棧的操作(怎么出棧怎么入棧)是由具體硬件來干預,程序員只要明白原理就可以了丹壕,但是要給相應的棧寄存器賦值庆械。當調(diào)用函數(shù)時,變量會自動放在棧中(入棧)當函數(shù)調(diào)用完后菌赖,棧會自動出棧.

( 6 ) 棧的 "發(fā)展"有四種情況缭乘,滿增棧,滿減棧琉用,空增棧堕绩,空減棧策幼,至于是那種要根據(jù)編譯器決定,而s5pv21 是滿減棧奴紧。

1.7特姐、堆的理解:

(1)操作系統(tǒng)堆管理器管理:堆管理器是操作系統(tǒng)的一個模塊,堆管理內(nèi)存分配靈活绰寞,按需分配到逊。

(2)大塊內(nèi)存:堆內(nèi)存管理者總量很大的操作系統(tǒng)內(nèi)存塊,各進程可以按需申請使用滤钱,使用完釋放。

(3)臟內(nèi)存:堆內(nèi)存也是反復使用的脑题,而且使用者用完釋放前不會清除件缸,因此也是臟的。

(4)臨時性:堆內(nèi)存只在malloc和free之間屬于我這個進程叔遂,而可以訪問他炊。在malloc之前和free之后都不能再訪問,否則會有不可預料的后果已艰。

(5)程序手動申請&釋放:手工意思是需要寫代碼去申請malloc和釋放free痊末。(記住:不要把申請的地址給搞丟了哩掺, 不然自己用不了凿叠,也釋放不了)

申請一段內(nèi)存,可以是:
malloc(10*sizeof ( int ) );

原型:
void *malloc(size_t size);

//指針函數(shù) size_t是宏定義int 都是便于可移植性 嚼吞,返回一個內(nèi)存地址盒件,void *可以看出,希望申請的內(nèi)存用來存放什么就強制類型什么舱禽。

calloc( 10,sizeof ( int ) ); 原型:void *calloc(size_t nmemb, size_t size);// nmemb個單元炒刁,每個單元size字節(jié)void *realloc(void ptr, size_t size);// 改變原來申請的空間的大小的ptr是原來申請內(nèi)存的指針,size是想要重新申請內(nèi)存的大小使用就是(p+1)=12 ; *(P+3)=110誊稚;

申請失敗返回NULL,申請成功返回一個地址翔始,申請之后一定要檢驗(NULL!=p)用完一定要 free ( p ) ;釋放后不是不能用里伯,是不應該使用了城瞎。可以給它“洗盤子‘俏脊,p=NULL;

其實申請的內(nèi)存并不能真正改變大小全谤,原理是先重新申請一段內(nèi)存,然后把原來申請的內(nèi)存上的內(nèi)容復制到新的內(nèi)存上爷贫,然后釋放掉原來的內(nèi)存认然,返回新的指針补憾。

(6) 在申請內(nèi)存時,malloc(0)其實也是成功的卷员,因為系統(tǒng)規(guī)定少于一定數(shù)目的大小盈匾,都申請規(guī)定的大小,如在win32系統(tǒng)下申請少于32字節(jié)的地址毕骡,最后申請到的空間是32字節(jié)削饵,在朱老師視頻中申請少于16字節(jié)的地址,最后申請到的是16字節(jié)未巫,至于規(guī)定多少字節(jié)窿撬,由具體的系統(tǒng)而言。

1.8叙凡、內(nèi)存里的數(shù)據(jù):

(1)代碼段:存放代碼二進制劈伴、常量(char *p="linux",則”linux“存放在代碼段,是不可更改的)

(2) 數(shù)據(jù)段: 存放非0全局變量握爷、靜態(tài)局部變量(局部只屬于函數(shù)的跛璧,不是整個程序的)

(3) bss : 存放為0的全局變量/為0的靜態(tài)局部變量、存放未初始化全局變量/靜態(tài)局部變量

注意:const int a=9; 有兩種存放方式:第一種確實存放在代碼段新啼,讓a不能修改追城,第二種是仍然存放在數(shù)據(jù)段中,讓編譯器來判斷燥撞,如果有改變的代碼就會報錯座柱。 至于那種,是不確定的叨吮,像單片機就屬于第一種辆布。

1.9、《1》一個源文件實際上是以段為單位編譯成連接成可執(zhí)行文件(a .out );這個可執(zhí)行文件總的說是分為數(shù)據(jù)段茶鉴,代碼段锋玲,自定義段,數(shù)據(jù)段還可以細分成 .bbs 段涵叮。而雜段會在執(zhí)行的時候拿掉惭蹂。所以a.out分為雜段,數(shù)據(jù)段(存放的是非0全局變量).bbs段割粮,代碼段盾碗。

《2》內(nèi)存實際上被劃分了兩大區(qū)域,一個是系統(tǒng)區(qū)域舀瓢,另一個是用戶區(qū)域廷雅,而每一個區(qū)域又被劃分成了幾個小區(qū)域,有堆,棧航缀,代碼區(qū)商架,.bbs區(qū),數(shù)據(jù)區(qū)(存放的是非0全局變量)芥玉。

《3》對于有操作系統(tǒng)而言蛇摸, 當我們在執(zhí)行a.out可執(zhí)行文件時,執(zhí)行這個文件的那套程序會幫我們把雜段清掉灿巧,然后把相應的段加載到內(nèi)存對應的段赶袄。對于裸機程序而言,我們是使用一套工具將a.elf的可執(zhí)行程序給清掉了所有段的符號信息抠藕,把純凈的二進制做成.bin格式的燒錄文件饿肺。所以我們加載到內(nèi)存的程序是連續(xù)的,也就是說代碼段和數(shù)據(jù)段幢痘、.bbs段都是連續(xù)的唬格。當然,椦账担空間是我們自己設置的。而且在裸機中我們不能使用malloc函數(shù)汰聋,因為我們使用的只是編譯器门粪、連接器工具沒有集成庫函數(shù),沒有定義堆空間區(qū)烹困。

《4》大總結多程序運行情況: 在Linux系統(tǒng)中運行cdw1.out時玄妈,運行這個文件的那套程序會幫我們把相應的段加載到內(nèi)存對應的段。然后操作系統(tǒng)會把下載到內(nèi)存的具體物理地址與每條命令(32位)的鏈接地址映射到TTB中(一段內(nèi)存空間)髓梅,當我們又運行cdw2.out時拟蜻,同樣也像cdw1.out一樣加載進去,并映射到TTB表中枯饿。而且這兩個.out文件默認都是鏈接0地址(邏輯)酝锅,當cpu發(fā)出一個虛擬地址(Linux中程序邏輯地址)通過TTB查找的物理地址是不一樣的。所以對于每一個程序而言奢方,它獨占4G的內(nèi)存空間搔扁,看不到其他程序。

二蟋字、位操作

2.1 ~(0u)是全1稿蹲;

2.2 位與& 位或 | 位取反~ 位異或^

2.3、位與鹊奖、位或苛聘、位異或的特點總結:

位與:(任何數(shù),其實就是1或者0)與1位與無變化,與0位與變成0
位或:(任何數(shù)设哗,其實就是1或者0)與1位或變成1唱捣,與0位或無變化
位異或:(任何數(shù),其實就是1或者0)與1位異或會取反熬拒,與0位異或無變化

2.4爷光、左移位<< 與右移位>> C語言的移位要取決于數(shù)據(jù)類型。
對于無符號數(shù)澎粟,左移時右側(cè)補0(相當于邏輯移位)
對于無符號數(shù)蛀序,右移時左側(cè)補0(相當于邏輯移位)
對于有符號數(shù),左移時右側(cè)補0(叫算術移位活烙,相當于邏輯移位)
對于有符號數(shù)徐裸,右移時左側(cè)補符號位(如果正數(shù)就補0,負數(shù)就補1啸盏,叫算術移位)

2.5重贺、小記:常與 1 拿來 做位運算。讓他取反回懦、移位 得到想要的數(shù)气笙。

2.6、直接用宏來置位怯晕、復位(最右邊為第1位)潜圃。 置位置1,復位置0 舟茶;

define SET_NTH_BIT(x, n) (x | ((1U)<<(n-1)))

define CLEAR_NTH_BIT(x, n) (x & ~((1U)<<(n-1)))

}

三谭期、指針—精髓

3.1 printf("%p \n"); 其中%p表示輸出一個指針,就是指針變量(其存放的那個地址)吧凉,可以理解為輸出一個地址隧出。

3.2 int* p1, p2 ; 等同于 int *p1; int p2;  int *p="Linux"阀捅,其不能改變*P胀瞪,因為”linux"是一個常數(shù)。

3.3 ( 代碼規(guī)范性 )在定義指針時也搓,同時賦值為NULL赏廓,在用指針時,先判斷它是不是NULL傍妒。尤其是在malloc申請內(nèi)存后幔摸,free(p);則一定要讓p=NULL

3.4 C/C++中對NULL的理解: { #ifdef _cplusplus// 定義這個符號就表示當前是C++環(huán)境

define NULL 0;// 在C++中NULL就是0

else

define NULL (void *) 0颤练;// 在C中NULL是強制類型轉(zhuǎn)換為void *的0

endif

3.5既忆、修飾詞:const (修飾變量為常量,應該理解為不應該去變它,當作常量患雇,而并非永遠不能改變,當然要看具體運行環(huán)境跃脊,在gcc,const 這種就可以采用指針方式修改,但是在在VC6.6++中就不可以修改):其雖然是當作常數(shù)苛吱,但是仍然存放在數(shù)據(jù)段中酪术,用指針仍然可以改變值。

第一種:const int *p;
第二種:int const *p;
第三種:int * const p;
第四種:const int * const p;

3.6翠储、 數(shù)組 int a[2]; 其中a是指首元素的首地址绘雁,&a是整個數(shù)組的收地址(數(shù)組指針,其這個指針指向一個數(shù)組)援所,他們的值是一樣的庐舟,但意義不一樣,可以參照 int a; int *p=&a; 來理解住拭。數(shù)組和指針天生姻緣在于數(shù)組名挪略;

int a[3]; int* p=a;是可以的,但是 int p=&a;就會報錯滔岳,盡管他們的值是一樣的杠娱,但意義不一樣,所以是不允許的谱煤,除非強制類型轉(zhuǎn)換墨辛。在訪問時是a[0],其實編譯器會把它變成(a+0)的方式,只是用a[0]看起來更方便趴俘,封裝了一下而已,實質(zhì)還是指針奏赘。

3.7寥闪、 siziof()是一個運算符,測試所占內(nèi)存空間磨淌,如 int a[100] ;sizeof(a)=400;

與strlen( )要有所區(qū)別疲憋,他是測字符串實際長度的,不包括‘\0‘梁只,如果給strlen傳的參數(shù)不是一個字符串缚柳,則它會一直去找,直到 找到第一個 ‘\0’搪锣,然后再計算其長度秋忙。

如 char a[]="chen"; char *p=a; 則strlen(p)=4;

3.8、 當數(shù)組作為一個形參時禽捆,其實參是一個數(shù)組名(也可以是指針垂券,其本質(zhì)就是指針),意義是首元素的首地址干茉,則傳過去只影響形參的第一個元素弹澎。形參數(shù)組的地址被實參數(shù)組地址所綁定朴下;

實參的大小會丟失,所以往往會傳一個int num 大小進去苦蒿。

3.9殴胧、 結構體做為形參時,應盡量用指針/地址方式來傳佩迟,因為結構體變量有時會占很大团滥,效率很低。

3.10音五、 int *p=&u; p存放的是變量u的地址惫撰,而&p的意思就是變量p本身的地址。

3.11躺涝、當要傳參的個數(shù)比較多時厨钻,我們可以打包成一個結構體,傳參的個數(shù)越多坚嗜,其開銷就更大.

3.12 一個函數(shù)作用其實就是輸入輸出夯膀,參數(shù)可以作為輸入,返回可以作為輸出苍蔬,但是當要返回多個輸出時诱建,這時候就不夠用了,所以常常返回值用來判斷程序又沒有出錯碟绑,而參數(shù)就是當作輸入輸出的俺猿,輸入時可以加const表示它沒必要去修改,而輸出都是指針格仲,因為要改變它的值押袍,只能采用地址傳遞這種方式。比如:char *strcpy(char *dest,const char *src)

四凯肋、C語言復雜表達式

4.1谊惭、在表達式中,要看符號的優(yōu)先級和結合性侮东。

4.2圈盔、在理解內(nèi)存時,內(nèi)存0地址在最底下悄雅,至上地址逐漸增加驱敲。

4.3、int *p;是定義的一指針變量p煤伟,而int ( p)[4]癌佩;也是一個指針變量p木缝;也可以這樣想:凡是遇到(p)什么的判斷他是指針后,就可以說他是指針變量围辙,包括函數(shù)指針我碟。

4.4、一個函數(shù) int max(int a ,int b); 則他的函數(shù)指針是 int ( *p ) (int ,int );其意思就是定義這個類型的函數(shù)指針變量p; p=max是賦值姚建,引用是p();則相當于max()調(diào)用這個函數(shù)矫俺。

函數(shù)指針必須和原函數(shù)的類型一樣。

4.5 函數(shù)指針其實就是為了做結構體內(nèi)嵌指針的掸冤,這樣就構成了高級語言中的類厘托。再一個就是上述4.4中p=&max;也是可以的,它和p=max,值和意義都是一樣的稿湿,這個和數(shù)組有所區(qū)別铅匹,數(shù)組的a和&a的值雖然一樣,但是意義完全不一樣饺藤。int a[4];a有兩層意思包斑,第一層是數(shù)組名,&a表示整個數(shù)組的地址涕俗,第二層表示首元素的首地址罗丰。

4.6 int (*p[4])(int ,int)其意思是函數(shù)指針數(shù)組,一個4長度的數(shù)組再姑,里面存了4個函數(shù)指針萌抵。

  • 4.7 printf在做輸出時,其機制是緩沖行來輸出元镀,即當遇到一個\n后再打印出來绍填,即使再多printf,沒有遇到\n,都不是一個一個打印栖疑。

'\r'是回車沐兰,'\n'是換行,前者使光標到行首蔽挠,后者使光標下移一格,通常敲一個回車鍵瓜浸,即是回車澳淑,又是換行(\r\n)。Unix中每行結尾只有“<換行>插佛,即“\n”杠巡;Windows中每行結尾是“<換行><回車>”,即“\r\n”雇寇;Mac中每行結尾是“<回車>”氢拥。scanf("");里面不要加\n符蚌铜。

4.8 在一個c文件中,有時候會多次引入一個.h文件嫩海,所以在寫.h文件時冬殃,要寫

{#ifndef FINE

define FINE

XXXXXXXX
XXXXXXXXXXX

endif }

4.9、typedef int *intType; const intType p,其意思是指針p為const叁怪;

4.9.1 對于typedef的定義:如typedef const int cdw; 可以這樣理解审葬,typedef就是給一個類型區(qū)別名的,那么系統(tǒng)會自動識別該類型奕谭,如果typedef const int char 則就報錯涣觉。

4.9.2 在開發(fā)中經(jīng)常會typedef int int32_t ; typedef short int16_t; 這樣做的目的是便于在不同平臺下的移植,如果當在另一個平臺下血柳,int 是64位的官册,但是我的項目中都是用的int32_t;

所以只需要修改int32_t就可以了,我可以讓他typedef short int32_t;這樣我只更改一次难捌,其余的都改了膝宁,做到一改全改。

** 4.9.3 int **p; int *a[4]; p=a;可以這樣理解:首先它是指針數(shù)組栖榨,既然是數(shù)組昆汹,則a即表示數(shù)組名又表示首元素的首地址,a[0]是一個一重指針婴栽,而a是a[0]的地址满粗,那么a就是一個二重指針;{ 一重指針的地址就是二重指針變量愚争,所以有p=a; 而 int a[4][3] ,a和一維數(shù)組的意思是一樣的映皆,如 int a[3][6],int *p ;p=a[0];所以不能p=a,int *a[3][3],int **p;p=a[0];}

** 4.9.4、二維數(shù)組是為了簡化編程轰枝,平面型捅彻。數(shù)組以下標示方式訪問其實是編譯器封裝起來的,實質(zhì)是指針訪問鞍陨。int (*p)[5]; int a[2][5];則有 p=a; 關鍵是要把二維數(shù)組抽象成n行n列用指針訪問方式理解:二維數(shù)組可以看作是一個方格子的矩陣步淹,比如a[2][5],那么就是2行5列的10個小格子,第一行可以收納起來變成一個指向一維數(shù)組的指針诚撵,第二行也是如此缭裆;

這樣收納后就變成了一個新的數(shù)組a[2],每一個格子存放的是它收納的第一個元素的地址寿烟,如a[0]存放的是第一行第一列元素的地址澈驼,“a”[1]存放的是第二行第一列的地址;

再與一維數(shù)組相聯(lián)系筛武,一維數(shù)組名即表示數(shù)組名又表示數(shù)組第一個元素的地址缝其,所以a[2][5]中的a表示“a"[2]數(shù)組第一個元素的地址挎塌;那么再把p=a;層層推遞,(p+i)表示指向第幾行的地址内边,(p+i)表示取第幾行的值(而這個值存放的是第幾行一列元素的首地址)榴都,(p+i)+j 表示指向第幾行第幾列的地址,最后在引用這個地址假残,(p+i)+j)就表示第幾行第幾列的值了缭贡。

一重指針----------->一維數(shù)組

二重指針----------->指針數(shù)組

數(shù)組指針----------->二維數(shù)組

函數(shù)指針----------->普通函數(shù)

五、數(shù)組&字符串&結構體&共用體&枚舉

5.1辉懒、c語言中定義一個字符串: char a[6]={'l','i','n','u','x','\0'}; '\0'的字符編碼為0就是NULL;也就是說內(nèi)存中遇到0阳惹,翻譯成字符是就是'\0',或這是NULL;

char a[6]="linux";
char *p="linux";

5.2、 sizeof(a)=6是運算符眶俩,其意思是所占空間大小莹汤,包括字符串后面的‘\0',strlen(a)=5是一個函數(shù),其意思是字符串的長度颠印。strlen( p)纲岭;其中p只有是字符指針變量才有意義,它的形參是數(shù)組變量是沒有意義的线罕,因為strlen是根據(jù)什么時候遇到 '\0',才結束測試字符串的長度止潮,就算數(shù)組不初始化也是有長度的。

char *p="linux"; sizeof(p)永遠等于4钞楼,因為p是指針變量喇闸,存的是地址。所以總結:sizeof()是拿來測數(shù)組的大小询件,strlen()是拿來測試字符串的長度燃乍。

5.3、結構體用 . 或者是 ->訪問內(nèi)部變量宛琅,其實質(zhì)是用的指針訪問刻蟹。如

struct student{
int a;
double b;
char c;
}s1;

則s1.a =12;實質(zhì)就是int *p=(int ) &s1;p=12 首先a是int 型嘿辟,所以是強制類型 int * 舆瘪,其次是就是算地址,然后強制類型红伦,地址應該是int 型然后加減介陶,不然的話,系統(tǒng)s1.b=12.2;實質(zhì)就是double *p= (double ) ((int)&s1+4),p=12.2; 不知道是以int 型加減還是以float型加減色建,還是以char型加減,所以 應當 (int)&s1; 而且因為地址是s1.c=c;實質(zhì)就是 char *p=(char *) ((int)&s1+12); *p=c; 4字節(jié)的舌缤,所以必須是int型箕戳。

&* 5.4某残、對齊方式:

(1)猜測如果是32位系統(tǒng),那么編譯器默認是4字節(jié)對齊陵吸,64位系統(tǒng)玻墅,那么編譯器默認是8字節(jié)對齊,因為32位或64位一次性訪問效率是最高的壮虫。

(2)

<1>結構體首地址對齊(編譯器自身幫我們保證澳厢,會給它分配一個對齊的地址,因為結構體自身已經(jīng)對齊了囚似,那么第一個變量也就自然對齊剩拢,所以我們才可以想象成第一個變量從0地址存放);

<2>結構體內(nèi)部的各個變量要對齊饶唤。

<3>整個結構體要對齊徐伐,因為定義結構體變量s1時,再定義變量s2時募狂,如果s1沒有對齊办素,就坑了s2,所以也要保證整個結構體對齊。

無論是按照幾字節(jié)對齊祸穷,我們都可以聯(lián)想到內(nèi)存實際的安排性穿。1字節(jié)對齊那么不管int float double 類型,在每4個格子的內(nèi)存挨著存放雷滚。2字節(jié)對齊需曾,也是一樣的想法,舉一個列子揭措,如果第一個變量是char 型胯舷,第二個變量是int型,那么0地址存放char型绊含,1地址空著桑嘶,2地址存放int型地址部分,3地址存放int型地址部分躬充,然后上排最右4逃顶、5地址存放int型高址部分。4字節(jié)對齊充甚,如果第一個變量是char型以政,第二個變量是int型,那么0地址存放char型伴找,1盈蛮,2,3地址空著技矮,從4地址開始存放int抖誉,最后給變量分配完內(nèi)存空間后殊轴,必須要保證整個結構體對齊,下一個結構體的存放起始地址是n字節(jié)對齊整數(shù)倍袒炉,如是4字節(jié)對齊旁理,那么最后short算成4字節(jié) 以保證整個結構體對齊。

整個結構體對齊我磁,如2字節(jié)對齊(2的整數(shù)倍)孽文,只要是0、2夺艰、4地址就行了芋哭,如果是4字節(jié)對齊(4的整數(shù)倍),就必須是0劲适、4地址楷掉。8字節(jié)對齊(8的整數(shù)倍)

(3)猜測4字節(jié)/8字節(jié)其實是針對int型/double型的,比如0地址是char型霞势,那么4字節(jié)對齊烹植,int型、float型就必須從4地址開始存放愕贡,那么8字節(jié)對齊草雕,int型就必須從4地址存放,double型就必須從8地址開始存放.小于幾字節(jié)對齊的那些固以,如char型和short型只要能按照規(guī)則存放就行了墩虹。

(4)對齊命令:<1>需要#prgama pack(n)開頭,以#pragma pack()結尾憨琳,定義一個區(qū)間诫钓,這個區(qū)間內(nèi)的對齊參數(shù)就是n。(不建議使用)

如:s1占5個字節(jié)篙螟,s2占8字節(jié)(默認)

pragma pack(1)

struct stu1
{

(結構體本身以及變量) 對齊規(guī)則:2字節(jié)對齊(2的整數(shù)倍)菌湃,只要是0、2遍略、4地址就行了惧所,4字節(jié)對齊(4的整數(shù)倍),就必須是0绪杏、4地址下愈,
8字節(jié)對齊(8的整數(shù)倍),就必須是0蕾久、8势似、16

char c;
int a;
//short d;
}s1;

struct stu2
{

char c;
int a;
//short d;
}s2;

<2>gcc推薦的對齊指令attribute((packed)) attribute((aligned(n))),在VC中就不行,沒有定義這個命令

(1)attribute((packed))使用時直接放在要進行內(nèi)存對齊的類型定義的后面履因,然后它起作用的范圍只有加了這個東西的這一個類型辖佣。packed的作用就是取消對齊訪問。

(2)attribute((aligned(n)))使用時直接放在要進行內(nèi)存對齊的類型定義的后面搓逾,然后它起作用的范圍只有加了這個東西的這一個類型。它的作用是讓整個結構體變量整體進行n字節(jié)對齊(注意是結構體變量整體n字節(jié)對齊杯拐,而不是結構體內(nèi)各元素也要n字節(jié)對齊霞篡,內(nèi)部元素按照默認對齊方式)

例子:

struct mystruct11
{// 1字節(jié)對齊4字節(jié)對齊
int a;// 44
char b;// 12(1+1)
short c;// 22
}__attribute__((packed));
typedef struct mystruct111
{// 1字節(jié)對齊4字節(jié)對齊2字節(jié)對齊
int a;// 44 4
char b;// 12(1+1)2
short c;// 22 2
short d;// 2 4(2+2)2
}__attribute__((aligned(1024))) My111;

5.5、offsetof宏:#define offsetof( TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER)

(1)offsetof宏的作用是:用宏來計算結構體中某個元素和結構體首地址的偏移量(其實質(zhì)是通過編譯器來幫我們計算)端逼。

(2)offsetof宏的原理:我們虛擬一個type類型結構體變量朗兵,然后用type.member的方式來訪問那個member元素,繼而得到member相對于整個變量首地址的偏移量顶滩。

(3)學習思路:第一步先學會用offsetof宏余掖,第二步再去理解這個宏的實現(xiàn)原理。

(TYPE *)0 這是一個強制類型轉(zhuǎn)換礁鲁,把0地址強制類型轉(zhuǎn)換成一個指針盐欺,這個指針指向一個TYPE類型的結構體變量。 (實際上這個結構體變量可能不存在仅醇,但是只要我不去解引用這個指針就不會出錯)冗美。

((TYPE *)0)->MEMBER(TYPE *)0是一個TYPE類型結構體變量的指針,通過指針指針來訪問這個結構體變量的member元素析二,然后對這個元素取地址粉洼,又因為改地址是從0地址開始算的,所以這個地址就是相對起始地址的偏移量叶摄。

5.6 container_of宏: #define container_of(ptr, type, member) ({
const typeof(((type *)0)->member) * __mptr = (ptr);
(type *)((char *)__mptr - offsetof(type, member)); }) 兩條語句属韧;,然后用{ } 蛤吓,\表示提示編譯器本行因為屏幕不夠宵喂,鏈接下一行。用#(也就是宏定義)時柱衔,如果本行不夠要用 \ 提示編譯器接著是下一行的樊破。必須要用 \ ,猜測因為宏定義一行就算結束了唆铐。

(1)作用:知道一個結構體中某個元素的指針哲戚,反推這個結構體變量的指針。有了container_of宏艾岂,我們可以從一個元素的指針得到整個結構體變量的指針顺少,繼而得到結構體中其他元素的指針。

(2)typeof關鍵字的作用是:typepef(a)時由變量a得到a的類型,typeof就是由變量名得到變量數(shù)據(jù)類型的脆炎。

(3)這個宏的工作原理:先用typeof得到member元素的類型定義成一個指針梅猿,然后用這個指針減去該元素相對于整個結構體變量的偏移量(偏移量用offsetof宏得到的),減去之后得到的就是整個結構體變量的首地址了秒裕,

再把這個地址強制類型轉(zhuǎn)換為type *即可袱蚓。

5.7 p是一個地址,(int)p+6 和(char *)+6几蜻;效果是一樣的喇潘,第一種是將地址p當作int型加減,第二種是將地址p做為char *指針梭稚,他每次加減都是一字節(jié)一字節(jié)相加減的颖低,如果是 (int )P+6,那么他每次加減都是按照4字節(jié)一跳。就相當于加了+46弧烤;

5.8 小端模式:變量的高地址存放在高地址忱屑,低地址存放在低地址函卒; 通信模式也要分大小端奖年,先發(fā)送/接受的是高地址還是低地址瘤泪,大端模式:變量的高地址存放在低地址锁蠕,低地址存放在高地址绘沉;

測試:有用共用體 union 和指針方式來測試增蹭,基本思路是讓 int a=1; 看低地址 char 是0還是1 疚颊;變量都是從地址開始存放霜瘪,只是變量的高地址和低地址先存放誰不確定幔崖。

不能用位與來測食店,因為存放和讀取都是按照某一個方式來的,結果永遠都是一樣的赏寇。int a=1; char b=(char)a;這種方式不可以測試吉嫩,因為不管大小端,它都以變量a的低地址部分賦給b;

union stu{
int a; int ce( )
{
int a=1;
int b=*((char *)&a);
return b;
}
char b;
}
int ce( )
{
union stu s;
s.a=1;
return s.b;
}

5.9嗅定、枚舉類型(int型): 這樣寫默認從第一個常量開始是0自娩,1,2渠退,3忙迁,4.........也可以自己賦值,但是每一個值是不一樣的,否則邏輯上出錯碎乃。

enum week{
sunday, sunday=1,
moday, moday=5,
tuseday, 然后其他常量以此遞增姊扔。
wenzeday,
friday,
saterday,
}today; today=sunday;
* // 錯誤1,枚舉類型重名梅誓,編譯時報錯:error: conflicting types for ‘DAY’
typedef enum workday
{
MON, // MON = 1;
TUE,
WEN,
THU,
FRI,
}DAY;

typedef enum weekend
{
SAT,
SUN,
}DAY;
*/
/ /錯誤2恰梢,枚舉成員重名佛南,編譯時報錯:redeclaration of enumerator ‘MON’
typedef enum workday
{
MON, // MON = 1;
TUE,
WEN,
THU,
FRI,
}workday;

typedef enum weekend
{
MON,
SAT,
SUN,
}weekend;
}

六、C語言宏定義與預處理嵌言、函數(shù)和函數(shù)庫(看博客strcyp原函數(shù))

6.1嗅回、源碼.c->(預處理)->預處理過的 .i 文件->(編譯)->匯編文件.S->(匯編)->目標文件.o->(鏈接)->elf可執(zhí)行程序預處理用預處理器,編譯用編譯器摧茴,匯編用匯編器绵载,鏈接用鏈接器,這幾個工具再加上其他一些額外的會用到的可用工具苛白,合起來叫編譯工具鏈尘分。gcc就是一個編譯工具鏈。

<1>預處理的意義(1)編譯器本身的主要目的是編譯源代碼丸氛,將C的源代碼轉(zhuǎn)化成.S的匯編代碼。編譯器聚焦核心功能后著摔,就剝離出了一些非核心的功能到預處理器去了缓窜。

(1)預處理器幫編譯器做一些編譯前的雜事。如:(1)#include(#include <>和#include ""的區(qū)別)

(2)注釋

(3)#if #elif #endif#ifdef

(4)宏定義

備注: gcc中只預處理不編譯的方法 -o生成可執(zhí)行文件名 -c只編譯不鏈接 -E 只預處理不編譯 -I ( 是大i,不是L )編譯時從某個路徑下尋找頭文件 . /當前

(1)gcc編譯時可以給一些參數(shù)來做一些設置谍咆,譬如gcc xx.c -o xx可以指定可執(zhí)行程序的名稱禾锤;譬如gcc xx.c -c -o xx.o可以指定只編譯不連接,也可以生成.o的目標文件摹察。

(2)gcc -E xx.c -o xx.i可以實現(xiàn)只預處理不編譯恩掷。一般情況下沒必要只預處理不編譯,但有時候這種技巧可以用來幫助我們研究預處理過程供嚎,幫助debug程序黄娘。

(3)鏈接器:鏈接的時候是把目標文件(二進制)通過有序的排列組合起來,如 star.s main.c led.c 這三個源文件克滴,分別被編譯成三個目標文件 逼争,每個目標文件有很多函數(shù)集合。鏈接的時候會根據(jù)運行思路把這些雜亂的函數(shù)給排列組合起來劝赔,不是把目標文件簡單的排列組合誓焦。

(4)當生成可執(zhí)行程序之后,這個可執(zhí)行程序里面有很多符號信息着帽,有個符號表杂伟,里面的符號與一個地址相對應,如 函數(shù)名max對應一個地址仍翰,雖然這個程序有符號信息赫粥,但是為什么還是可以執(zhí)行呢?因為如windows的exe程序歉备,有專門的一套程序來執(zhí)行這個.exe 文件傅是,就好比壓縮文件,就有一套 “好壓”的軟件,然后去壓縮(執(zhí)行).rar .zip的文件喧笔,而這套程序就把這些符號信息給過濾掉帽驯,然后得到純凈的二進制代碼,最后把他們加載到內(nèi)存中去书闸。

(5) debug版本就是有符號信息尼变,而Release版本就是純凈版本的〗ⅲ可用strip工具: strip是把可執(zhí)行程序中的符號信息給拿掉嫌术,以節(jié)省空間。(Debug版本和Release版本)objcopy:由可執(zhí)行程序生成可燒錄的鏡像bin文件牌借。

6.2度气、預處理:

<1>頭文件有”“是本目錄去找,找不到就去庫頭文件找膨报,和< > 只到庫頭文件去找磷籍,庫頭文件可以自己制作,用 -I ( 是大i,不是L )參數(shù)去尋找路徑现柠。
頭文件在預處理時院领,會把文件的內(nèi)容原封不動的賦值到 c 文件里面。

<2>注釋:在預處理時够吩,把注釋全部拿掉比然。 注意:#define zf 1 再判斷 #undef zf 2 時,也是通過的周循。其意思是有沒有定義過zf.

<3>條件編譯:當作一個配置開關 #define NUM 表示定義了NUM,則執(zhí)行下一條語句强法,且NUM用空格替代,而且預處理會刪掉條件編譯湾笛,留下正確的執(zhí)行語句拟烫。

<4>宏定義:#define cdw 1 在預處理階段,會替代那些宏迄本,可以多重替代宏硕淑;也可以表示多個語句,如 #define cdw printf("cdw\n") ; printf("zf\n"); cdw嘉赎;這條語句會直接展開
還有帶參宏置媳,#define max(a,b) ((a)+(b)) 注意的是帶參宏一定要( ) 不然有時候會引起錯誤,每一個”形參“都應該要()公条;

define year (36524606060*60 ) 安理說是可以的拇囊,但是year是int型的已經(jīng)超過了范圍,所以要把它搞成無符號長整形靶橱。

define year (36524606060*60ul ) 這樣才是正確的

宏定義的變量是不占內(nèi)存空間的寥袭,直接替換減少開銷路捧,但是變量替換是不進行類型檢查;
函數(shù)的變量要占用空間传黄、要壓棧等操作杰扫,就需要很大的開銷,但是調(diào)用函數(shù)時膘掰,編譯器會檢查函數(shù)變量的類型是否相同章姓。
內(nèi)聯(lián)函數(shù)集合普通函數(shù)、宏定義的兩個優(yōu)勢识埋,它直接就地展開凡伊,直接替換,減少開銷窒舟,同時編譯器也會檢查變量的類型系忙。但是函數(shù)體積要小,不然效率反而很低惠豺,至于
原因暫時不詳笨觅。

6.3、內(nèi)聯(lián)函數(shù):對函數(shù)就地展開耕腾,像宏定義一樣,這樣減少開銷杀糯,同時也檢查變量的類型扫俺。但是必須函數(shù)的內(nèi)部體積小才用這種方式,以達到更好的效率固翰。體積大的函數(shù)就作為普通函數(shù)狼纬。
內(nèi)聯(lián)函數(shù)通過在函數(shù)定義前加inline關鍵字實現(xiàn)。

  • 6.4骂际、條件編譯的應用:做一個調(diào)試開關疗琉。#define DEBUG #undef DEBUG 是注銷 DEBUG 宏

ifdef DEBUG

define debug(x) printf(x)

else

define debug(x)

endif

6.5、函數(shù):

(1)整個程序分成多個源文件歉铝,一個文件分成多個函數(shù)盈简,一個函數(shù)分成多個語句,這就是整個程序的組織形式太示。這樣組織的好處在于:分化問題柠贤、便于編寫程序、便于分工类缤。

(2)函數(shù)的出現(xiàn)是人(程序員和架構師)的需要臼勉,而不是機器(編譯器、CPU)的需要餐弱。

(3)函數(shù)的目的就是實現(xiàn)模塊化編程宴霸。說白了就是為了提供程序的可移植性囱晴。

<1>函數(shù)書寫的一般原則:

第一:遵循一定格式。函數(shù)的返回類型瓢谢、函數(shù)名(男女廁所)畸写、參數(shù)列表(太多用結構體)等。

第二:一個函數(shù)只做一件事:函數(shù)不能太長也不宜太短(一個屏幕的大卸魑拧)艺糜,原則是一個函數(shù)只做一件事情。

第三:傳參不宜過多:在ARM體系下幢尚,傳參不宜超過4個破停。如果傳參確實需要多則考構體打包考慮。

第四:盡量少碰全局變量:函數(shù)最好用傳參返回值來和外部交換數(shù)據(jù)尉剩,不要用全局變量真慢。

<2> 之所以函數(shù)能被調(diào)用,根本實質(zhì)是在編譯時理茎,檢查到了該函數(shù)的聲明黑界,不是因為函數(shù)定義了(當然也要定義才行,只是不是本質(zhì))皂林。

6.6朗鸠、遞歸函數(shù):自己調(diào)用自己的函數(shù),常用舉例:階乘 int jiecheng( int n) 斐波那契數(shù)例: f(n)=f(n-1)+f(n-2) n>2的正整數(shù)

{ int he(int n)

注意: if(n<1) if(3==n||4==n)

棧溢出:遞歸函數(shù)會不停的耗費棿”叮空間 { {
所以要注意遞歸不要太多 printf("error\n"); return 1;
收斂性:必須 要有一個終止遞歸的條件 } }

else if(n>1) else if(n>4)
{ {
return n*jiecheng(n-1); return he(n-1) +he(n-2)
} }
else
{
return 1;
}

6.7烛占、函數(shù)庫:<1>靜態(tài)鏈接庫其實就是商業(yè)公司將自己的函數(shù)庫源代碼經(jīng)過只編譯不連接形成.o的目標文件,然后用ar工具將.o文件歸檔成.a的歸檔文件(.a的歸檔文件又叫靜態(tài)鏈接庫文件)沟启。

商業(yè)公司通過發(fā)布.a庫文件和.h頭文件來提供靜態(tài)庫給客戶使用忆家;客戶拿到.a和.h文件后,通過.h頭文件得知庫中的庫函數(shù)的原型德迹,然后在自己的.c文件中直接調(diào)用這些庫文件芽卿,在連接的時候鏈接器會去.a文件中拿出被調(diào)用的那個函數(shù)的編譯后的.o二進制代碼段鏈接進去形成最終的可執(zhí)行程序。

<2>動態(tài)鏈接庫本身不將庫函數(shù)的代碼段鏈接入可執(zhí)行程序胳搞,只是做個
標記卸例。然后當應用程序在內(nèi)存中執(zhí)行時,運行時環(huán)境發(fā)現(xiàn)它調(diào)用了一個動態(tài)庫中的庫函數(shù)時肌毅,會去加載這個動態(tài)庫到內(nèi)存中币厕,然后以后不管有多少個應用程序去調(diào)用這個庫中的函數(shù)都會跳轉(zhuǎn)到第一次加載的地方去執(zhí)行(不會重復加載)。

也就是在運行時芽腾,會把庫函數(shù)代碼放入內(nèi)存中旦装,然后多個程序要用到庫函數(shù)時,就從這段內(nèi)存去找摊滔,而靜態(tài)鏈接對于多程序就是重復使用庫函數(shù)阴绢,比較占內(nèi)存店乐。

(1) gcc中編譯鏈接程序默認是使用動態(tài)庫的,要想靜態(tài)鏈接需要顯式用-static來強制靜態(tài)鏈接呻袭。

(2) 庫函數(shù)的使用需要注意3點:第一眨八,包含相應的頭文件;第二左电,調(diào)用庫函數(shù)時注意函數(shù)原型廉侧;第三,有些庫函數(shù)鏈接時需要額外用-lxxx來指定鏈接篓足;第四段誊,如果是動態(tài)庫,要注意-L指定動態(tài)庫的地址栈拖。

6.8连舍、常見的兩個庫函數(shù):<1>C庫中字符串處理函數(shù)包含在string.h中,這個文件在ubuntu系統(tǒng)中在/usr/include中字符串函數(shù) 如:memcpy(內(nèi)存字符串復制涩哟,直接復制到目標空間)確定src和dst不會overlap重復索赏,則使用memcpy效率高memmove(內(nèi)存字符串復制,先復制到一個內(nèi)存空間贴彼,然后再復制到目標空間)確定會overlap或者不確定但是有可能overlap潜腻,則使用memove比較保險
memset strncmp
memcmp strdup
???? memchr strndup
strcpy strchr
strncpy strstr
strcat strtok
strncat 。器仗。融涣。
strcmp

<2> 數(shù)學函數(shù):math.h 需要加 -lm 就是告訴鏈接器到libm中去查找用到的函數(shù)。

C鏈接器的工作特點:因為庫函數(shù)有很多青灼,鏈接器去庫函數(shù)目錄搜索的時間比較久。為了提升速度想了一個折中的方案:鏈接器只是默認的尋找?guī)讉€最常用的庫妓盲,如果是一些不常用的庫中的函數(shù)被調(diào)用杂拨,需要程序員在鏈接時明確給出要擴展查找的庫的名字。

鏈接時可以用-lxxx來指示鏈接器去到libxxx.so中去查找這個函數(shù)悯衬。

6.9弹沽、自制靜態(tài)鏈接庫:

(1)第一步:自己制作靜態(tài)鏈接庫,首先使用gcc -c只編譯不連接,生成.o文件筋粗;然后使用ar工具進行打包成.a歸檔文件庫名不能隨便亂起策橘,一般是lib+庫名稱,后綴名是.a表示是一個歸檔文件
注意:制作出來了靜態(tài)庫之后娜亿,發(fā)布時需要發(fā)布.a文件和.h文件丽已。

(2)第二步:使用靜態(tài)鏈接庫,把.a和.h都放在我引用的文件夾下买决,然后在.c文件中包含庫的.h沛婴,然后直接使用庫函數(shù)吼畏。

備注:

<1>.a 文件,前綴一定要加lib ,如 libzf.a ; 鏈接屬性 -l(小L),表示庫名嘁灯,屬性-L表示庫的路徑泻蚊。所以:gcc cdw.c -o cdw -lzf -L ./include -I(大i) ./include

<2> 頭文件“ ”表示外部自定義,如果沒加路徑屬性丑婿,默認當前路徑找性雄,如果在其他文件夾下,必須用 -I(大i) 路徑羹奉。用<>表示的頭文件一種是在編譯器下的庫文件找秒旋,第二種是自己定義的庫文件找,但是要定義其路徑尘奏。

<3> 在makefile文件中用到gcc/arm-gcc 那么在shell中就用相應的編譯器 gcc/arm-gcc .

<4> nm ./include/libmax.a 查看max庫的信息滩褥,有哪些 .o 文件 .o文件有哪些函數(shù)。

舉例:makefile: arm-gcc aston.c -o aston.o -c
arm-ar -rc libaston.a aston.o

6.9.1炫加、自制動態(tài)鏈接庫:

<1>動態(tài)鏈接庫的后綴名是.so(對應windows系統(tǒng)中的dll)瑰煎,靜態(tài)庫的擴展名是.a .

<2>第一步:創(chuàng)建一個動態(tài)鏈接庫。 gcc aston.c -o aston.o -c -fPIC (-fPIC表示設置位置無關碼)
gcc -o libaston.so aston.o -shared (-shared表示使用共享庫)
注意:做庫的人給用庫的人發(fā)布庫時俗孝,發(fā)布libxxx.so和xxx.h即可酒甸。

第二步:使用自己創(chuàng)建的共享庫。gcc cdw.c -o cdw -lmax.so -L ./動態(tài)鏈接庫 -I ./動態(tài)鏈接庫

第三步:上述編譯成功了赋铝,但是在 ./cdw 執(zhí)行時會報錯插勤,原因是采用動態(tài)鏈接,在可執(zhí)行文件只是做了一個標記革骨,標記是使用了哪個函數(shù)庫的哪個函數(shù)农尖。

并沒有將庫函數(shù)加載到源文件中,所以可執(zhí)行文件很小良哲,在執(zhí)行時盛卡,需要立即從系統(tǒng)里面找到使用到的函數(shù)庫,然后加載到內(nèi)存中筑凫,在linux系統(tǒng)中
默認是從 /usr/bin 中尋找滑沧,(不確定:如果使用shell中運行)會先執(zhí)行環(huán)境變量的路徑然后再查找 /usr/bin;所以我們可以用兩種辦法解決運行的問題

第四步:將動態(tài)庫 libmax.so 復制到 /usr/lib 下面,但是如果以后所有的庫都這樣放的話巍实,會越來越臃腫滓技,導致運行速度變慢(一個一個查找);或者是新添加一個環(huán)境變量

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/hgfs/share/include (將庫 libmax.so 復制到這個路徑下面)這樣就可以運行了棚潦。

<3>使用 ldd 命令判斷一個可執(zhí)行文件是否能運行成功令漂; ldd cdw
linux-gate.so.1 => (0xb77a8000)
libmax.so => not found //發(fā)現(xiàn) not found意思就是沒有找到對應的函數(shù)庫
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75e2000)
/lib/ld-linux.so.2 (0xb77a9000)

七、存儲類&作用域&生命周期&鏈接屬性

7.1、概念詞:存儲類(棧洗显、堆外潜、數(shù)據(jù)區(qū)、.bss段挠唆、.text段)
作用域(代碼塊作用范圍处窥,也就是變量作用的范圍)
生命周期(變量的誕生和死亡)
鏈接屬性(外鏈接屬性、內(nèi)鏈接屬性玄组、無連接屬性)

7.2滔驾、Linux下的內(nèi)存映射(分配情況、組織情況):見圖內(nèi)存映射俄讹。其中有關進程的空間哆致,如進程控制塊、頁表等都是在內(nèi)核里面的患膛。文件區(qū)是映射外部文件的摊阀,如打開記事本,那么這個文件臨時存放在文件區(qū)域踪蹬。(見引用資料)

問題:虛擬地址技術胞此? 解決:后期在Linux應用/網(wǎng)絡編程會講。

OS下和裸機下C程序加載執(zhí)行的差異跃捣? 解決:在arm裸機第十六部分有介紹漱牵。

7.3、存儲類關鍵字:

<1> auto 自動的(一個用法:修飾局部變量疚漆,在定義變量時可以省略) 【外鏈接:與第二個c文件鏈接】【內(nèi)鏈接:只與本c文件鏈接】【無連接:就是無鏈接】

<2> static 靜態(tài)的(有兩個用法酣胀,第一個是修飾局部變量,意思是當作全局變量娶聘,存放在數(shù)據(jù)區(qū)闻镶,作用域只是定義的那個函數(shù)范圍,生命周期和整個程序一樣丸升,屬于無連接

第二個是修改全局變量/函數(shù)铆农,意思是這個全局變量/函數(shù)只在當前c文件有效,其他c文件是不能使用它的发钝,屬于內(nèi)鏈接顿涣,普通全局變量屬于外連接)
<3>register 寄存器(一個用法波闹,修飾變量酝豪,作用是讓編譯器把這個變量放在寄存器中,當這個變量頻繁的被使用時精堕,用這個方法可以提高效率孵淘,但有時候不一定就放在寄存器,因為寄存器是有限的歹篓,沒有剩余的寄存器了)

<4>extern (一個用法瘫证,修飾全局變量揉阎,表示該文件要使用的這個變量,在另外一個c文件中已經(jīng)定義了背捌,其一個聲明的作用毙籽,不能初始化)

<5>volatile (一個用法,修飾變量毡庆,表示對這個變量的語句不要去優(yōu)化)

(1) volatile的字面意思:可變的坑赡、易變的。C語言中volatile用來修飾一個變量么抗,表示這個變量可以被編譯器之外的東西改變毅否。編譯器之內(nèi)的意思是變量的值的改變是代碼的作用,編譯器之外的改變就是這個改變不是代碼造成的蝇刀,或者不是當前代碼造成的螟加,編譯器在編譯當前代碼時無法預知。譬如在中斷處理程序isr中更改了這個變量的值吞琐,譬如多線程中在別的線程更改了這個變量的值捆探,譬如硬件自動更改了這個變量的值(一般這個變量是存在寄存器,或許當其他進程要用到這個寄存器時顽分,就把這個寄存器的變量給改變了徐许,同時也就改變了這個變量)

(2) 以上說的三種情況(中斷isr中引用的變量,多線程中共用的變量卒蘸,硬件會更改的變量)都是編譯器在編譯時無法預知的更改雌隅,此時應用使用volatile告訴編譯器這個變量屬于這種(可變的、易變的)情況缸沃。編譯器在遇到volatile修飾的變量時就不會對改變量的訪問進行優(yōu)化恰起,就不會出現(xiàn)錯誤。

(3) 編譯器的優(yōu)化在一般情況下非常好趾牧,可以幫助提升程序效率检盼。但是在特殊情況(volatile)下,變量會被編譯器想象之外的力量所改變翘单,此時如果編譯器沒有意識到而去優(yōu)化則就會造成優(yōu)化錯誤吨枉,優(yōu)化錯誤就會帶來執(zhí)行時錯誤。

而且這種錯誤很難被發(fā)現(xiàn)哄芜。

(4) volatile是程序員意識到需要volatile然后在定義變量時加上volatile稚叹,如果你遇到了應該加volatile的情況而沒有加程序可能會被錯誤的優(yōu)化族阅。如果在不應該加volatile而加了的情況程序不會出錯只是會降低效率隐砸。

所以我們對于volatile的態(tài)度應該是:正確區(qū)分吓懈,該加的時候加不該加的時候不加,如果不能確定該不該加為了保險起見就加上。

舉例子1: int a=3 剧腻,b,c拘央;
b=a;
c=b;

那么編譯器會優(yōu)化成 c=b=a=3; 如果此時遇到上述三種情況,突然改變了a的值书在,那么灰伟,對于沒有優(yōu)化前是對的,但是對于優(yōu)化后儒旬,那么c仍然是3袱箱,就會出錯。

所以當我們程序員知道這個變量會發(fā)生改變時义矛,就應該加 volatile发笔,提示編譯器不要幫我們做優(yōu)化。

舉列子2:

int square(volatile int *ptr)
   {
   return *ptr * *ptr;
   }

這段代碼的有個惡作劇凉翻。這段代碼的目的是用來返指針ptr指向值的平方了讨,但是,由于ptr指向一個volatile型參數(shù)制轰,編譯器將產(chǎn)生類似下面的代碼:

int square(volatile int *ptr)
  {
   int a,b;
   a = *ptr;
   b = *ptr;
   return a * b;
   }

由于*ptr的值可能被意想不到地該變前计,因此a和b可能是不同的。結果垃杖,這段代碼可能返不是你所期望的平方值男杈!正確的代碼如下:

long square(volatile int *ptr)
   {
    int a;
   a = *ptr;
   return a * a;
   }

<6> restrict (1)c99中才支持的,所以很多延續(xù)c89的編譯器是不支持restrict關鍵字调俘,gcc支持的伶棒。

(2)restrict 作用是讓編譯器對這個變量做一些優(yōu)化,讓它提高效率彩库。下面的網(wǎng)站有列子肤无。

(3)restrict只用來修飾指針,不能修飾普通變量,它表示只能該指針才能修改它的內(nèi)容骇钦。如用memcpy時宛渐,兩個內(nèi)存存在重疊的現(xiàn)象。

(4)https://blog.chinaunix.net/uid-22197900-id-359209.html (這個網(wǎng)站里面有詳細的例子)

(5)memcpy和memmove的區(qū)別 void *memcpy( void * restrict dest ,const void * restrict src,sizi_t n)眯搭;這樣它可以優(yōu)化成memmove原理的方式(當存在重疊時窥翩,先復制到一段內(nèi)存空間,然后再把它復制到目的空間)

7.4鳞仙、作用域:

(1)全局變量起名字一般是 g_a;

(2)名字加前綴表示

7.5寇蚊、總結:<1>局部變量地址由運行時在棧上分配得到,多次執(zhí)行時地址不一定相同繁扎,函數(shù)不能返回該類變量的地址(指針)作為返回值幔荒。

<2>為什么要分為數(shù)據(jù)段和.bbs段?因為當加載到內(nèi)存重定位時梳玫,如果這些數(shù)據(jù)(包括0)一個一個的復制爹梁,會降低效率,為0的變量提澎,直接清內(nèi)存就可以了,這樣提高效率积糯。

<3>在頭文件聲明全局變量時谦纱, extern int a; 聲明函數(shù)就是 void int max(int a, int b);

<4>寫程序盡量避免使用全局變量看成,尤其是非static類型的全局變量川慌。能確定不會被其他文件引用的全局變量一定要static修飾。(因為全局變量占內(nèi)存的時間是最長的祠乃,要看你的變量是不是需要這么長的時間,這樣可以節(jié)約內(nèi)存空)

八琴拧、一些雜散但值得討論的問題

8.1嘱支、操作系統(tǒng)的理解:

<1>它是一個管理階級者除师,管理所有資源,負責調(diào)配優(yōu)化等操作于置。這樣想象八毯,就像裸機一樣的話话速,要實現(xiàn)LED閃爍的進程芯侥、串口傳輸?shù)倪M程、蜂鳴器等這些云石,他們都要搶占一些資源汹忠,這個時候沒有操作系統(tǒng)雹熬,就亂成一鍋粥竿报,當有了OS的時候铅乡,它就專門負責資源的調(diào)配,讓各個任務都能很好的實施隆判,起一個決策者的作用侨嘀。

<2>如果我們要做一個產(chǎn)品咬腕,軟件系統(tǒng)到底應該是裸機還是基于操作系統(tǒng)呢涨共?本質(zhì)上取決于產(chǎn)品本身的復雜度举反。只有極簡單的功能火鼻、使用極簡單的CPU(譬如單片機)的產(chǎn)品才會選擇用裸機開發(fā)魁索;一般的復雜性產(chǎn)品都會選擇基于操作系統(tǒng)來開發(fā)粗蔚。

<3>操作系統(tǒng)負責管理和資源調(diào)配饶火,應用程序負責具體的直接勞動,他們之間的接口就是API函數(shù)抖僵。當應用程序需要使用系統(tǒng)資源(譬如內(nèi)存、譬如CPU寺晌、譬如硬件操作)時就通過API向操作系統(tǒng)發(fā)出申請呻征,然后操作系統(tǒng)響應申請幫助應用程序執(zhí)行功能陆赋。

8.2攒岛、C庫函數(shù)和API的關系:

<1>從內(nèi)核的角度看灾锯,需要考慮提供哪些有用的API函數(shù)顺饮,并不需要關注它們?nèi)绾伪皇褂眉嫘邸9示幊虝r用API函數(shù)會感覺到不好用赦肋,沒有做優(yōu)化金砍。系統(tǒng)只負責做出一個可以用的API恕稠,沒有考慮到用戶使用的方便性鹅巍。所以c庫函數(shù)對API做了一些優(yōu)化骆捧,讓用戶使用庫函數(shù)更容易達到我們想要的目的敛苇。

<2>庫函數(shù)實質(zhì)還是用的API枫攀,或者調(diào)用了一個API,也或者調(diào)用了更多的API,只不過是做了更多的優(yōu)化来涨。比如 庫函數(shù)fopen 蹦掐,而API是open.

<3>有的庫函數(shù)沒有用到API,比如strcpy函數(shù)(復制字符串)和atoi函數(shù)(轉(zhuǎn)換ASCII為整數(shù))卧抗,因為它們并不需要向內(nèi)核請求任何服務社裆。

8.3浦马、不同平臺(windows晶默、linux磺陡、裸機)下庫函數(shù)的差異

(1)不同操作系統(tǒng)API是不同的币他,但是都能完成所有的任務蝴悉,只是完成一個任務所調(diào)用的API不同拍冠。

(2)庫函數(shù)在不同操作系統(tǒng)下也不同,但是相似性要更高一些碟摆。這是人為的典蜕,因為人下意識想要屏蔽不同操作系統(tǒng)的差異愉舔,因此在封裝API成庫函數(shù)的時候屑宠,盡量使用了同一套接口,所以封裝出來的庫函數(shù)挺像的丧叽。

但是還是有差異踊淳,所以在一個操作系統(tǒng)上寫的應用程序不可能直接在另一個操作系統(tǒng)上面編譯運行迂尝。于是乎就有個可移植性出來了垄开。

(3)跨操作系統(tǒng)可移植平臺溉躲,譬如QT锻梳、譬如Java語言。

8.4神汹、

<1>main()函數(shù)的寫法:int main(int argc,char **argv) ; int main(int argc ,char *argv[ ] ); 這兩種寫法是一樣的屁魏。二重指針等同于指針數(shù)組氓拼。

<2>不管是主函數(shù)還是功能函數(shù)桃漾,它都應該有一個返回值撬统,而主函數(shù)的返回值是給調(diào)用的那個人的/main函數(shù)從某種角度來講代表了我當前這個程序恋追,或者說代表了整個程序苦囱。main函數(shù)的開始意味著整個程序開始執(zhí)行鱼鸠,
main函數(shù)的結束返回意味著整個程序的結束蚀狰。誰執(zhí)行了這個程序麻蹋,誰就調(diào)用了main哥蔚。誰執(zhí)行了程序糙箍?或者說程序有哪幾種被調(diào)用執(zhí)行的方法深夯?一個程序當然會運行咕晋,那么就是調(diào)用了main( ).

<3>inux下一個新程序執(zhí)行的本質(zhì)

(1)表面來看滓玖,linux中在命令行中去./xx執(zhí)行一個可執(zhí)行程序

(2)我們還可以通過shell腳本來調(diào)用執(zhí)行一個程序

(3)我們還可以在程序中去調(diào)用執(zhí)行一個程序(fork exec)

總結:我們有多種方法都可以執(zhí)行一個程序势篡,但是本質(zhì)上是相同的禁悠。linux中一個新程序的執(zhí)行本質(zhì)上是一個進程的創(chuàng)建碍侦、加載瓷产、運行蜒什、消亡灾常。linux中執(zhí)行一個程序其實就是創(chuàng)建一個新進程然后把這個程序丟進這個進程中去執(zhí)行直到結束钞瀑。

新進程是被誰開啟雕什?在linux中進程都是被它的父進程fork出來的贷岸。

分析:命令行本身就是一個進程躏救,在命令行底下去./xx執(zhí)行一個程序盒使,其實這個新程序是作為命令行進程的一個字進程去執(zhí)行的少办。

總之一句話:一個程序被它的父進程所調(diào)用枉疼。

結論:main函數(shù)返回給調(diào)用這個函數(shù)的父進程骂维。父進程要這個返回值干嘛航闺?父進程調(diào)用子進程來執(zhí)行一個任務潦刃,然后字進程執(zhí)行完后通過main函數(shù)的返回值返回給父進程一個答復。這個答復一般是表示子進程的任務執(zhí)行結果完成了還是錯誤了胧洒。

(0表示執(zhí)行成功卫漫,負數(shù)表示失敗,正規(guī)的要求返回失敗的原因列赎,返回-1表示什么,返回-2又表示什么诗越,然后父進程好做相應的處理)

(4) main函數(shù)的返回值應當是int 型掺喻。父進程要求是int型的感耙,如果寫成 float 型逃片,則返回就為0褥实,這樣是要出錯的损离。

8.5 用shell腳本來看main()的返回值。如:#!/bin/sh 其文本格式為 .sh

./a.out
echo $?

8.6窟勃、argc秉氧、argv與main函數(shù)的傳參:當我們的父進程不需要傳參時,就用 int main(void);當我們需要傳參時梆暖,就應該是 int main(int argv ,char *argc[ ]);它默認本身就是一個參數(shù),占了argv[0]這個位置弟灼,它里面存的是 ./a.out (這個相應變化)

如: ./a.out boy girl ;則 argv=3; argc[0]="./a.out"; argc[1]="boy"; argc[2]="girl" ; printf("%s\n",argc[0]);

解釋:argv表示傳了多少個參數(shù),argc實質(zhì)是存的一個指針掩驱,也就是一個地址民逼,只是沒有一個被綁定的變量名而已拼苍。這個地址指向一個字符串疮鲫,一般字符串都和指針相關。所以可以稱之為字符串數(shù)組弦叶,每一個都存了一個字符串俊犯。

在程序內(nèi)部如果要使用argc,那么一定要先檢驗argv,先檢驗個數(shù)伤哺,然后使用瘫析。

8.7默责、void類型的本質(zhì):即使空型又是未知類型贬循,看具體情況。比如一個函數(shù)void表示不返回桃序, void *malloc(20);就是未知類型杖虾。

(1)編程語言分2種:強類型語言和弱類型語言。強類型語言中所有的變量都有自己固定的類型媒熊,這個類型有固定的內(nèi)存占用奇适,有固定的解析方法;弱類型語言中沒有類型的概念芦鳍,所有變量全都是一個類型(一般都是字符串的)嚷往,程序在用的時候再根據(jù)需要來處理變量。就如:makefile柠衅、html語言皮仁。

(2)C語言就是典型的強類型語言,C語言中所有的變量都有明確的類型菲宴。因為C語言中的一個變量都要對應內(nèi)存中的一段內(nèi)存贷祈,編譯器需要這個變量的類型來確定這個變量占用內(nèi)存的字節(jié)數(shù)和這一段內(nèi)存的解析方法。

(3)void類型的正確的含義是:不知道類型喝峦,不確定類型势誊,還沒確定類型、未知類型谣蠢,但是將來一定有類型粟耻。

(4)void *a;(編譯器可以通過)定義了一個void類型的變量查近,含義就是說a是一個指針,而且a肯定有確定的類型挤忙,只是目前我還不知道a的類型嗦嗡,還不確定,所以標記為void饭玲。

void “修飾”的是指針侥祭,因為指針就是內(nèi)存地址,它不知道指向的另一個變量是哪一種類型茄厘,而變量一定是確定的矮冬,void a;就是錯誤的次哈。

8.9胎署、C語言中的NULL

NULL在C/C++中的標準定義

(1)NULL不是C語言關鍵字,本質(zhì)上是一個宏定義窑滞,其保護指針的作用琼牧,不要讓他亂開槍。

(2)NULL的標準定義:

ifdef _cplusplus // 條件編譯c++環(huán)境

define NULL 0

else

define NULL (void *)0 // 這里對應C語言的情況

endif

解釋:C++的編譯環(huán)境中哀卫,編譯器預先定義了一個宏_cplusplus巨坊,程序中可以用條件編譯來判斷當前的編譯環(huán)境是C++的還是C的。

NULL的本質(zhì)解析:NULL的本質(zhì)是0此改,但是這個0不是當一個數(shù)字解析趾撵,而是當一個內(nèi)存地址來解析的,這個0其實是0x00000000共啃,代表內(nèi)存的0地址。(void *)0這個整體表達式表示一個指針移剪,這個指針變量本身占4字節(jié)究珊,地址在哪里取決于指針變量本身,但是這個指針變量的值是0纵苛,也就是說這個指針變量指向0地址(實際是0地址開始的一段內(nèi)存)剿涮。如 char *p=NULL; 因為0地址本身就不是我們來訪問的,所以 *p時是不可訪問的赶站。在程序運行的邏輯上就不會出錯幔虏。

正規(guī)寫:

int *p = NULL;// 定義p時立即初始化為NULL
p = xx;
if (NULL != p)
{
*p // 在確認p不等于NULL的情況下才去解引用p
}

(1)'\0'是一個轉(zhuǎn)義字符,他對應的ASCII編碼值是0贝椿,內(nèi)存值是0,一個char空間陷谱。

(2)'0'是一個字符烙博,他對應的ASCII編碼值是48瑟蜈,內(nèi)存值是int型48,一個char空間渣窜。

(3)0是一個數(shù)字铺根,沒有ASCll編碼, 內(nèi)存值是int型0乔宿,一個int空間位迂。

(4)NULL是一個表達式,是強制類型轉(zhuǎn)換為void *類型的0详瑞,內(nèi)存值是0(內(nèi)存地址)掂林,一個int空間。

8.9.1坝橡、運算中的臨時匿名變量

<1>“小動作”:高級語言在運算中允許我們大跨度的運算泻帮。意思就是低級語言中需要好幾步才能完成的一個運算,在高級語言中只要一步即可完成计寇。譬如C語言中一個變量i要加1锣杂,在C中只需要i++即可,看起來只有一句代碼番宁。但實際上翻譯到匯編階段需要3步才能完成:第1步從內(nèi)存中讀取i到寄存器元莫,

第2步對寄存器中的i進行加1,第3步將加1后的i寫回內(nèi)存中的i蝶押。

<2> float a=12.3; int b=(int)a; (int )a 就是匿名變量;先找一個內(nèi)存空間柒竞,里面存(int)a; 然后把這個值賦值給b;最后匿名值銷毀播聪。

float a; int b=10; a=b/3; 左邊是3.00000朽基; 右邊是3;其中有個匿名變量离陶,先找一個內(nèi)存空間稼虎,里面存 b/3; 然后把它再轉(zhuǎn)換成float型,再賦值個a;最后匿名值銷毀招刨。

8.9.2 分析DEBUG宏

學習級:

define DEBUG #undef DEBUG 是注銷 DEBUG 宏

ifdef DEBUG

define debug(x) printf(x)

else

define debug(x)

endif

應用級:

ifdef DEBUG

define DBG(...) fprintf(stderr, " DBG(%s, %s( ), %d): ", FILE, FUNCTION, LINE); fprintf(stderr, VA_ARGS)

else

define DBG(...)

endif

解 釋:<1>...表示變參霎俩,提示編譯器不要對參數(shù)個數(shù)斤斤計較,不要報錯沉眶; 其實完全可以把 ...換成 cdw 也是可以的打却,只是非要裝一下而已。

<2> FILEFUNCTIONLINE 都是c庫函數(shù)的宏定義谎倔,分別表示要輸出的這句話屬于哪個文件名柳击、屬于哪個函數(shù)名、在第幾行片习。

<3> 在 fprintf(stderr,"cdw");其中stderr是c庫函數(shù)中宏定義了的捌肴,這是VC6.0找到的 #define stderr (&_iob[2]) 蹬叭;也就是說stderr是一個被宏定義了的指針,它是標準錯誤輸出流對象(stderr)状知,輸出到屏幕上秽五。

fprintf是C/C++中的一個格式化寫—庫函數(shù),位于頭文件中饥悴,其作用是格式化輸出到一個流/文件中坦喘;(重點是流/文件)

printf()函數(shù)是格式化輸出函數(shù), 一般用于向標準輸出設備按規(guī)定格式輸出(重點是標準輸出設備,有時候輸出的不一定顯示在屏幕上西设,只是編譯器規(guī)定顯示到屏幕上而已瓣铣。)

總結:也就是說printf()其實不是輸出屏幕上的,只是這個標準輸出設備中济榨,編譯器規(guī)定顯示到屏幕上而已坯沪,而真正輸出到屏幕是fprintf(stderr,"cdw");其中stderr就是輸出到屏幕上的流。它也可以 fprintf( FILE *stream, const char *format,...),這個就是輸出到文件流中的擒滑。

比如:一般情況下腐晾,你這兩個語句運行的結果是相同的,沒有區(qū)別丐一,只有一下情況才有區(qū)別:運行你的程序的時候藻糖,命令行上把輸出結果進行的轉(zhuǎn)向,比如使用下面的命令把你的程序a.c運行的結果轉(zhuǎn)向到記事本文件a.txt:a.exe > a.txt

在這樣的情況库车,如果使用printf輸出錯誤信息巨柒,會保存到a.txt文件里面,如果使用fprintf輸出錯誤柠衍,會顯示在屏幕上洋满。

<4>上面中的VA_ARGS也是一個宏定義,表示預處理時實際的參數(shù)珍坊。
如:DBG("tiaoshi.\n");

則允許的效果是 DBG(debug.c, main( ), 14): tiaoshi.

內(nèi)核級:

ifdef DEBUG_S3C_MEM

define DEBUG(fmt, args...)printk(fmt, ##args)

else

define DEBUG(fmt, args...)do {} while (0)

endif

九牺勾、鏈表&狀態(tài)機與多線程(9.9.1?具體鏈表實現(xiàn)留到驅(qū)動模塊講解)

9.1阵漏、鏈表是一個一個的節(jié)點驻民,每一個節(jié)點分為兩部分,一部分是數(shù)據(jù)區(qū)(可以由多個類型的數(shù)據(jù))履怯,另一部分是指向下一個節(jié)點的指針回还;結構體定義里面的變量并沒有生成,是不占空間的叹洲,相當于聲明的作用柠硕。

9.2、鏈表的數(shù)據(jù)存放在內(nèi)存的那個空間呢疹味?(棧仅叫,不靈活帜篇,不能用date數(shù)據(jù)段)所以只能用堆內(nèi)存糙捺,申請一個節(jié)點的大小并檢測NULL, 要使用它诫咱,就得清理它,因為上一個進程用了這段內(nèi)存洪灯,存的是臟數(shù)據(jù)坎缭,
然后對這個節(jié)點內(nèi)存賦值,鏈接起來.

9.3签钩、當要改變頭節(jié)點是掏呼,也就是要給head=p賦值時,必須傳 head地址即 形參(struct student *head)铅檩;這樣才能真正改變憎夷,不然傳一個 (struct student head)只是單純的賦值。

9.4昧旨、在scanf("%d",&(s->age)) 一定要注意拾给,studeny *s; s->age訪問的是一個變量,而不要理解成地址兔沃,所以要加&蒋得,scanf要注意&;

9.5乒疏、細節(jié):<1>在 .h文件中聲明一個函數(shù)要用分號额衙,而且是英文符號.用無頭節(jié)點的方式,需要修改頭指針位置怕吴,所以比較復雜窍侧。

<2> 定義一個node *head=NULL,想要改變head值通過函數(shù)傳參是不行的,因為head是一個地址转绷,傳參過去伟件,只是賦值給另一個指針而已,只能修改它指向的數(shù)據(jù)暇咆,而本身(地址)是不能修改的锋爪,所以要先返回修改好的地址,然后再head=node_add(head)

<3>定義爸业、用指針都應該想到NULL,如 node *head=NULL; node *new=(node *)mallo(sizeof(node));if(NULL!=new){ }

<4>在結構體想定義一個字符串時不要用 char *name; 應該要用char name[10];如果使用第一種的話其骄,編譯通過,執(zhí)行錯誤扯旷,因為為name賦值時就要放在代碼段中拯爽,而代碼段已確定了铺遂,所以報段錯誤蘑斧。

9.6葡粒、頭節(jié)點肃弟、頭指針、第一個節(jié)點:頭節(jié)點是一個節(jié)點桃煎,頭節(jié)點的下一個指向第一個節(jié)點篮幢,頭節(jié)點的數(shù)據(jù)一般存的是鏈表長度等信息,也可以是空为迈,頭指針指向頭節(jié)點三椿。鏈表可以沒有頭節(jié)點,但不能沒有頭指針葫辐。

頭節(jié)點可以想成數(shù)組的0位置搜锰,其余節(jié)點當作從1開始,所以有頭節(jié)點的長度可以定義為就是含有真實數(shù)據(jù)節(jié)點的個數(shù)耿战。

9.7蛋叼、刪除一個節(jié)點應該做的事:如果這個節(jié)點的數(shù)據(jù)不重要,一定要記住free()掉剂陡,你邏輯上刪除狈涮,其實仍然存在內(nèi)存中的,頭節(jié)點的好處就是函數(shù)返回值int可以幫助我們一些信息,而沒有頭節(jié)點有時必須返回head;

9.8鹏倘、單鏈表之逆序:見代碼薯嗤。

9.9、單鏈表的優(yōu)點和缺點:<優(yōu)點>單鏈表是對數(shù)組的一個擴展纤泵,解決了數(shù)組的大小比較死板不容易擴展的問題骆姐。使用堆內(nèi)存來存儲數(shù)據(jù),將數(shù)據(jù)分散到各個節(jié)點之間捏题,其各個節(jié)點在內(nèi)存中可以不相連玻褪,節(jié)點之間通過指針進行單向鏈接。鏈表中的各個節(jié)點內(nèi)存不相連公荧,有利于利用碎片化的內(nèi)存带射。

<缺點>單鏈表各個節(jié)點之間只由一個指針單向鏈接,這樣實現(xiàn)有一些局限性循狰。局限性主要體現(xiàn)在單鏈表只能經(jīng)由指針單向移動(一旦指針移動過某個節(jié)點就無法再回來窟社,如果要再次操作這個節(jié)點除非從頭指針開始再次遍歷一次),因此單鏈表的某些操作就比較麻煩(算法比較有局限)绪钥。

回憶之前單鏈表的所有操作(插入灿里、刪除節(jié)點、 遍歷程腹、從單鏈表中取某個節(jié)點的數(shù)·····)匣吊,因為單鏈表的單向移動性導致了不少麻煩。

總結:單鏈表的單向移動性導致我們在操作單鏈表時,當前節(jié)點只能向后移動不能向前移動色鸳,因此不自由社痛,不利于解決更復雜的算法。

9.9.1命雀、 內(nèi)核鏈表的思想是:<1>先做一個純鏈表蒜哀,沒有數(shù)據(jù)區(qū),只有節(jié)點的鏈接方法咏雌。然后要做一個鏈表出來凡怎,直接用純鏈表然后稍加修改就可以了校焦。

<2>內(nèi)核中的方法不要輕易使用赊抖,是給內(nèi)核用的,否則容易出錯寨典,用戶應該使用沒有的方法氛雪;如:__list_add() ; list_add();

<3>內(nèi)核默認是頭指針+頭節(jié)點的思路。

<4>其實質(zhì)就是操作里面內(nèi)嵌 純鏈表這個變量耸成,再利用controf宏來訪問結構體的數(shù)據(jù)报亩。詳情見驅(qū)動。

9.9.2井氢、狀態(tài)機:

<1>概念:其實就是有多種狀態(tài)切換弦追,如電腦的休眠、關機花竞、睡眠劲件。

<2>類型:(1)Moore型狀態(tài)機特點是:輸出只與當前狀態(tài)有關(與輸入信號無關)。相對簡單约急,考慮狀態(tài)機的下一個狀態(tài)時只需要考慮它的當前狀態(tài)就行了零远。

(2)Mealy型狀態(tài)機的特點是:輸出不只和當前狀態(tài)有關,還與輸入信號有關厌蔽。狀態(tài)機接收到一個輸入信號需要跳轉(zhuǎn)到下一個狀態(tài)時牵辣,狀態(tài)機綜合考慮2個條件(當前狀態(tài)、輸入值)后才決定跳轉(zhuǎn)到哪個狀態(tài)奴饮。

<3>理解:要時時刻刻檢查當前狀態(tài)纬向,用循環(huán)+switch(狀態(tài));然后根據(jù)輸入信號戴卜,進行更多的處理逾条,轉(zhuǎn)換到其他狀態(tài)。

十叉瘩、增補知識

10.1膳帕、一個字節(jié)可以表示8位字符,字符真的有256種,128~255表示西歐字符危彩,是不常見攒磨,詳情見文檔。 字符相加的時候汤徽,會自動轉(zhuǎn)成 int型加娩缰。

10.2、在C中谒府,默認的基礎數(shù)據(jù)類型均為signed拼坎,現(xiàn)在我們以char為例,說明(signed) char與unsigned char之間的區(qū)別完疫。

首先在內(nèi)存中泰鸡,char與unsigned char沒有什么不同,都是一個字節(jié)壳鹤,唯一的區(qū)別是盛龄,char的最高位為符號位,因此char能表示-127~127,unsigned char沒有符號位芳誓,因此能表示0~255余舶,這個好理解,8個bit锹淌,最多256種情況匿值,因此無論如何都能表示256個數(shù)字。

10.3赂摆、為什么在鏈接時需要一個鏈接地址挟憔?因為數(shù)據(jù)是要放在一個模擬地址內(nèi)存空間的,它要把這個數(shù)據(jù)先加載到寄存器库正,才能給cpu使用曲楚,那么寄存器怎么知道是哪個內(nèi)存地址位置呢,是因為在編譯時褥符,編譯出像 ldr r0 0x12345678 ,而這個0x12345678就是內(nèi)存地址龙誊,再編譯出像 ldr r1,[r0] ,這樣就可以拿到0x12345678內(nèi)存位置的數(shù)據(jù)了

10.4、printf 變參喷楣?

10.5趟大、arm-2009q3.tar.bz2 這套編譯器自帶了函數(shù)庫,比如有strcmp , malloc ,printf 等铣焊,但是有些庫函數(shù)我們卻不能用他們逊朽,比如printf,因為這個函數(shù)默認是同過屏幕輸出的,而我們常用uart調(diào)試曲伊。感覺malloc也不能用叽讳,因為我們不知道內(nèi)存哪一塊做了堆內(nèi)存追他,只有系統(tǒng)才知道。

10.6岛蚤、清bss段:編譯器可能已經(jīng)幫我們做了邑狸,只是在重定位那節(jié),因為要重定位那部分內(nèi)存空間并沒有清0 涤妒,所以要手動編程清bss段单雾。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市她紫,隨后出現(xiàn)的幾起案子硅堆,更是在濱河造成了極大的恐慌,老刑警劉巖贿讹,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渐逃,死亡現(xiàn)場離奇詭異,居然都是意外死亡围详,警方通過查閱死者的電腦和手機朴乖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來助赞,“玉大人,你說我怎么就攤上這事袁勺”⑹常” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵期丰,是天一觀的道長群叶。 經(jīng)常有香客問我,道長钝荡,這世上最難降的妖魔是什么街立? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮埠通,結果婚禮上赎离,老公的妹妹穿的比我還像新娘。我一直安慰自己端辱,他們只是感情好梁剔,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著舞蔽,像睡著了一般荣病。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渗柿,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天个盆,我揣著相機與錄音,去河邊找鬼。 笑死颊亮,一個胖子當著我的面吹牛鸡岗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播编兄,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼轩性,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了狠鸳?” 一聲冷哼從身側(cè)響起揣苏,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎件舵,沒想到半個月后卸察,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡铅祸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年坑质,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片临梗。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡涡扼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出盟庞,到底是詐尸還是另有隱情吃沪,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布什猖,位于F島的核電站票彪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏不狮。R本人自食惡果不足惜降铸,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望摇零。 院中可真熱鬧推掸,春花似錦、人聲如沸遂黍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雾家。三九已至铃彰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芯咧,已是汗流浹背牙捉。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工竹揍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人邪铲。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓芬位,卻偏偏與公主長得像,于是被迫代替她去往敵國和親带到。 傳聞我的和親對象是個殘疾皇子昧碉,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容

  • 指針是C語言中廣泛使用的一種數(shù)據(jù)類型。 運用指針編程是C語言最主要的風格之一揽惹。利用指針變量可以表示各種數(shù)據(jù)結構被饿; ...
    朱森閱讀 3,430評論 3 44
  • 1.語言中變量的實質(zhì) 要理解C指針,我認為一定要理解C中“變量”的存儲實質(zhì)搪搏, 所以我就從“變量”這個東西開始講起吧...
    金巴多閱讀 1,745評論 0 9
  • 第十章 指針 1. 地址指針的基本概念: 在計算機中狭握,所有的數(shù)據(jù)都是存放在存儲器中的。一般把存儲器中的一個字節(jié)稱為...
    堅持到底v2閱讀 1,066評論 2 3
  • 在C語言中,五種基本數(shù)據(jù)類型存儲空間長度的排列順序是: A)char B)char=int<=float C)ch...
    夏天再來閱讀 3,339評論 0 2
  • 今天小編給大家?guī)韈語言學習之路--由淺入深(快速掌握c基礎)疯溺。溫馨提示:亮點在最后论颅! 1.第一個C程序:Hell...
    云上傘閱讀 598評論 0 1