Chapter 1- ccp是有指向有const 限定符的char 的指針,cp是指向沒(méi)有限定符修飾的指針;左操作數(shù)具有右操作數(shù)的所有限定符(空),再加上自身的限定符(const);但是反過(guò)來(lái)賦值cp = ccp 就會(huì)違反賦值約束條件,就會(huì)產(chǎn)生編譯告警。
- 而const float * 是指向具有const限定符的float類(lèi)型的指針,也就是說(shuō)const限定符是修飾指針?biāo)赶虻念?lèi)型,而不是指針本身。類(lèi)似的,const char ** 也不是具有限定符的類(lèi)型,它是指向有const 限定符的float類(lèi)型的指針的指針,也就是說(shuō)const 限定符是修飾指針的指針?biāo)赶虻念?lèi)型;char ** 類(lèi)型指向char *,const char **類(lèi)型指向const char * 兩者是不相容的。
- const 并不能把變量變成常量,在一個(gè)符號(hào)前面加上const 限定符只是表示這個(gè)符號(hào)不能被賦值,但也不能防止通過(guò)程序的內(nèi)部方法來(lái)修改這個(gè)值,const 最有用之處就是用它來(lái)限定函數(shù)的形參,這樣該函數(shù)將不會(huì)修改實(shí)參指針?biāo)傅臄?shù)據(jù),但其他的函數(shù)卻可能會(huì)修改它,這是const最一般的用法。
const int * limitp = &limit;
- 這段代碼表示limitp是一個(gè)指向常量整形的指針,也就是說(shuō)const 是修飾limitp指針指向的int類(lèi)型。這個(gè)指針不能用于修改被指向的int類(lèi)型,但是任何時(shí)候limitp本身的值卻可以改變。這樣limitp就指向了不同的地址。const int * a與 int const * a兩者沒(méi)有區(qū)別;int * const a 與前兩者不同,表示指針a的值不能修改,即a指向地址不能修改,但是可以修改a指向地址的內(nèi)容;前兩者表示不能通過(guò)指針a修改a所指向的內(nèi)容,但是a可以指向其他地址;---參考鏈接:https://www.cnblogs.com/wecode/p/6146739.html
- 有符號(hào)數(shù)二進(jìn)制原碼,正數(shù),最高位符號(hào)位是0,其余部分是數(shù)值位;負(fù)數(shù),最高位符號(hào)位是1,其余部分是數(shù)值位;
- 有符號(hào)數(shù)二進(jìn)制補(bǔ)碼,正數(shù)的補(bǔ)碼就是二進(jìn)制原碼,負(fù)數(shù)的補(bǔ)碼是二進(jìn)制反碼加1;
- 有符號(hào)數(shù)SWORD16的最大值是0x7FFF,第一位拿來(lái)當(dāng)做符號(hào)位;
- 有符號(hào)數(shù)的二進(jìn)制反碼,正數(shù)的反碼與二進(jìn)制原碼相同,負(fù)數(shù)的二進(jìn)制反碼是二進(jìn)制原碼除符號(hào)位外按位取反;
- 有符號(hào)數(shù)轉(zhuǎn)換成無(wú)符號(hào)數(shù),有符號(hào)正數(shù)原碼就是無(wú)符號(hào)正數(shù)原碼,有符號(hào)負(fù)數(shù)二進(jìn)制原碼的補(bǔ)碼就是轉(zhuǎn)換成的無(wú)符號(hào)數(shù);
- 無(wú)符號(hào)數(shù)轉(zhuǎn)換成有符號(hào)數(shù),最高位不為1,無(wú)符號(hào)數(shù)的原碼就是有符號(hào)數(shù)的原碼,最高位為1,將此無(wú)符號(hào)數(shù)的原碼視為有符號(hào)數(shù)的原碼,除最高位即符號(hào)位負(fù)號(hào)外,最低7位作為數(shù)值位;
- 補(bǔ)碼=>原碼,符號(hào)位0 補(bǔ)碼就是原碼;符號(hào)位1 ,其余位取反后加1 是原碼
Chapter2//一種簡(jiǎn)單的方法,使一段代碼第一次執(zhí)行時(shí)的行為與以后的執(zhí)行時(shí)不同 generate_initializer(char * string) static char separator = ' '; printf('%c %s \n', seperator, string); static function turnip() {} //static 聲明表示在這個(gè)文件之外不可見(jiàn) static 在函數(shù)內(nèi)部,表示該變量的值在各個(gè)調(diào)用間一直保持延續(xù)性; extern 用于函數(shù)定義,表示全局可見(jiàn)(屬于冗余),用于變量表示它在其他地方定義; void 作為函數(shù)的返回類(lèi)型表示不反悔任何值,在指針的申聲明中表示通用指針的類(lèi)型,位于參數(shù)列表中表示沒(méi)有參數(shù); sizeof操作數(shù)是類(lèi)型時(shí)兩邊必須加上括號(hào),但操作數(shù)如果是變量則不必加括號(hào);
針對(duì)最后一行,當(dāng)執(zhí)行i = 1,2; 賦值最運(yùn)算符優(yōu)先級(jí)高于逗號(hào),i 應(yīng)該是1,但是實(shí)際上i 賦值為1,接著執(zhí)行常量2的運(yùn)算,計(jì)算結(jié)果將丟棄.最終i的結(jié)果是1而不是2.在if 和while 等后面需要一個(gè)布爾值的時(shí)候,& 和 |就被翻譯成&& 和 || ;如果在一般的表達(dá)式里,就被解釋成位操作符。 操作符的結(jié)合性,在幾個(gè)操作符具有相同優(yōu)先級(jí)時(shí)決定先執(zhí)行哪一個(gè),賦值運(yùn)算符具有右結(jié)合性,位操作符 & 和 | 具有左結(jié)合性。 不能返回指向局部變量的指針
/* 將源文件的timestamp轉(zhuǎn)換為表示當(dāng)?shù)馗袷饺掌诘淖址?*/ char * localized_time(char * filename) /* 獲得源文件的timestamp, 格式為time_t */ stat(filename, &stat_block); /* 把UNIX的time_t轉(zhuǎn)換為tm結(jié)構(gòu),里面保存當(dāng)?shù)貢r(shí)間 */ tm_ptr = localtime(&stat_block.st_mttime); /* 把tm結(jié)構(gòu)轉(zhuǎn)換成以當(dāng)?shù)厝掌诟袷奖硎镜淖址?*/ strftime(buffer, sizeof(buffer), '%a %b %e %T %Y',tm_ptr);
// 這段代碼的問(wèn)題在于最后一行返回局部變量的指針,當(dāng)函數(shù)結(jié)束時(shí),由于該變量已經(jīng)被銷(xiāo)毀,即當(dāng)控制流離開(kāi) //聲明自動(dòng)變量范圍時(shí),局部變量自動(dòng)失效,無(wú)法知道這個(gè)局部變量指針?biāo)傅膬?nèi)容是什么? 解決辦法有: 1.返回一個(gè)指向字符串常量的指針; 2.使用全局申明的數(shù)組,但是也要防止其他人修改這個(gè)全局?jǐn)?shù)組; 3.使用靜態(tài)數(shù)組,將buffer聲明為static char buffer[120],這可以防止其他人修改這個(gè)數(shù)組只有擁有指向該數(shù)組的指針的函數(shù)才能修改這個(gè)靜態(tài)數(shù)組,但是函數(shù)的下一次調(diào)用將會(huì)覆蓋這個(gè)數(shù)組的內(nèi)容,另外,和全局?jǐn)?shù)組一樣,大型緩沖區(qū)閑置不用是非常浪費(fèi)內(nèi)存的; 4.顯式分配一些內(nèi)存, 這個(gè)方法具有靜態(tài)數(shù)組的優(yōu)點(diǎn),每次調(diào)用會(huì)創(chuàng)建一個(gè)新的緩沖區(qū),所以該函數(shù)以后的調(diào)用不用覆蓋以前的返回值。適用于多線(xiàn)程的代碼。缺點(diǎn)是要求程序員必須承擔(dān)內(nèi)存管理的責(zé)任,會(huì)增加內(nèi)存尚在適用就釋放或者內(nèi)存泄露的風(fēng)險(xiǎn); 5.最好的解決方法是要求調(diào)用者分配內(nèi)存來(lái)保存函數(shù)的返回值。為了提高安全性,調(diào)用者應(yīng)該同時(shí)指定緩存區(qū)的大小 void func(char * result, int size) { strncpy(result, 'That'd be in the data segment, Bob', size);
Chapter 3C語(yǔ)言晦澀的聲明語(yǔ)句是一個(gè)缺點(diǎn); union與結(jié)構(gòu)體類(lèi)似,但是內(nèi)存布局上存在關(guān)鍵性的區(qū)別,在結(jié)構(gòu)體中所有成員依次存儲(chǔ),在union中所有成員的都從零開(kāi)始存儲(chǔ)。這樣,每個(gè)成員的位置都重疊在一起:在某一時(shí)刻,只有一個(gè)成員正真存儲(chǔ)于該地址。union與struct結(jié)構(gòu)一樣,只是用union取代了關(guān)鍵字struct。union一般用來(lái)節(jié)省空間,也可以把同一個(gè)數(shù)據(jù)解釋成兩種不同的東西而不需要賦值或強(qiáng)制類(lèi)型轉(zhuǎn)換 int whole; /*一個(gè)32位的值*/ struct {char c0, c1, c2, c3;} byte;/*4個(gè)8位的字節(jié)*/
用enum完成的事都可以用#define來(lái)完成;默認(rèn)情況下enum從整形值0開(kāi)始,如果對(duì)某個(gè)標(biāo)識(shí)符賦值,那么緊隨其后的那個(gè)標(biāo)識(shí)符的值就比所賦的值大1。 enum Day {MON=1, TUE, WED, THU, FRI, SAT, SUN};
C語(yǔ)言聲明的優(yōu)先級(jí)規(guī)則 const關(guān)鍵字的后緊跟類(lèi)型說(shuō)明符,那么它作用于類(lèi)型說(shuō)明符。其他情況下,關(guān)鍵字作用于左邊緊鄰的指針星號(hào)*。 合理使用const關(guān)鍵字可以使編譯器自然地保護(hù)那些不希望被改變的參數(shù),防止其被無(wú)意的代碼修改。 優(yōu)先級(jí)從高到低依次是: 1.聲明中被括號(hào)括起來(lái)的部分; 2.后綴操作符:括號(hào)()表示這個(gè)是函數(shù),方括號(hào)[]表示這是一個(gè)數(shù)組; 3.前綴操作符:星號(hào)* 表示 “指向...的指針” 用優(yōu)先級(jí)規(guī)則來(lái)分析C語(yǔ)言聲明,char * const *(*next)(); 1.首先找標(biāo)識(shí)符“next”,并注意到它被括號(hào)括??; 2.把括號(hào)里的東西作為一個(gè)整體,得出是“next是一個(gè)指向...的指針” 3.然后考慮括號(hào)外面的東西,在星號(hào)前綴和括號(hào)后綴之間作出選擇, 4.后綴運(yùn)算符優(yōu)先級(jí)高于前綴運(yùn)算符,右邊是括號(hào),得出“next是一個(gè)函數(shù)指針,指向一個(gè)返回...的函數(shù)” 5.然后處理前綴運(yùn)算符,得出指針?biāo)傅膬?nèi)容 6.把char * const 解釋指向字符的常量指針 總結(jié)就是,next是一個(gè)指針,指向一個(gè)函數(shù),函數(shù)返回另一個(gè)指針,該指針指向一個(gè)指向char 類(lèi)型的常量指針 char *( * c[10])(int **p);聲明是c是一個(gè)數(shù)組,數(shù)組中每個(gè)元素是一個(gè)指針,這些指針都是具有(int **p)形參的指針函數(shù),函數(shù)返回一個(gè)指向char類(lèi)型的指針,函數(shù)的形參(int **p) p是一個(gè)指針,指向一個(gè)指向int的指針; - typedef 并不創(chuàng)建變量,而是宣稱(chēng)“這個(gè)名字是指定類(lèi)型的同義詞”
- void (*signal(int sig, void(*func)(int)))(int);分析這個(gè)聲明,signal前面有*結(jié)合后面的括號(hào)說(shuō)明是函數(shù)且返回指針,參數(shù)是int和函數(shù)指針;返回的指針指向一個(gè)函數(shù),函數(shù)接收一個(gè)int類(lèi)型參數(shù)并返回void。
- 用typedef定義簡(jiǎn)化這個(gè)函數(shù),分為兩步
typedef void(* ptr_to_func) (int); //typedef聲明表示ptr_to_func是這種函數(shù)指針結(jié)構(gòu)的別名,可以用ptr_to_func在其他地方替換它代表的函數(shù)指針結(jié)構(gòu) /*表示ptr_to_func是一個(gè)函數(shù)指針,函數(shù)接收int參數(shù),返回void*/ ptr_to_func signal (int, ptr_to_func); /* signal是一個(gè)函數(shù),它接受兩個(gè)參數(shù),一個(gè)是int,一個(gè)是ptr_to_func,返回ptr_to_func*/
typedef int x[10]和#define x int[10]的區(qū)別 1.typedef定義的類(lèi)型別名不能擴(kuò)展 unsigned peach i; /* 沒(méi)問(wèn)題 */ unsigned banana i; /* 錯(cuò)誤!非法 */
2.在連續(xù)的變量聲明中,用typedef定義的類(lèi)型能夠保證聲明中所有的變量均為同一種類(lèi)型,用#define定義的類(lèi)型無(wú)法保證。 // 經(jīng)過(guò)宏擴(kuò)展變成int * chalk, cheese;這使得chalk和cheese是不同的類(lèi)型 char_ptr Bentley, Rolls_Royce; // Bentley和Rolls_Royce的類(lèi)型依然相同
typedef聲明引入了my_type這個(gè)“別名”作為“struct my_tag {int i;}”的簡(jiǎn)寫(xiě)形式,但同時(shí)引入了結(jié)構(gòu)標(biāo)簽my_tag, typedef struct my_tag {int i;} my_type; struct my_tag variable_1;
但是如果用同一個(gè)標(biāo)志符表示標(biāo)簽和別名,那么以后使用這個(gè)標(biāo)志符號(hào)時(shí)前面就不必使用關(guān)鍵字struct,但是這個(gè)向灌輸了一種 完全錯(cuò)誤的思維方式;為了使代碼清晰,結(jié)構(gòu)標(biāo)簽和typedef別名不能用相同的標(biāo)志符。 typedef struct fruit {int weight,price;} fruit; /* 定義了結(jié)構(gòu)標(biāo)簽fruit和別名fruit */ struct veg{int weight, price;} veg; /* 定義了結(jié)構(gòu)標(biāo)簽veg和變量veg */ struct fruit mandarin; /* 使用結(jié)構(gòu)標(biāo)簽fruit */ fruit mandarin; /* 使用結(jié)構(gòu)類(lèi)型fruit */ struct veg potato; /* 可以接受 */
鏈接:函數(shù)指針與指針函數(shù)的解釋有助于理解復(fù)雜的函數(shù)聲明,參考---https://www.cnblogs.com/code1527/p/3249027.html 始終從內(nèi)往外找標(biāo)志符; 優(yōu)先級(jí):被括號(hào)括起來(lái)的聲明 > () 和 [] > *; int *pfun(int, int);
pfun左邊*右邊(),()優(yōu)先級(jí)高于*,pfun(int,int)結(jié)合,pfun是函數(shù),然后從pfun函數(shù)名往左找返回值,找到*,* pfun函數(shù)返回指針,然后繼續(xù)int * pfun(), 指針指向int類(lèi)型;總結(jié)下來(lái)就是,pfun是一個(gè)函數(shù),返回指向int類(lèi)型的指針 找到標(biāo)識(shí)符pfun,pfun被()和*一起括起來(lái)后優(yōu)先級(jí)高,(*pfun)是一個(gè)指針,往右讀結(jié)合()后,(*pfun)(int, int)說(shuō)明指針指向函數(shù),然后從(*pfun)往左找函數(shù)的返回類(lèi)型int;總結(jié)是pfun是一個(gè)指針,指向返回int類(lèi)型的函數(shù). int *(*x[10])(void);
找到標(biāo)志符x,x左側(cè)*右側(cè)[],[]優(yōu)先級(jí)高于*,x結(jié)合[]得到x是一個(gè)數(shù)組x[],然后與*括起來(lái)得到(*x[10])表示數(shù)組元素是指針,(*x[10])左側(cè)*右側(cè)(),()優(yōu)先級(jí)高于*,則(*x[10])(void)表示數(shù)組的元素是指針,而指針指向參數(shù)為void的函數(shù),最后從(*x[10])向左找函數(shù)的函數(shù)類(lèi)型*,*(*x[10])(void)表示指針指向的函數(shù)返回類(lèi)型是指針,然后繼續(xù)往左int *(*x[10])(void)指針指向一個(gè)int類(lèi)型;總結(jié)是x是數(shù)組,數(shù)組的元素是指針,指針指向函數(shù),函數(shù)返回指向int類(lèi)型的指針。 int (*ff(int))(int *, int); 找到標(biāo)志符ff,左側(cè)*右側(cè)(),()優(yōu)先級(jí)高于*,ff()表示ff函數(shù),然后被括號(hào)括起來(lái)(*ff(int)),ff的左側(cè)是*表示函數(shù)的返回類(lèi)型是指針,然后結(jié)合右側(cè)括號(hào)后得到(*ff(int))(int*,int),ff函數(shù)返回的指針指向一個(gè)參數(shù)為(int *,int)的函數(shù),然后從(*ff(int))往左找函數(shù)的返回類(lèi)型是int;總結(jié)是,ff是一個(gè)函數(shù),函數(shù)返回一個(gè)指針,指針指向參數(shù)為(int*, int)返回int類(lèi)型的函數(shù);這樣的用法晦澀難懂,一般會(huì)這樣用typedef int(*PF)(int*, int); PF是一個(gè)指針,用typedef聲明后,則PF成為函數(shù)指針“類(lèi)型”,可以當(dāng)做函數(shù)的返回類(lèi)型; PF ff(int),用PF聲明函數(shù),這樣來(lái)替代int (*ff(int))(int*,int)。 Chapter 4為什么指針和數(shù)組很多情況下會(huì)被錯(cuò)誤認(rèn)為是可以互換的?答案是對(duì)數(shù)組的引用總是可以寫(xiě)成對(duì)指針的引用; extern char a[] 與extern char a[100]等價(jià),都表示a是一個(gè)數(shù)組,也就是一個(gè)內(nèi)存地址。 編譯器并不需要知道數(shù)組總共有多長(zhǎng),因?yàn)樗划a(chǎn)生偏離起始地址的偏移地址;而指針的訪(fǎng)問(wèn)必須明確知道指針的地址。 數(shù)組和指針的訪(fǎng)問(wèn)不同在于,編譯器符號(hào)表存儲(chǔ)的是數(shù)組的首地址,再加上偏移量就可以訪(fǎng)問(wèn)數(shù)組元素, 對(duì)指針而言存儲(chǔ)的是指針?lè)?hào),需要先訪(fǎng)問(wèn)符號(hào)p的地址,取符號(hào)p的內(nèi)容,最后取p的內(nèi)容指向的字符; 定義成指針,以數(shù)組訪(fǎng)問(wèn)時(shí) char *p = 'abcdefg'; ... p[3] char a[] = 'abcdefg' ... a[3]
兩者都可以取得字符“d”,指針p獲得字符d的過(guò)程 1、取得符號(hào)表p的地址,提取存儲(chǔ)于此處的指針, 2、把下標(biāo)所表示的偏移量與指針的值相加,產(chǎn)生一個(gè)地址, 3、訪(fǎng)問(wèn)上面這個(gè)地址,取得字符; 指針與數(shù)組的區(qū)別 定義指針時(shí),編譯器并不為指針?biāo)赶虻膶?duì)象分配空間,它只是分配指針本身的空間,除非為用字符串常量初始化指針; char *p = 'bread', 在ANSI C中,初始化指針?biāo)鶆?chuàng)建的字符串常量被定義為只讀;初始化數(shù)組所創(chuàng)建的字符串常量是可以被修改的char a[] = 'strawberry'; strncpy(a, 'black', 5); Chapter 5編譯系統(tǒng):預(yù)處理器->編譯器->匯編器->鏈接器; 預(yù)處理 (C Preprocessor):預(yù)處理以字符#開(kāi)頭的命令,修改原始的C程序,得到.i作為文件擴(kuò)展名; 編譯階段: 將文本文件.i翻譯成文本文件.s,它包含一個(gè)匯編語(yǔ)言程序,匯編語(yǔ)言為不同的編譯器提供了通用的輸出語(yǔ)言; 如下所示hello.s 匯編階段:匯編器將.s翻譯成機(jī)器語(yǔ)言指令,并打包成可重定位目標(biāo)程序(relocatable object program),并將結(jié)果保存在hello.o中,hello.o文件是一個(gè)二進(jìn)制文件,用文本編輯器打開(kāi)是一堆亂碼; 鏈接階段 :hello程序調(diào)用了printf函數(shù),它是每個(gè)C編譯器都提供的標(biāo)注C庫(kù)的一個(gè)函數(shù)。printf函數(shù)存在于一個(gè)名為printf.o的單獨(dú)的預(yù)編譯好了的目標(biāo)文件中,而這個(gè)文件必須以某種方式合并到hello.o中。鏈接就是把.o文件合并在一起得到hello文件,它是一個(gè)可執(zhí)行目標(biāo)文件,可以被加載到內(nèi)存,處理器讀取并解釋儲(chǔ)存在內(nèi)存中的指令。 GNU環(huán)境包括EMACS編輯器,GCC編譯器,GDB調(diào)試器,處理二進(jìn)制文件的工具以及其他一些部件,但內(nèi)核是Linux的;gcc編譯器支持多種語(yǔ)言,C,C++,Fortran,Java,Pascal,Object-C 系統(tǒng)的硬件組成:總線(xiàn),通常總線(xiàn)被設(shè)計(jì)成傳送定長(zhǎng)的字節(jié)塊,也就是字(word),字中的字節(jié)數(shù)(字長(zhǎng))是一個(gè)基本的系統(tǒng)參數(shù),32位機(jī)器字長(zhǎng)是四個(gè)字節(jié),64位機(jī)器是8個(gè)字節(jié);I/O設(shè)備:鼠標(biāo),鍵盤(pán),顯示器,磁盤(pán)都有一個(gè)控制器或適配器與總線(xiàn)相連;主存是一個(gè)線(xiàn)性的字節(jié)數(shù)組,每個(gè)字節(jié)都有都有其唯一的地址,一般來(lái)說(shuō)程序的每條指令都由不同數(shù)量的字節(jié)構(gòu)成;處理器:解釋存儲(chǔ)在主存中指令的引擎,處理器的核心是一個(gè)大小為一個(gè)字的存儲(chǔ)設(shè)備(寄存器),成為程序計(jì)數(shù)器(PC),任何時(shí)候,PC都指向主存中的某條機(jī)器語(yǔ)言指令(含有該條指令的地址)。處理器從系統(tǒng)上電到斷電一直不斷的執(zhí)行程序計(jì)數(shù)器指向的指令,再更新程序計(jì)數(shù)器,使其指令下一條指令。處理器看上去是按照一個(gè)非常簡(jiǎn)單的指令執(zhí)行模型來(lái)操作的,這個(gè)模型是指令集架構(gòu)決定的。定的。 高速緩存存儲(chǔ)器,緩解了處理器寄存器和內(nèi)存之間的讀取速度差異,作為暫時(shí)的集結(jié)區(qū)域,存放處理器近期可能會(huì)需要的信息。j進(jìn)程是操作系統(tǒng)對(duì)一個(gè)正在運(yùn)行的程序的一種抽象,在一個(gè)系統(tǒng)上可以同時(shí)運(yùn)行多個(gè)進(jìn)程;而并發(fā)進(jìn)行則是說(shuō)一個(gè)進(jìn)程的指令和另一個(gè)進(jìn)程的指令是交錯(cuò)執(zhí)行的。操作系統(tǒng)實(shí)現(xiàn)處理器在進(jìn)程間切換的機(jī)制稱(chēng)為上下文切換。進(jìn)程到另一個(gè)進(jìn)程是由操作系統(tǒng)內(nèi)核管理的,內(nèi)核是操作系統(tǒng)代碼常駐主存的部分。一個(gè)進(jìn)程實(shí)際上由多個(gè)稱(chēng)為線(xiàn)程的執(zhí)行單元組成,每個(gè)線(xiàn)程都運(yùn)行在進(jìn)程的上下文中,并共享同樣的代碼和全局?jǐn)?shù)據(jù)。 虛擬內(nèi)存為每個(gè)進(jìn)程提供了完整的虛擬地址空間,讓進(jìn)程認(rèn)為它獨(dú)占內(nèi)存。進(jìn)程的虛擬地址空間, 程序被編譯成二進(jìn)制文件=代碼段(text)+ 數(shù)據(jù)段(data)+ Bss段 ; 在運(yùn)行時(shí)程序在內(nèi)存中分布 = 代碼段(text)+ 初始化數(shù)據(jù)段(data) + 未初始化數(shù)據(jù)段(Bss) + 堆(heap)+ 棧(stack) Bss中存儲(chǔ)未初始化的數(shù)據(jù),所以data與bss的區(qū)別在于bss只存儲(chǔ)變量需要占據(jù)的內(nèi)存大小但并不分配內(nèi)存,要為data分配內(nèi)存 以上內(nèi)容可參考博客: https://blog.csdn.net/YuZhiHui_No1/article/details/38458711 https://blog.csdn.net/gatieme/article/details/43567433 https://blog.csdn.net/K346K346/article/details/45592329 https://blog.csdn.net/love_gaohz/article/details/41310597 https://blog.csdn.net/zhangskd/article/details/6956638 https://blog.csdn.net/acs713/article/details/9055193 ( 字符數(shù)組分配在棧中分配內(nèi)存;而字符串分配在文字常量區(qū)(數(shù)據(jù)區(qū))); 指令集并行:現(xiàn)代處理器可以同時(shí)執(zhí)行多條指令的屬性成為指令集并行。 當(dāng)鏈接一個(gè)程序時(shí),需要使用的每個(gè)庫(kù)函數(shù)的一份拷貝被加入到可執(zhí)行文件中。今年來(lái)更新的動(dòng)態(tài)鏈接逐漸被采用。動(dòng)態(tài)鏈接庫(kù)提供一個(gè)龐大的函數(shù)庫(kù)集合,程序?qū)⒃谶\(yùn)行時(shí)尋找需要的函數(shù),而不是把函數(shù)庫(kù)的二進(jìn)制代碼作為自身可執(zhí)行文件的一部分。 如果函數(shù)庫(kù)的一份拷貝作為可執(zhí)行文件的物理組成部分,稱(chēng)之為靜態(tài)鏈接;如果可執(zhí)行文件名只是包含了文件名,讓載入器在運(yùn)行時(shí)能夠?qū)ふ页绦蛩枰暮瘮?shù)庫(kù),稱(chēng)之為動(dòng)態(tài)鏈接。執(zhí)行可執(zhí)行文件的三個(gè)階段是鏈接-編輯(link-editing)、載入(loading)和運(yùn)行時(shí)鏈接(runtime linking); 靜態(tài)鏈接和動(dòng)態(tài)鏈接 靜態(tài)鏈接的模塊在鏈接編輯并載入等待運(yùn)行,動(dòng)態(tài)鏈接的模塊被鏈接編輯后載入,在運(yùn)行時(shí)進(jìn)行鏈接以便運(yùn)行。程序執(zhí)行時(shí),在main()函數(shù)被調(diào)用之前,運(yùn)行時(shí)載入把共享的數(shù)據(jù)對(duì)象載入到進(jìn)程的地址空間。外部函數(shù)被真正調(diào)用之前,運(yùn)行時(shí)載入器并不解析它們。因此即使鏈接了函數(shù)庫(kù),如果并沒(méi)有實(shí)際調(diào)用,也不會(huì)帶來(lái)額外開(kāi)銷(xiāo)。 動(dòng)態(tài)鏈接的主要目的是把程序和它使用的特定的函數(shù)庫(kù)版本分離開(kāi)來(lái),取而代之的是,約定由系統(tǒng)向程序提供一個(gè)接口,并保持穩(wěn)定,不隨時(shí)間和操作系統(tǒng)的后續(xù)版本發(fā)生變化。由于它是介于應(yīng)用程序和函數(shù)庫(kù)二進(jìn)制可執(zhí)行文件所提供的服務(wù)之間的接口,所以稱(chēng)為應(yīng)用程序的二進(jìn)制接口(Application Binary Interface,ABI)。動(dòng)態(tài)鏈接必須保證4個(gè)特定函數(shù)庫(kù):libc(C 運(yùn)行時(shí)函數(shù)庫(kù))libsys(其他系統(tǒng)函數(shù)),libX(X windowing)和libnsl(網(wǎng)絡(luò)服務(wù))。 動(dòng)態(tài)鏈接的可執(zhí)行文件體積可以很小,雖然運(yùn)行速度稍微慢一些,但可以更有效的利用磁盤(pán)空間和虛擬內(nèi)存,因?yàn)楹瘮?shù)庫(kù)只有在需要時(shí)才被映射到進(jìn)程中,鏈接器通過(guò)把庫(kù)文件名或路徑名植入到可執(zhí)行文件來(lái)做到這一點(diǎn),而且鏈接編輯階段的時(shí)間也會(huì)縮短(因?yàn)殒溄悠鞯挠行┕ぷ鞅煌七t到載入時(shí))。所有動(dòng)態(tài)鏈接到某個(gè)特定函數(shù)庫(kù)的可執(zhí)行文件在運(yùn)行時(shí)共享該函數(shù)庫(kù)的一個(gè)單獨(dú)拷貝,這就提供了更高的I/O交換和交換空間利用率。 動(dòng)態(tài)鏈接是一種just-in-time鏈接,意味著程序運(yùn)行時(shí)必須能夠找到它們所需要的函數(shù)庫(kù)。鏈接器通過(guò)把庫(kù)文件名或路徑名植入到可執(zhí)行文件中來(lái)做到這一點(diǎn)。這意味著,函數(shù)庫(kù)的路徑不能隨意移動(dòng)。當(dāng)在一臺(tái)機(jī)器人編譯完成后,拿到另一臺(tái)機(jī)器上運(yùn)行時(shí)可能會(huì)出現(xiàn)這種情況。 靜態(tài)鏈接擴(kuò)展名“.a”(archive),動(dòng)態(tài)鏈接擴(kuò)展名“.so”(shared object,表示每一個(gè)鏈接到該函數(shù)庫(kù)的程序都共享它的一份拷貝);創(chuàng)建靜態(tài)或動(dòng)態(tài)的函數(shù)庫(kù),只需簡(jiǎn)單地編譯一些不包含main函數(shù)的代碼,并把編譯所產(chǎn)生的.o文件用正確的實(shí)用工具處理,如果是靜態(tài)庫(kù),實(shí)用“ar”,如果是動(dòng)態(tài)庫(kù),使用“l(fā)d”. 參考博客:Unix編譯C語(yǔ)言程序----https://blog.csdn.net/ZR_Lang/article/details/17080335 Chapter 6運(yùn)行時(shí)系統(tǒng); a.out是assembler output“匯編程序輸出”的縮寫(xiě)形式(由于歷史原因),但實(shí)際上是鏈接器輸出。 UNIX系統(tǒng),ELF,Extensible Linker Format 可擴(kuò)展鏈接器格式,現(xiàn)在指Executable and Linking Format,可執(zhí)行文件和鏈接格式;section是ELF文件中最小組織單位,一個(gè)段(Segment)一般包含幾個(gè) section,段表示一個(gè)二進(jìn)制文件相關(guān)的內(nèi)容塊。 size a.out會(huì)得到a.out文件中三個(gè)段(文本段,數(shù)據(jù)段和bss段),bss段是Block Started by Symbol(由符號(hào) 開(kāi)始的塊),bss字段只保存沒(méi)有值的變量; a.out以segment(段)的形式組織,因?yàn)槎慰梢苑奖愕挠成涞竭\(yùn)行時(shí)鏈接器直接載入載入的對(duì)象中。載入器只是取 文件中每個(gè)段的映像,并放入到內(nèi)存中。 堆棧段在程序執(zhí)行時(shí)用于保存臨時(shí)數(shù)據(jù),局部變量,傳遞到函數(shù)中的參數(shù)等,還有malloc分配的動(dòng)態(tài)內(nèi)存。 過(guò)程活動(dòng)記錄,跟蹤調(diào)用鏈; Chapter 7虛擬地址,頁(yè) 虛擬內(nèi)存鏈接: https:///post/59f8691b51882534af254317; https://www.cnblogs.com/zl1991/p/8193398.html 進(jìn)程只能操作位于物理內(nèi)存的頁(yè)面。當(dāng)進(jìn)程引用一個(gè)不存在的物理內(nèi)存頁(yè)面時(shí),MMU(內(nèi)存管理單元)產(chǎn)生頁(yè)錯(cuò)誤,內(nèi)核會(huì)判斷該引用是否有效。如果無(wú)效,內(nèi)核向進(jìn)程發(fā)出一個(gè)'segmentation violation(段違規(guī))'的信號(hào)。如果有效內(nèi)核從磁盤(pán)取回該頁(yè),換入到內(nèi)存中。一旦頁(yè)面進(jìn)入內(nèi)存,進(jìn)程便被解鎖,可以重新運(yùn)行---進(jìn)程本身并不知道它曾經(jīng)因?yàn)轫?yè)面換入事件等待了一會(huì) 訪(fǎng)問(wèn)速度排序,CPU寄存器>Cache存儲(chǔ)器>內(nèi)存>磁盤(pán);虛擬內(nèi)存以“頁(yè)”的形式組織。頁(yè)就是在操作系統(tǒng)在磁盤(pán)和內(nèi)存之間移動(dòng)的對(duì)象,一般大小幾K字節(jié)。內(nèi)存的映像在磁盤(pán)和物理內(nèi)存之間來(lái)回移動(dòng),稱(chēng)為page in(移入內(nèi)存)或page out(移到磁盤(pán))。虛擬地址按頁(yè)劃分,頁(yè)映射到物理內(nèi)存或虛擬內(nèi)存上,頁(yè)被加載入內(nèi)存時(shí)MMU(內(nèi)存管理單元)會(huì)將虛地址轉(zhuǎn)換成物理內(nèi)存地址。 野指針可能會(huì)訪(fǎng)問(wèn)沒(méi)有建立與物理內(nèi)存映射的虛擬內(nèi)存。內(nèi)核空間根據(jù)獨(dú)立且唯一的頁(yè)表init_mm.pgd進(jìn)行映射,而用戶(hù)空間的頁(yè)表則每個(gè)進(jìn)程維護(hù)自己的一份。 Cache包含一個(gè)地址的列表以及它們的內(nèi)容,隨著CPU不斷引用新的內(nèi)存地址,Cache的地址列表也一直處于變化中。所有對(duì)于內(nèi)存的讀取和寫(xiě)入都會(huì)經(jīng)過(guò)Cache。當(dāng)處理器要引用的地址在Cache中,它可以立刻從Cache中獲取。否則,Cache就向內(nèi)存?zhèn)鬟f這個(gè)請(qǐng)求,于是就要較緩慢的訪(fǎng)問(wèn)內(nèi)存操作。內(nèi)存讀取的數(shù)據(jù)以行為單位,在讀取的同時(shí)也轉(zhuǎn)入到Cache中。在UNIX中,內(nèi)存就是磁盤(pán)inode的Cache.在關(guān)閉電源前需要把Cache的內(nèi)容刷新到磁盤(pán),文件系統(tǒng)就有可能損壞。 Cache的訪(fǎng)問(wèn)單位是'行'(line),每行有兩部分組成,數(shù)據(jù)部分以及用于指定它所代表的地址的標(biāo)簽;數(shù)據(jù)部分被稱(chēng)為“塊”(block),塊保存來(lái)回移動(dòng)于Cache行和內(nèi)存之間的字節(jié)數(shù)據(jù),典型塊大小為32字節(jié);一個(gè)Cache行的內(nèi)容代表特定的內(nèi)存塊,如果處理器試圖訪(fǎng)問(wèn)屬于該塊對(duì)應(yīng)范圍地址的內(nèi)存,Cache會(huì)立刻做出反應(yīng)不需要向內(nèi)存?zhèn)鬟f請(qǐng)求,這樣自然就會(huì)塊很多。一個(gè)Cache(一般為64K到1M之間)由許多行組成。為了提高速度,Cache的位置離CPU很近,而且內(nèi)存系統(tǒng)和總線(xiàn)經(jīng)過(guò)高度優(yōu)化,盡可能地提高大小等于Cache塊的數(shù)據(jù)塊的移動(dòng)速度。 就像堆棧段能夠根據(jù)需要內(nèi)存上的堆區(qū)域用于動(dòng)態(tài)內(nèi)存的分配。堆內(nèi)存的回收不必與它所分配的順序一致,所以無(wú)序的malloc/free最終會(huì)產(chǎn)生堆碎片內(nèi)存損壞,釋放或改寫(xiě)仍在使用的內(nèi)存;內(nèi)存泄露:未釋放不再使用的內(nèi)存。 函數(shù)參數(shù)中,數(shù)組是作為指針來(lái)傳遞的,所以函數(shù)參數(shù)中數(shù)組和指針形式是可以互換的,譬如main函數(shù)中的char **argv和char *argv[]使用快速的左移運(yùn)算代替緩慢的加法預(yù)算;“表達(dá)式中的數(shù)組名”就是指針;C語(yǔ)言把數(shù)組的下標(biāo)改寫(xiě)成指針偏移量的根本原因是指針和偏移量是底層硬件所使用的基本模型;”作為函數(shù)參數(shù)的數(shù)組名“等同于指針;ANCI C標(biāo)準(zhǔn)規(guī)定作為”類(lèi)型的數(shù)組“的形參的聲明應(yīng)該調(diào)整為”類(lèi)型的指針“。在函數(shù)形參定義這個(gè)特殊情況下,編譯器必須把數(shù)組形式的形參改寫(xiě)成指向該數(shù)組第一個(gè)元素的指針形式。編譯器只向函數(shù)傳遞數(shù)組的地址,而不是整個(gè)數(shù)組的拷貝。為什么C語(yǔ)言把數(shù)組形參當(dāng)做指針?在C語(yǔ)言中,所有非數(shù)組形參的數(shù)據(jù)均以傳值形式(對(duì)實(shí)參做一份拷貝并傳給調(diào)用的函數(shù),函數(shù)不能修改作為實(shí)參的實(shí)際變量的值,而只能修改傳遞給它的那份拷貝)調(diào)用。如果拷貝整個(gè)數(shù)組,內(nèi)存空間和時(shí)間上開(kāi)銷(xiāo)都非常大。 Chapter 9 arr = array2; //雖然arr申明為數(shù)組,但是作為函數(shù)形參會(huì)被編譯器轉(zhuǎn)成指向數(shù)組第一個(gè)元素的指針 int array[100], array2[100]; array = array2;//編譯失敗,因?yàn)閍rray這里就是數(shù)組,不等同于指針,數(shù)組名作為左值是不能被修改 鏈接:https://www.cnblogs.com/Howe-Young/p/4160289.html int (*array)[20] //指針,指向20個(gè)元素的int數(shù)組 int *array[20] //指針數(shù)組,每一個(gè)元素指向int類(lèi)型 int (*pear)[20]; /* 聲明一個(gè)指向包含20個(gè)int元素的數(shù)組的指針 */ pear = calloc(20, sizeof(int)); if(!pear) longjmp(error, 1); result = paf(); //調(diào)用函數(shù) (*result)[3] = 12; //訪(fǎng)問(wèn)數(shù)組 //定義結(jié)構(gòu)包含數(shù)組,讓函數(shù)返回結(jié)構(gòu) struct a_tag my_function() { //函數(shù)不可以返回指向局部變量的指針,但可以返回局部變量,確切的說(shuō),函數(shù)不能返回指向調(diào)用棧的指針, //但是可以返回指向調(diào)用堆的指針;將局部變量聲明為static,變量就保存在數(shù)據(jù)段而不是調(diào)用棧中,該變量 //聲明周期與程序一樣長(zhǎng),函數(shù)退出時(shí),變量的值依然存在
Chapter 11面向?qū)ο笞兂傻奶攸c(diǎn)是集成和動(dòng)態(tài)綁定。C++通過(guò)類(lèi)的派生支持集成,通過(guò)虛擬函數(shù)支持動(dòng)態(tài)綁定。虛擬函數(shù)提供了一種封裝類(lèi)體系實(shí)現(xiàn)細(xì)節(jié)的方法。 繼承與嵌套不同。嵌套常用于實(shí)現(xiàn)容器類(lèi)(數(shù)據(jù)結(jié)構(gòu)的類(lèi),如鏈表,散列表,隊(duì)列等)。C++的模板也用于實(shí)現(xiàn)容器類(lèi)。 C++使用<<操作符(輸入)和>>操作符(輸出)來(lái)替代C語(yǔ)言中的putchar()和getchar()等函數(shù)。 <<和>>操作符在C語(yǔ)言中用作左移位和右移位操作符,在C++中被重載用于C++的I/O,編譯器查看操作數(shù)的類(lèi)型來(lái)決定移位還是I/O代碼。 <<和>>操作符可被定義用于任何類(lèi)型,不需要像C語(yǔ)言一樣用字符串格式化限定符%d,并且可以連續(xù)輸出 例如,cout << 'the value is' << i << endl; 但仍然可以使用C++中的stdio.h函數(shù); 函數(shù)重載總是在編譯時(shí)進(jìn)行解析,編譯器查看操作數(shù)的類(lèi)型并決定使用哪種定義,因此重載函數(shù)原型必須不同,運(yùn)算符重載也是函數(shù)重載。 多態(tài)-----運(yùn)行時(shí)綁定,也成為后期綁定,virtual 成員函數(shù)聲明多態(tài)函數(shù);C++通過(guò)覆蓋支持(override)機(jī)制來(lái)實(shí)現(xiàn)多態(tài);當(dāng)使用類(lèi)繼承時(shí)就要用到這種機(jī)制;virtual 函數(shù)的原型必須相同,由運(yùn)行時(shí)系統(tǒng)決定調(diào)用哪個(gè)函數(shù),基類(lèi)中用virtual聲明,virtual關(guān)鍵字不可以省略。 虛擬函數(shù)表; 異常(exception):通過(guò)發(fā)生錯(cuò)誤時(shí)把自動(dòng)切換到程序中用于處理錯(cuò)誤的那部分代碼,從而簡(jiǎn)化錯(cuò)誤處理 模板(template):支持參數(shù)化類(lèi)型。同類(lèi)型/對(duì)象的關(guān)系一樣,函數(shù)/模板的關(guān)系也可以看作是為算法提供一種“甜點(diǎn)刀具”的方法,一旦確定了基本的算法就可以用于不同的類(lèi)型。例如,template<class T> T min(T a, T b) { return (a<b) ? a : b },允許對(duì)min函數(shù)和變量a,b賦予任意的類(lèi)型(該類(lèi)型必須能接收<操作符) 內(nèi)聯(lián)(inline):規(guī)定某個(gè)函數(shù)在行內(nèi)以指令流的形式展開(kāi)(就像宏一樣),而不是產(chǎn)生一個(gè)函數(shù)調(diào)用。 new 和 delelte操作符,用于取代malloc和free函數(shù)。new和delete用來(lái)更方便一些(如能夠自動(dòng)完成sizeof的計(jì)算工作,并會(huì)自動(dòng)調(diào)用合適的構(gòu)造函數(shù)和析構(gòu)函數(shù)),new能夠真正地創(chuàng)建一個(gè)對(duì)象,而malloc只是分配內(nèi)存。 傳引用調(diào)用(call by reference,相當(dāng)于傳址調(diào)用):C語(yǔ)言只使用傳值調(diào)用 編程語(yǔ)言有一個(gè)特性,成為正交性(orthogonality),是指不同的特性遵循同一個(gè)基本原則的程度(也就是學(xué)會(huì)一種特性有助于學(xué)習(xí)其他的特性)。 Fortran語(yǔ)言(Formula translation)是第一個(gè)高級(jí)語(yǔ)言,提供了強(qiáng)大的方法來(lái)表達(dá)數(shù)學(xué)公式 C語(yǔ)言設(shè)計(jì)哲學(xué) 一切工作程序員自己負(fù)責(zé); C語(yǔ)言的所有特性都不需要隱式的運(yùn)行時(shí)支持; 程序員所做的都是對(duì)的; 程序員應(yīng)該知道自己在干什么,并保證自己的所作所為是正確。 C++對(duì)C語(yǔ)言的改進(jìn) C語(yǔ)言中,初始化一個(gè)字符數(shù)組的方式很容易產(chǎn)生錯(cuò)誤,就是數(shù)組很可能沒(méi)有足夠的空間存放結(jié)尾的NULL字符。 C++對(duì)此作了一些改進(jìn),像char b[3] = 'Bob'這樣的表達(dá)式被認(rèn)為是錯(cuò)誤的,但是在C語(yǔ)言中是合法的。 類(lèi)型轉(zhuǎn)換即可以是float(i)這樣看上去更順眼的形式,也可以寫(xiě)成像(float)i這樣稍怪異的C語(yǔ)言風(fēng)格的形式。 C++允許一個(gè)常量整數(shù)來(lái)定義數(shù)組的大小, const int size = 128; char a[size]; 但是C語(yǔ)言中卻是不合法的,C語(yǔ)言聲明數(shù)組的大小只能用char a[128]或char a[宏定義定義的數(shù)字] C++聲明可以穿插于語(yǔ)句之間。在C語(yǔ)言中,一個(gè)語(yǔ)句塊中的所有聲明必須放在所有語(yǔ)句的前面。C++去掉了這個(gè)限制。 在C++中存在,但在C語(yǔ)言中卻不存在的限制有 C++中用戶(hù)代碼不能調(diào)用main()函數(shù),但在C語(yǔ)言中確實(shí)允許的; C++中要求完整的函數(shù)原型聲明,但是在C語(yǔ)言中卻沒(méi)那么嚴(yán)格; C++ typedef定義的名字不能與已有的結(jié)構(gòu)標(biāo)簽沖突,但是C語(yǔ)言中卻是允許的(它們分別屬于不同的名字空間)。 當(dāng)void *指針賦值給另一個(gè)類(lèi)型的指針時(shí),C++規(guī)定必須進(jìn)行類(lèi)型轉(zhuǎn)換,但在C語(yǔ)言中卻無(wú)必要。 在C++和C語(yǔ)言中含義不一樣的特性: C++至少增加了十幾個(gè)關(guān)鍵字。這些關(guān)鍵字在C語(yǔ)言中可以作為標(biāo)識(shí)符使用,但如果這樣做了,用C++編譯器編譯這些代碼就會(huì)產(chǎn)生錯(cuò)誤信息。 C++,聲明可以出現(xiàn)在語(yǔ)句可以出現(xiàn)的任何地方。在C語(yǔ)言代碼中塊中,所有的聲明必須出現(xiàn)在所有語(yǔ)句的前面 C++中一個(gè)內(nèi)層作用域的結(jié)構(gòu)名將會(huì)隱藏外層空間相同的對(duì)象名。在C語(yǔ)言中則并非如此。
|