C語言深度剖析讀書筆記_第1頁
C語言深度剖析讀書筆記_第2頁
C語言深度剖析讀書筆記_第3頁
C語言深度剖析讀書筆記_第4頁
C語言深度剖析讀書筆記_第5頁
已閱讀5頁,還剩12頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、C語言深度剖析讀書筆記第1章關(guān)鍵字1.1、定義與聲明的區(qū)別:定義創(chuàng)建了對象并為對象分配了內(nèi)存,聲明沒有分配內(nèi)存1.2、register請求編譯器盡可能將變量存在CPU寄存器中以提高訪問速度,register變量必須為CPU寄存器所能接受的類型,它須是一個單一的值,并且長度<=整型的長度,由于register變量可能不放在內(nèi)存中,故不可以用”&”來獲取它的地址1.3、函數(shù)前面加static使得函數(shù)成為靜態(tài)函數(shù),它的作用域僅限于本文件中,故又稱內(nèi)部函數(shù)1.4、case關(guān)鍵字后面只能是整數(shù)或字符型的常量或常量表達(dá)式。      

2、0;  const int a = 5;    case a:   /const只讀變量,編譯出錯,case label does not reduce to an integer constant    case 1.1: /小數(shù),編譯出錯,case label does not reduce to an integer constant    case 3/2:  /沒有問題,分?jǐn)?shù)會被轉(zhuǎn)換成整數(shù)1.5、“跨循環(huán)層”的概念本身是說,由外層循環(huán)進(jìn)入內(nèi)層循環(huán)

3、是要重新初始化循環(huán)計數(shù)器的,包括保存外層循環(huán)的計數(shù)器和加載內(nèi)層循環(huán)計數(shù)器,退出內(nèi)層的時候再恢復(fù)外層循環(huán)計數(shù)器。把長循環(huán)放在里面可以顯著減小這些操作的數(shù)量,還可以增加cache的命中率。在多重循環(huán)中,如果有可能,應(yīng)當(dāng)將最長的循環(huán)放在最內(nèi)層,最短的循環(huán)放在最外層,以減少CPU跨切循環(huán)層的次數(shù)。for(i = 0; i < 50; i+)    for(j = 0; j < 10000; j+)    效率比下面這個高for(i = 0; i < 10000; i+)   

4、 for(j = 0; j < 50; j+)    1.6、void指針的算術(shù)操作    void *pvoid;    pvoid+; /ANSI認(rèn)為是錯誤的,因為它認(rèn)為進(jìn)行算術(shù)操作的指針必須知道它所指向的數(shù)據(jù)類型大小    pvoid += 1; /ANSI認(rèn)為是錯誤的    /但GNU指定void *的算術(shù)操作跟char *相同。1.7、const  &

5、#160; 編譯器通常不為普通const 只讀變量分配存儲空間,而是將它們保存在符號表中,這使得它成為一個編譯期間的值,沒有了存儲與讀內(nèi)存的操作,使得它的效率也很高。例如:#define M 3/宏常量const int N=5; /此時并未將N 放入內(nèi)存中.int i=N;/此時為N 分配內(nèi)存,以后不再分配!int I=M;/預(yù)編譯期間進(jìn)行宏替換,分配內(nèi)存int j=N;/沒有內(nèi)存分配int J=M;/再進(jìn)行宏替換,又一次分配內(nèi)存!const 定義的只讀變量從匯編的角度來看,只是給出了對應(yīng)的內(nèi)存地址,在程序運行過程中只有一份拷貝。#defin

6、e 定義的宏常量在內(nèi)存中有若干個拷貝。#define 宏是在預(yù)編譯階段進(jìn)行替換,而const 修飾的只讀變量是在編譯的時候確定其值怎么看const修飾哪個對象先忽略類型名(編譯器解析的時候也是忽略類型名)。看const 離哪個近。離誰近就修飾誰。const int *p; /const *p/const 修飾*p,p 是指針,*p 是指針指向的對象,不可變int const *p; /const *p/const 修飾*p,p 是指針,*p 是指針指向的對象,不可變int *const p; /

7、*const p/const 修飾p,p 不可變,p 指向的對象可變const int *const p; /前一個const 修飾*p,后一個const 修飾p,指針p 和p 指向的對象都不可變1.8、volatile    編譯器遇到這個關(guān)鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進(jìn)行優(yōu)化,從而可以提供對特殊地址的穩(wěn)定訪問先看看下面的例子:int i=10;int j = i;/(1)語句int k = i;/(2)語句這時候編譯器對代碼進(jìn)行優(yōu)化,因為在(1)(2)兩條語句中,

8、i 沒有被用作左值。這時候編譯器認(rèn)為i 的值沒有發(fā)生改變,所以在(1)語句時從內(nèi)存中取出i 的值賦給j 之后,這個值并沒有被丟掉,而是在(2)語句時繼續(xù)用這個值給k 賦值。編譯器不會生成出匯編代碼重新從內(nèi)存里取i 的值,這樣提高了效率。但要注意:(1)(2)語句之間i 沒有被用作左值才行。再看另一個例子:volatile int i=10;int j = i;/(3)語句int k = i;/(4)語句volatile 關(guān)鍵字告訴編譯器i 是隨時可能發(fā)生變化的,每次使用它的時候必須從內(nèi)存中取出i的值,因而

9、編譯器生成的匯編代碼會重新從i 的地址處讀取數(shù)據(jù)放在k 中。這樣看來,如果i 是一個寄存器變量或者表示一個端口數(shù)據(jù)或者是多個線程的共享數(shù)據(jù),就容易出錯,所以說volatile 可以保證對特殊地址的穩(wěn)定訪問。1.9 、大部分編譯器中,默認(rèn)情況,enum會轉(zhuǎn)化為intenum ColorGREEN = 1,REDCol故sizeof(Col) = sizeof(int)第2章、符號2.1、注釋int /*.*/i;   /編譯器會用空格代替原來的注釋,這里相當(dāng)于 int   i  編譯能通過2.2、

10、a<<b+c 相當(dāng)于a << (b+c)+優(yōu)先級高于 <<2.3、貪心法  a+b 表達(dá)式與 (a+) +b 一致C 語言有這樣一個規(guī)則:每一個符號應(yīng)該包含盡可能多的字符。也就是說,編譯器將程序分解成符號的方法是,從左到右一個一個字符地讀入,如果該字符可能組成一個符號,那么再讀入下一個字符,判斷已經(jīng)讀入的兩個字符組成的字符串是否可能是一個符號的組成部分;如果可能,繼續(xù)讀入下一個字符,重復(fù)上述判斷,直到讀入的字符組成的字符串已不再可能組成一個有意義的符號。這個處理的策略被稱為“貪心法”。按照這個規(guī)則可能很輕松的判斷 a+

11、b 表達(dá)式與 a+ +b 一致第3章、預(yù)處理3.1、注釋先于預(yù)處理指令被處理#define BSC /#define BMC /*#define EMC */D),BSC my single-line commentE),BMC my multi-line comment EMCD)和E)都錯誤,為什么呢?因為注釋先于預(yù)處理指令被處理,當(dāng)這兩行被展開成/或/*/時,注釋已處理完畢,此時再出現(xiàn)/或/*/自然錯誤.因此,試圖用宏開始或結(jié)束一段注釋是不行的。3.6、內(nèi)存對齊使用指令#pragma pack (n),編譯器將按照 n 個字節(jié)對齊。使用指令#pragma pack (),編譯器將取消自定

12、義字節(jié)對齊方式。#include <stdio.h>struct st1    char a ;    int  b ;    short c ;struct st2    char a;    struct st1 b;  /復(fù)雜類型(如結(jié)構(gòu))的默認(rèn)對齊方式是它最長的成員的對齊方式    int c;int main(int argc, char *argv)  

13、  printf("%d %dn", sizeof(struct st1), sizeof(struct st2);    return 0;運行結(jié)果:12 20St1 :char占一個字節(jié),起始偏移為0 ,int 占4個字節(jié),min(#pragma pack()指定的數(shù),這個數(shù)據(jù)成員的自身長度) = 4(VC6默認(rèn)8字節(jié)對齊),所以int按4字節(jié)對齊,起始偏移必須為4的倍數(shù),所以起始偏移為4,在char后編譯器會添加3個字節(jié)的額外字節(jié),不存放任意數(shù)據(jù)。short占2個字節(jié),按2字節(jié)對齊,起始偏移為8,正好是2的倍數(shù),無須添加額外字節(jié)

14、。到此規(guī)則1的數(shù)據(jù)成員對齊結(jié)束,此時的內(nèi)存狀態(tài)為:oxxx|  oooo|  oo0123  4567   89 (地址)(x表示額外添加的字節(jié))共占10個字節(jié)。還要繼續(xù)進(jìn)行結(jié)構(gòu)本身的對齊,對齊將按照#pragma pack指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合)最大數(shù)據(jù)成員長度中,比較小的那個進(jìn)行,st1結(jié)構(gòu)中最大數(shù)據(jù)成員長度為int,占4字節(jié),而默認(rèn)的#pragma pack 指定的值為8,所以結(jié)果本身按照4字節(jié)對齊,結(jié)構(gòu)總大小必須為4的倍數(shù),這樣在處理數(shù)組時可以保證每一項都邊界對齊,需添加2個額外字節(jié)使結(jié)構(gòu)的總大小為12 。此時的內(nèi)存狀態(tài)為:oxxx|

15、oooo|ooxx0123 4567 89ab  (地址)到此內(nèi)存對齊結(jié)束。St1占用了12個字節(jié)而非7個字節(jié)。3.7、宏參數(shù)中的#字符串中包含宏參數(shù),那我們就可以使用“#”#define SQR(x) printf("The square of "#x" is %d.n", (x)*(x);再使用:SQR(8);則輸出的是:The square of 8 is 64.3.8、#這個運算符把兩個語言符號組合成單個語言符號??蠢?#define XNAME(n) x # n如果這樣使用宏:XNAME(8)則會被展開成這樣:x8#將前后兩部分粘合

16、起來 第4章、指針和數(shù)組4.2、int a5. sizeof(a5)關(guān)鍵字 sizeof 求值是在編譯的時候。雖然并不存在a5這個元素,但是這里也并沒有去真正訪問 a5,而是僅僅根據(jù)數(shù)組元素的類型來確定其值。所以這里使用 a5并不會出錯。4.3.3指針和數(shù)組的定義與聲明。要確認(rèn)你的代碼在一個地方定義為指針,在別的地方也只能聲明為指針;在一個的地方定義為數(shù)組,在別的地方也只能聲明為數(shù)組。如文件1中定義 char a100 = 0x31, 0x32, 0x33, 0x34, 0x35;文件2中這樣進(jìn)行聲明extern char *a;雖然在文件 1 中,編譯器知道 a 是一個數(shù)組,但是在

17、文件 2 中,編譯器并不知道這點。大多數(shù)編譯器是按文件分別編譯的,編譯器只按照本文件中聲明的類型來處理。所以,雖然 a 實際大小為 100 個 byte,但是在文件 2 中,編譯器認(rèn)為 a 是一個char*指針,只占 4 個 byte。編譯器會把存在指針變量中的任何數(shù)據(jù)當(dāng)作地址來處理。故文件2中a存放的數(shù)據(jù)其實是文件1中數(shù)組a的前4個元素,即文件2中 a=0x34333231(小端機(jī)器),對這個未定義的地址進(jìn)行訪問,所以出錯了。C語言多文件編譯時,編譯器不檢測其聲明的變量類型與定義時的類型是否匹配4.5指針的算術(shù)運算/* Compiler: GCC* Last Update:  Sa

18、t 21 Apr 2012 05:25:43 PM CST*/#include <stdio.h> int main(int argc, char *argv)    char a7='A','B','C','D', 'E', 'F'    char (*p3)3 = &a; /編譯會提示warning,運行沒錯,最好別這樣用    char (*p4)3 = a; &#

19、160; /編譯會提示warning,運行沒錯,最好別這樣用    int b2;    printf("%dn", &b1 - &b0); /相減也是以步長來計算    printf("%sn", *p3);    /指針相加就是加上指針的步長,這里是+ sizeof(char 3)    printf("%sn", *(p3 + 1);  

20、0; printf("%cn", *p3);    printf("%sn", *p4);    printf("%sn", *(p4 + 1); /+ sizeof(char 3)    printf("%cn", *p4);    return 0;運行結(jié)果:1ABCDEFDEFAABCDEFDEFA4.6.3二維數(shù)組參數(shù)與二維指針參數(shù),二維數(shù)組初始化void fun(char a34);可以

21、把 a34理解為一個一維數(shù)組 a3,其每個元素都是一個含有 4 個 char 類型數(shù)據(jù)的數(shù)組。上面的規(guī)則, 語言中,當(dāng)一維數(shù)組作為函數(shù)參數(shù)的時候,編譯器總是把它解析成一個指向其首元素首地址的指針。也就是說我們可以把這個函數(shù)聲明改寫為:void fun(char (*p)4);/* Compiler: GCC* Last Update:  Sat 21 Apr 2012 05:36:16 PM CST*/#include <stdio.h> int main(int argc, char *argv)    int a4=0,0,3,

22、0,10; /第一個表示對第一行元素進(jìn)行賦值,第二個對第二行.    int i, j;    for(i = 0; i < 3; +i)        for(j = 0; j < 4; +j)               printf("%d ", aij);   

23、;             printf("n");           return 0;運行結(jié)果:0 0 3 00 0 0 00 10 04.7.2函數(shù)指針/* Compiler: GCC* Last Update:  Sat 21 Apr 2012 05:45:15 PM CST*/#include <stdio.h>int f(int a)

24、0;   return a;int main(int argc, char *argv)    int (*ptrF1)(int) = f;       int (*ptrF2)(int) = &f;       printf("%x %xn", f, f + 1);  /函數(shù)指針+1的結(jié)果就是該指針值直接加1,不需考慮步長    printf("%

25、x %xn", &f, &f + 1);    return 0;運行結(jié)果:80483c4 80483c580483c4 80483c5函數(shù)名是在編譯時關(guān)聯(lián)到某個地址.直接使用函數(shù)名的時候是指那個地址在函數(shù)名前加&還是那個地址,要具體了解原理得從編譯器的角度出發(fā)下面是我用gdb調(diào)試的結(jié)果,&f、f、*f、*f的值都是一樣的Breakpoint 1, main (argc=1, argv=0xbffff374) at 1.c:1515      int (*ptrF2)(int) =

26、&f;   (gdb) p f$1 = int (int) 0x80483c4 <f>(gdb) p &f$2 = (int (*)(int) 0x80483c4 <f>(gdb) p *f$3 = int (int) 0x80483c4 <f>(gdb) p *f$4 = int (int) 0x80483c4 <f>4.7.3 C Traps and Pitfalls書中的一個例子: (* ( void (*) ( )  )0 )( )第一步:void(*) (),可

27、以明白這是一個函數(shù)指針類型。這個函數(shù)沒有參數(shù),沒有返回值。第二步:(void(*) ()0,這是將 0 強(qiáng)制轉(zhuǎn)換為函數(shù)指針類型,0 是一個地址,也就是說一個函數(shù)存在首地址為 0 的一段區(qū)域內(nèi)。第三步:(*(void(*) ()0),這是取 0 地址開始的一段內(nèi)存里面的內(nèi)容,其內(nèi)容就是保存在首地址為 0 的一段區(qū)域內(nèi)的函數(shù)。第四步:(*(void(*) ()0)(),這是函數(shù)調(diào)用。第5章、內(nèi)存管理5.1、野指針,也就是指向不可用內(nèi)存區(qū)域的指針。通常對這種指針進(jìn)行操作的話,將會使程序發(fā)生不可預(yù)知的錯誤。    “野指針”不是NULL指針,是指向“垃圾”內(nèi)存的指針。人們

28、一般不會錯用NULL指針,因為用if語句很容易判斷。但是“野指針”是很危險的,if語句對它不起作用。野指針的成因主要有兩種:一、指針變量沒有被初始化。任何指針變量剛被創(chuàng)建時不會自動成為NULL指針,它的缺省值是隨機(jī)的,它會亂指一氣。所以,指針變量在創(chuàng)建的同時應(yīng)當(dāng)被初始化,要么將指針設(shè)置為NULL,要么讓它指向合法的內(nèi)存。二、指針p被free或者delete之后,沒有置為NULL,讓人誤以為p是個合法的指針。別看free和delete的名字惡狠狠的(尤其是delete),它們只是把指針?biāo)傅膬?nèi)存給釋放掉,但并沒有把指針本身干掉。通常會用語句if (p != NULL)進(jìn)行防錯處理。很遺憾,此時i

29、f語句起不到防錯作用,因為即便p不是NULL指針,它也不指向合法的內(nèi)存塊5.2、定義數(shù)組 int a10,用memset(a, 0, sizeof(a)對其進(jìn)行初始化5.3、assert 是一個宏,而不是函數(shù)。如果其后面括號里的值為假,則程序終止運行,并提示出錯;如果后面括號里的值為真,則繼續(xù)運行后面的代碼。這個宏只在 Debug 版本上起作用,而在 Release 版本被編譯器完全優(yōu)化掉,這樣就不會影響代碼的性能。、malloc()申請0字節(jié)內(nèi)存man 3 mallocIf size is 0, then malloc() returns either  N

30、ULL,       or a unique pointer value that can later be successfully passed to free().示例:/* Compiler: GCC* Last Update:  Sat 21 Apr 2012 11:59:22 PM CST*/#include <stdio.h>#include <string.h>#include <stdlib.h>int main(int argc, char *argv) &#

31、160;  char *c = malloc(0);    if(c != NULL) /申請0字節(jié),返回不是NULL        strcpy(c, "aaaaaaaaaaaaaaa");        puts(c);        free(c); /free出錯了     

32、60;  return 0;、void free(void *ptr);if free(ptr) has already been called before, undefined behavior occurs.  If ptr is NULL, no operation is performed示例:/* Compiler: GCC* Last Update:  Sun 22 Apr 2012 12:02:38 AM CST*/#include <stdio.h>#include <string.h>#include <stdlib.h>int main(int argc, char *argv)    char *c = malloc(20 * sizeof(char);    if(c != NULL)        strcpy(c, "aaaaaaaaaaaaaaa"

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論