第一章
第一節(jié)
1,register關(guān)鍵字簡化了編譯器,卻將包袱丟給了程序員。
第三節(jié)
1,宏處理器只應(yīng)該適量使用。最好只用于命名常量,并為一些適當(dāng)?shù)慕Y(jié)構(gòu)提供簡捷的記法。
2,不要使用C預(yù)處理器修改語言的基礎(chǔ)結(jié)構(gòu)。
第八節(jié)
1,K&R C中,函數(shù)聲明的形參類型檢測在鏈接時或者不作檢查,函數(shù)定義頭以分號結(jié)尾。
2,雖然編譯器忽略形參名,但函數(shù)聲明的形參名對程序員有好處。
3,除非需要使用類型提升,否則不要使用K&R C的寫法定義函數(shù)。
4,從const char *到const char **
第九節(jié)
1,const char * 和 char * const區(qū)別
const char *的const修飾的對象是其指針?biāo)傅腸har對象。
char * const的const修飾的對象是該指針本身的值。
2,從char **賦值到const char **
如果從char *復(fù)制到const char *,如很多的字符串處理函數(shù),都是可以的。
但是從char **賦值到const char **,就不行了。
因為char**的含義是:指向,一個指向char類型的指針,的指針。
而const char **的含義是:指向,一個指向char類型的,帶const限定的指針,的指針。
即兩個指針的類型并不相同。
但是將char **賦值給char * const *是沒有問題的。因為const修飾的是指針的對象,不再是指針本身。
第十節(jié)
1,c中的浮點數(shù)據(jù)類型:
float, double, long double三種,其長度分別為4,8, 12個字節(jié)。不可在其前加unsigned修飾。
2,尋常算術(shù)轉(zhuǎn)換:
如果一個操作數(shù)是long double,則另一個操作數(shù)被轉(zhuǎn)換為long double;如果一個操作數(shù)是double,
則另一個操作數(shù)被轉(zhuǎn)為double,再如果一個操作數(shù)是float,則另一個操作數(shù)被轉(zhuǎn)為float。
結(jié)果也一樣跟著被轉(zhuǎn)換。
3,c中的字符及整型
char , short int, int , long int , long long int
修飾short, long , long long其后可不接int,默認(rèn)為int(問題,不為int還能是其他么?)
4,ansi c的值保留原則:
多種不同類型的整型混合運算的時候,其轉(zhuǎn)換是依照類型的大小進(jìn)行。
5,整型提升:
c專家編程中1.10節(jié)的內(nèi)容看得懂,但梳理不了。
第十一節(jié)
#pragma指示器的作用是由編譯器定義的。
第二章
第一節(jié)
1,分析編程語言缺陷的一種方法就是將所有缺陷分成三種:多做之過,少做之過,和誤做之過。
第二節(jié)
1,switch的default語句可以出現(xiàn)在case列表的任意位置,并在所有case均無法匹配時被執(zhí)行。
2,switch在花括號之后的任意語句都可以進(jìn)行變量聲明,但在case之前的語句不會被執(zhí)行。
3,switch內(nèi)部的任何語句都可以包含標(biāo)簽。
4,switch缺省采用"fall througth",在97%的情況下都是錯誤的。
5,拖尾逗號沒有存在的必要(字符串?dāng)?shù)組的最后一個逗號)
6,c中對信息可見性的選擇非常少。
第三節(jié)
1,apple = sizeof(int) * p; 不懂。
2,當(dāng)按常規(guī)方式使用時,可能會引起誤會的任何運算符,都是c運算符存在錯誤優(yōu)先級的表現(xiàn)。
3,“有些運算符是的優(yōu)先級是錯誤的”,忘記這些錯誤,就是對了。
4,結(jié)合性,在幾個操作符具有相同優(yōu)先級時決定先執(zhí)行哪個。
5,在函數(shù)調(diào)用中,各個參數(shù)的計算順序是不確定的。
第四節(jié)
1,unix和ansi c都不能區(qū)分傳遞給程序的選項和參數(shù)。
2,空格在c中有時是必不可少的,例如:z=x+++++y;z=*x/*y;
3,c++的行注釋可能會破壞原來正確的c代碼,如:
a //*
//*/ b
4,避免函數(shù)返回值被覆蓋的最好方法也許就是要求調(diào)用者提供自行分配內(nèi)存。
5,將lint程序作為一個獨立的工具通常意味著將lint程序束之高閣。
第五節(jié)
1,即使可以保證你的編程語言100%可靠,你仍然可能成為算法中災(zāi)難性bug的犧牲品。
第三章
第一節(jié)
1,K&R 承認(rèn)C語言聲明的語法有時會帶來嚴(yán)重的問題。
2,指向數(shù)組的指針
char (*j)[20];
j = (char (*)[20])malloc(20);
3,telnet程序的一行代碼:
char * const *(*next)();
第二節(jié)
1,列出的兩個表看不懂,不知道有什么用。
第三節(jié),第四節(jié)
1,理解c語言聲明的優(yōu)先級規(guī)則和圖標(biāo)分析c語言聲明,感覺作用不大。感覺記住了運算符的優(yōu)先級和結(jié)合性才是王道。
第五節(jié)
1,signal函數(shù)的ANSI C聲明:
void (*signal(int sig, void(*func)(int)))(int);
分析:signal跟其后的括號結(jié)合,則signal是一個函數(shù);signal函數(shù)的返回值跟signal前的星號結(jié)合,即返回一個指針。
到此,最左邊括號的全部內(nèi)容分析完。返回的指針右邊又是一個括號(int),則返回的指針是個函數(shù)指針,該指針指向
的函數(shù)返回void類型。
2,typedef的混亂語法:
可以將多個聲明器塞到一個聲明中;
typedef可以不放在聲明的開始處。
如:
typedef int *ptr, (*fun)(), arr[5];
/*
*ptr是int指針類型,fun是返回值為int類型的函數(shù)指針類型,arr是長度為5的int數(shù)組類型。
*/
unsigned const long typedef int volatile *kumquat;
第六節(jié)
1,可以用其他類型說明符對宏類型名進(jìn)行擴展,但對typedef所定義的類型名不能這么做,如:
#define peach int
unsigned peech i; /* ok */
typedef int banana;
unsigned banana i; /* error */
2,在連續(xù)幾個變量聲明中,用tpyedef定義的類型能夠保證聲明中所有的變量均為同一類型,而用#define定義的類型則無法保證。如:
#define int_ptr int *;
int_ptr chalk, cheese; /* cheese為整型 */
typedef int * int_ptr;
int_ptr a, b; /* a,b均為整型指針 */
第七節(jié)
1,不要使用typedef來省略struct關(guān)鍵字,因為struct關(guān)鍵字可以提示一些信息。
2,typedef應(yīng)該用在:
數(shù)組,結(jié)構(gòu),指針及函數(shù)的組合類型。
可移植類型。
3,應(yīng)該始終在結(jié)構(gòu)的定義中使用結(jié)構(gòu)標(biāo)簽,即使它并非必須。
4,typedef struct my_tag {int i;} my_type;這個typedef聲明引入了my_type這個名字作為"struct my_tag{int i;}"的簡寫。
第八節(jié)
1,編寫一個能夠分析c語言的聲明并將它們翻譯成通俗語言的程序。
2,該節(jié)有偽代碼,第九節(jié)有詳細(xì)c代碼。
第四章
第三節(jié)
1,代碼:
/*file1*/
int s[3] = {1, 2, 3};
/*file2*/
extern int *s;
在file2中不能使用下標(biāo)進(jìn)行數(shù)組訪問,如s[0]。
解釋:當(dāng)在file2使用s[0]的時候,其內(nèi)存訪問方式為,提取s本身的地址,得到一個指針,再提取指針指向的地址,該地址加上偏移,取該偏移地址的值。
而在file1使用使用s[0]的時候,其內(nèi)存訪問方位為,提取s本身的地址,加上偏移,取偏移地址的值。
而在file1,file2中變量s本身的值是相同的,但在file2中的s作為指針,其指向并未賦值。
可以使用以下代碼驗證:
/*file1*/
int p[10] = {1, 2, 3};
int *s = p;
/*file2*/
extern int *p;
extern int *s;
int
main(int argc, char *argv[])
{
printf("%p\n", p);
printf("%p\n", &p);
printf("%p\n", s);
printf("%d\n", (int)(&p)[1]);
return 0;
}
2,似乎書上最后(4.3節(jié))對:“聲明為extern char *p,定義為char p[10],使用指針的p[i]進(jìn)行訪問”的解釋不正確。
書上的意思是,對p[i]可以得到一個字符,而編譯器理解成了指針,從而錯誤,
但實際錯誤應(yīng)該是對p指向地址不確定。
3,對于多維數(shù)組的聲明,需要提供除左邊一維之外其他維的長度。
4,數(shù)組名在符號表中被映射為一個地址,不可修改。因此數(shù)組名是個左值,但不是可修改的左值。
第五章
第一節(jié)
1,絕大多數(shù)編譯器通常由多達(dá)六七個稍小的程序鎖組成,包括:預(yù)處理器,語法語義檢查器,代碼生成器,匯編程序,優(yōu)化器,鏈接器和調(diào)用這些程序的驅(qū)動器。
2,PC的鏈接器一般只提供幾個基本的I/O服務(wù),稱作BIOS。存在于內(nèi)存中固定的地點,并不是每個可執(zhí)行程序的一部分。
3,靜態(tài)鏈接:庫函數(shù)的代碼被加入到可執(zhí)行文件。
4,動態(tài)鏈接收集模塊準(zhǔn)備執(zhí)行的三個階段:link-editing, loading, runtime linking。
5,在main函數(shù)執(zhí)行前,運行時載入器將共享數(shù)據(jù)對象載入進(jìn)程地址空間,但直到被實際調(diào)用,才解析它們。
第二節(jié)
1,動態(tài)鏈接庫必須保證的四個特定函數(shù)庫:
libc(c運行時函數(shù)庫),libsys(其他系統(tǒng)函數(shù)),libX(X windowing)和libnsl(網(wǎng)絡(luò)服務(wù))。
2,鏈接器通過將庫文件名或路徑名植入可執(zhí)行程序來保證程序在運行時能找到所需函數(shù)庫。
3,有些函數(shù)庫只能使用動態(tài)鏈接,如果使用了其中一個,程序就必須使用動態(tài)鏈接。
4,位置無關(guān)代碼?
第三節(jié)
1,頭文件名字并不與它對于的函數(shù)庫名相似;函數(shù)定義在一個函數(shù)庫中,卻聲明在多個頭文件中。
2,cpp, gcc, as, ld
3,在動態(tài)鏈接庫中,所有的庫符號進(jìn)入輸出文件的虛擬地址空間中,所有的符號對于鏈接到一起的所有文件都是可見的。相反,對于靜態(tài)鏈接,在處理archive時,它只是在archive中查找載入器當(dāng)前所知道的未定義符號。
第四節(jié)
1,Interpositioning就是通過編寫與庫函數(shù)同名的函數(shù)來覆蓋該庫函數(shù)的行為。
2,使用Interpositioning,不僅自己調(diào)用的該庫函數(shù)會被自己的版本覆蓋,另一些調(diào)用該庫函數(shù)的庫函數(shù)都會調(diào)用你自己的版本。
第六章
1,編程語言理論的經(jīng)典對立之一就是代碼和數(shù)據(jù)的區(qū)別。
第一節(jié)
1,a.out - 匯編語言程序和鏈接編輯輸出格式
“匯編語言程序輸出”這個名字的產(chǎn)生純屬歷史原因。
第二節(jié)
1,目標(biāo)文件和可執(zhí)行文件可以有幾種不同的格式,linux采用ELF(可擴展鏈接器格式)。
2,section是ELF文件中的最小組織單位。一個段(segments)一般包含幾個section。
3,BSS段不保存在目標(biāo)文件中(除了記錄BSS段在運行時所需要的大小)
第三節(jié)
1,從本質(zhì)上說,段在正在執(zhí)行的程序中是一塊內(nèi)存區(qū)域,每個區(qū)域都有特定的目的。
第四節(jié)
1,堆棧段有三個主要用途:
為局部變量提供存儲空間
保存函數(shù)調(diào)用棧幀
臨時存儲區(qū),如算術(shù)表達(dá)式計算,alloca函數(shù)
2,除了遞歸調(diào)用之外,堆棧并非必須的。因為在編譯時可以知道局部變量的大小,參數(shù)和返回地址所需的空間固定大小。
第五節(jié)
1,c語言自動提供的服務(wù)之一就是跟蹤調(diào)用鏈。
2,linux棧幀結(jié)構(gòu):參數(shù),返回地址,局部變量。
第六節(jié)
1,對于程序員來說,auto關(guān)鍵字幾乎沒有什么用處,因為它只能用于函數(shù)內(nèi)部。但是在函數(shù)內(nèi)部聲明的數(shù)據(jù)缺省就是這種分配。唯一能用到auto的地方就是使你的聲明更加清楚整齊。
2,過程活動記錄并不一定要存在于堆棧中。事實上,盡可能地將過程活動記錄的內(nèi)容放到寄存器中會使函數(shù)調(diào)用的速度更快,效果更好。
第七節(jié)
1,保證局部變量在longjmp過程中一直保持它的值的唯一可靠方法是將它聲明為volatile(這使用于那些值在setjmp執(zhí)行和longjmp返回之間會改變的變量)。
2,setjmp和longjmp在c++中變異為更普通的異常處理機制catch和throw。
3,想goto一樣,setjmp和longjmp使得程序難以理解和調(diào)試。如果不是處于特殊需要,最好避免使用它們。
第十一節(jié)
1,用grep來調(diào)試操作系統(tǒng)內(nèi)核,一個非比尋常的概念。有時候甚至連源代碼工具都可以幫助解決運行時問題!
第七章
第三節(jié)
1,所有現(xiàn)代的計算機系統(tǒng),從最大的超級計算機到最小的工作站,都使用了虛擬內(nèi)存。
第八章
第三節(jié)
1,類型提升的出現(xiàn)并不局限于涉及操作符和混合類型操作數(shù)的表達(dá)式。
2,ANSI C表示如果編譯器能夠保證運算結(jié)果一致,也可以省略類型提升,這通常出現(xiàn)在表達(dá)式中存在常量操作數(shù)的時候。
3,在ANSI C中,如果使用了適當(dāng)?shù)暮瘮?shù)原型,類型提升就不會發(fā)生,否則也會發(fā)生。在被調(diào)用的函數(shù)內(nèi)部,提升后的參數(shù)被裁剪為原先聲明的大小。
4,這就是為什么單個的printf格式字符串%d能適應(yīng)幾個不同類型,short,char,int,而不論實際傳遞的參數(shù)是上述類型的哪一個。
第四節(jié)
1,ANSI C函數(shù)原型的目的是使C語言成為一種更加可靠的語言。建立原型就是為了消除一種普通(但很難發(fā)現(xiàn))的錯誤,就是形參和實參之間類型不匹配。
第五節(jié)
1,缺省的函數(shù)聲明(函數(shù)在另外的文件定義)是K&R C的聲明?
2,K&R C函數(shù)聲明和K&R C函數(shù)定義,會發(fā)生類型提升。
3,ANSI C函數(shù)聲明和ANSI C函數(shù)定義,不會發(fā)聲類型提升。
4,ANSI C和K&R C的聲明和定義混合的時候,注意K&R C的聲明會提升后傳遞參數(shù),而K&R C的定義會按提升之后接受參數(shù)。
5,由于不懂匯編,書上那些代碼不太明白。
第六節(jié)
1,curses函數(shù)庫提供了基于字符的屏幕控制函數(shù)。
第七節(jié)
1,在c語言中,有好幾種方法來表達(dá)FSM(有限自動機),但它們絕大多數(shù)都是基于函數(shù)指針數(shù)組,如:
extern int a(), b(), c(), d();
int (*state[])() = {a, b, c, d};
2,調(diào)用函數(shù)和通過指針調(diào)用函數(shù)(或任意層次的指針間接引用)可以使用同一種語法。至于數(shù)組,也有一個對應(yīng)的方法。這種做法進(jìn)一步惡化了本來就有缺陷的“聲明與使用相似”的設(shè)計哲學(xué)。
第八節(jié)
1,如果你擁有十分復(fù)雜的數(shù)據(jù)結(jié)構(gòu)時,你可以編寫并編譯一個函數(shù),用于遍歷整個數(shù)據(jù)結(jié)構(gòu)并打印出來。這個函數(shù)不會在代碼的任何地方調(diào)用,但它卻是可執(zhí)行文件的一部分。它就是debugging hooks。
2,有時候,花點時間把編程問題分解成幾個部分往往是解決它的最快方法。
第九節(jié)
1,調(diào)用qsort時,可以在調(diào)用時對第四個參數(shù)(函數(shù)指針)進(jìn)行強制類型轉(zhuǎn)換,而無須在函數(shù)內(nèi)進(jìn)行。
第九章
第一節(jié)
1,聲明可以分三種情況:外部聲明;定義;函數(shù)參數(shù)聲明。
2,對編譯器而言,一個數(shù)組就是一個地址,一個指針就是一個地址的地址。
第二節(jié)
1,表達(dá)式中的數(shù)組名(與聲明不同)被編譯器當(dāng)作一個指向該數(shù)組第一個元素的指針。
2,下標(biāo)總是與指針的偏移量相同。
3,在函數(shù)參數(shù)的聲明中,數(shù)組名被編譯器當(dāng)作指向該數(shù)組第一個元素的指針。
4,在下列情況中,對數(shù)組的引用不能用指向該數(shù)組第一個元素的指針來替代:
(1)數(shù)組作為sizeof()的操作數(shù)。
(2)使用&操作符取數(shù)組的地址。
(3)數(shù)組是一個字符串(或?qū)捵址┏A砍跏贾担ú欢?/span>
5,因為編譯器需要知道指針進(jìn)行解引用操作時應(yīng)該取幾個字節(jié),以及每個下標(biāo)的步長應(yīng)該取幾個字節(jié),所以指針總有類型限制。
6,c語言把數(shù)組下標(biāo)改寫成指針偏移量的根本原因是指針和偏移量是底層硬件所使用的基本模型。
第三節(jié)
1,之所以要把傳遞給函數(shù)的數(shù)組參數(shù)轉(zhuǎn)換為指針是出于效率的考慮,這個理由常常也是對違反軟件工程做法的辯解。
2,在函數(shù)調(diào)用中,數(shù)組和函數(shù)參數(shù)是傳址調(diào)用(函數(shù)參數(shù)都是傳址?有什么表現(xiàn)?)
3,在函數(shù)內(nèi)部對數(shù)組參數(shù)的任何引用都將產(chǎn)生一個對指針的引用。
4,有一樣操作只能在指針里進(jìn)行而無法在數(shù)組中進(jìn)行,那就是修改它的值。
第六節(jié)
1,盡管術(shù)語上稱作“多維數(shù)組”,但C語言實際上只支持“數(shù)組的數(shù)組”。
2,由于“行/列主序”這個術(shù)語只適用于恰好是二維的多維數(shù)組,所以更確切的術(shù)語是“最右的下標(biāo)先變化”。
3,只有字符串常量才可以初始化指針數(shù)組。
第十章
第二節(jié)
1,用于實現(xiàn)多維數(shù)組的指針數(shù)組有多種名字,如“Iliffe向量”,“display”,或“dope向量”。
第三節(jié)
1,用字符串指針填充Iliffe向量來創(chuàng)建一個“鋸齒狀數(shù)組”。
2,Iliffe向量可能會使字符串分配于內(nèi)存中不同的頁面中,導(dǎo)致更加頻繁的頁面交換。
3,“數(shù)組名被寫成一個指針參數(shù)”,規(guī)則并是遞歸定義的。數(shù)組的數(shù)組會被改寫為“數(shù)組的指針”,而不是“指針的指針”。
第六節(jié)
1,對多維數(shù)組作為參數(shù)傳遞的支持缺乏是C語言存在的一個內(nèi)在限制。
2,函數(shù)可以返回一個結(jié)構(gòu)體。
3,向printf函數(shù)傳遞一個NULL指針會導(dǎo)致程序的崩潰。
第七節(jié)
1,全局?jǐn)?shù)組變量,不能使用變量來確定長度。
2,程序信息具有啟發(fā)性,而非煽動性,并且要避免使用諸如帶有褻瀆性,口語化,幽默或者夸張的非專業(yè)用語。尤其是,如果你規(guī)規(guī)距距地這樣做,就可以避免在凌晨3點鐘被叫醒。
4,在實踐中,不要把realloc()函數(shù)的返回值直接賦給字符指針。如果realloc()函數(shù)失敗,它會使該指針的值編程NULL,這樣就無法對現(xiàn)有的表進(jìn)行訪問。
備注:
這個月里諸多不順,但還是一點一點翻完了這本書。自己做的筆記極少會看,算做個紀(jì)念咯。
2012-05-28