《C陷阱與缺陷》筆記_第1頁
《C陷阱與缺陷》筆記_第2頁
《C陷阱與缺陷》筆記_第3頁
《C陷阱與缺陷》筆記_第4頁
《C陷阱與缺陷》筆記_第5頁
已閱讀5頁,還剩9頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、在初讀C陷阱與缺陷時,前幾章介紹的問題經常遇到,較容易掌握。又因懶于動手未做筆記,越到后面越覺得自己記憶力有限才補做筆記。畢竟好記性不如爛筆頭。前四章時在別的筆記中粘貼過來。后面幾章的學習中遇到似是而非的問題也參照了其他人得筆記并整理出自己的東西。現在一并發出來,為更多的人提供參考。讓我們一起堅定的走下去! 第1章 詞法“陷阱” 1.1 不同于 為比較運算符, 為賦值運算符例: while( c = ' ' | c = 't' | c = 'n' )   c = getc( f ); &

2、#160;  本意是c和 ' ' 比較,但錯用成賦值符。這樣的后果是將 ' ' | c = 't' | c = 'n' 這個表達式的值給了c, 而使c = 1。    同樣: if ( ( filedesc = open( argvi, 0 ) ) < 0 ) error();    open的返回值和filedesc比較的結果只能是0或1,所以,error沒有時機調用。但是,此時filedesc的值于open返回值無關,編譯器這里不會報錯。容易被無視,達

3、不到檢查效果。1.2 & 和 | 不同于 && 和 |&和|均為按位運算符,而&& 和 | 均為邏輯運算符,不能混淆。1.3 語法分析中的“貪心法”當C編譯器讀入一個字符后又跟了一個字符,那么編譯器就必須做出判斷:是將其作為兩個分別的符號對待,還是合起來作為一個符號對待。C語言對這個問題的解決方案可以歸納為一個很簡單的規則:每一個符號應該包含盡可能多的字符。 a-b 與 a - - b 的含義相同,而與 a - - b 的含義不同。1.4 整型常量如果一個整形常量的第一個字符是數字0,那么該常量將被視作八進制數。因此,10和010是完全不同的含義

4、。此外書中還介紹了一些ANSI C不允許的做法,比方將8和9也作為八進制數字處理。1.5 字符和字符串C語言中的單引號和雙引號含義迥異,在某些情況下如果把兩者弄混,編譯器并不會檢測報錯,從而在運行是產生難以預料的結果。 用單引號引起的一個字符實際上代表一個整數,整數值對應于該字符在編譯器采用的字符集中的序列值。 用雙引號引起的字符串,代表的卻是一個指向無名數字起始字符的指針,該數組被雙引號之間的字符以及一個額外的二進制為零的字符''初始化。 然而,某些C編譯器對函數參數并不進行類型檢查,特別是對printf函數的參數。因此,如果用 printf(' ');來代替

5、正確的 printf(" ");則會在程序運行的時候產生難以預料的錯誤,而不會給出編譯器診斷信息。 整型數一般為16位或32為的存儲空間可以容納多個字符一般為8位,因此有個C編譯器允許在一個字符常量以及字符串常量中包括多個字符。也就是說,用'yes'代替"yes"不會被該編譯器檢測到。后者的含義是“一次包括'y''e''s'以及空字符''的4個連續內存單元的首地址“。前者的含義并沒有準確的進行定義,但大多數編譯器理解為,“一個整數值,由'y''e

6、9;'s'所代表的整數值按照特定編譯器實現中定義的方式組合得到“。 注:在Borland C+ v5.5 和 LCC v3.6中采取的做法是,忽略多余的字符,最后的整數值即第一個字符的整數值;而在Visual C+ 6.0 和 GCC v2.95中采取的做法是,依次用后一個字符覆蓋前一個字符,最后得到的整數值即最后一個字符的整數值。)第2章:語法“陷阱”2.1 理解函數聲明 (*(void(*)()0) (); 任何復雜表達式其實只有一條簡單的規則:按照使用的方式來聲明。 任何C變量的聲明都由兩部分組成:類型以及一組類似表達式的聲明符(declarator)。聲明符從外表上看與

7、表達式有些類似,對它求值應該返回一個聲明中給定類型的結果。 因為聲明符與表達式的相似,所以我們也可以在聲明符中任意使用括號: float (f); 這個聲明的含義是:當對其求值時,(f)的類型為浮點類型,由此可以推知,f也是浮點類型。 各種形式的聲明還可以組合起來,就像在表達式中進行組合一樣。因此, float *g(),(*h)()表示*g()與(*h)()是浮點表達式。因為()結合優先級高于*,*g()也就是*(g():g是一個函數,該函數的返回值類型為指向浮點數的指針。同理,可以得出h是一個函數指針,h所指向函數的返回值為浮點類型。 一旦我們知道了如何聲明一個給定類型的變量,那么該類型的

8、類型轉換符就很容易得到了:只需要把聲明中的變量名和聲明末尾的分號去掉,再將剩余的部分用一個括號整個“封裝”起來即可。例如,因為下面的聲明: float (*h)(); 表示h是一個指向返回值為浮點類型的函數的指針,因此,(float (*)()表示一個“指向返回值為浮點類型的函數的指針”的類型轉換符。 (*fp)(); -> (*0)(); -> (*(void (*)()0)(); 2.2 運算符的優先級問題優先級最高者其實并不是真正意義上的運算符,包括:數組下標,函數調用操作符各結構成員選擇操作符。他們都是自左于右結合,因此 a.b.c的含義是(a.b).c。 () ->

9、 . 單目運算符的優先級僅次于前述運算符。在所有的真正意義上的運算符中,它們的優先級最高。單目運算符是自右至左結合。因此*p+會被編譯器解釋成*(p+)。! + = = (type) * & sizeof優先級比單目運算符要低的,接下來就是雙目運算符。在雙目運算符中,算術運算符的優先級最高,移位運算符次之,關系運算符再次之,接著是邏輯運算符,賦值運算符,最后是條件運算符。 * / % + - << >> < <= > >= = != & | && | ?:我們需要記住的最重要的兩點是:1. 任何一個邏輯運算符的優先

10、級低于任何一個關系運算符。2. 移位去處符的優先級比算術運算符要低,但是比關系運算符要高。 2.3 主義作為語句結束標志的分號 2.4 關于switch語句 case ' ': linecount+; case ' ': case '': . 2.5 函數調用 f();是一個函數調用語句,而f; 計算函數f的地址,卻并不調用該函數。 2.6 “懸掛”else引發的問題 if (x = 0) if (y = 0) error(); else z = x + y; f(&z); else與最近的if配對。除非用括號進行劃分區域。 第3章 “語

11、義”陷阱 3.1 指針和數組 C語言中的數組值得注意的地方有以下兩點: 1.C語言中只有一維數組,而且數組的大小必須在編譯期就作為一個常數確定下來。然而,C語言中數組的元素可以是任何類型的對象,當然也就可以是另外一個數組。 注:C99標準允許變長數組VLA。GCC編譯器中實現了變長數組,但細節與C99標準不完全一致。 2.對于一個數組,我們只能夠做兩件事:確定該數組的大小,以及獲得指向該數組下標為0的元素的指針。其他有關數組的操作,哪怕他們看上去是以數組下標進行運算的,實際上都是通過指針進行的。換句話說,任何一個數組下標運算都等同于一個對應的指針運算,因此我們完全可以依據指針行為定義數組下標的

12、行為。 很多程序設計語言中都內建有索引運算,在C語言中索引運算是以指針算術的形式來定義的。 如果一個指針指向的是數組中的一個元素,那么我們只要給這個指針加1,就能夠得到指向該數組中下一個元素的指針。同樣地,如果我們給這個指針減1,得到就是指向該數組中前一個元素的指針。 int calendar1231; int *p; 則p = calendar; 是非法的。因為calendar是一個二維數組,即“數組的數組”,在此處的上下文中使用calendar名稱會將其轉換為一個指向數組的指針;而p是一個指向整型變量的指針,這個語句試圖將一種類型的指針賦值給另一種類型的指針。 要構造一個指向數組的指針的方

13、法: int calendar1231; int (*monthp)31; monthp = calendar; 這樣,monthp將指向數組calendar的第一個元素,也就是數組calendar的12個有著31個元素的數組類型元素之一。 3.2 非數組的指針 在C語言中,字符串常量代表了一塊包括字符串中所有字符以及一個空字符('')的內存區域的地址。 假定我們有兩個字符串s和t,我們希望將這兩個字符串連接成單個字符串t。 考慮: char *r,*malloc(); r = mallor(strlen(s) + strlen(t); strcpy(r,s); strcat(

14、r,t); 這個例子的錯誤有3點: 1,malloc函數有可能無法提供請求的內存。 2,顯式地分配了內存必須顯式地釋放內存。 3,malloc函數并未分配足夠的內存。 正確是方法: char *r,*malloc(); r = malloc(strlen(s) + strlen(t) + 1); if(!r) complain(); exit(1); strcpy(r,s); strcat(r,t); /*一段時間之后*/ free(r); 3.3 作為參數的數組聲明 在C語言中,我們沒有方法可以將一個數組作為函數參數直接傳遞。如果我們使用數組名作為參數,那么數組名會立刻被轉換為指向該數組第1

15、個元素的指針。 因此,將數組作為函數參數毫無意義。所以,C語言中會自動地將作為參數的數組聲明轉換為相應的指針聲明。 3.4 防止“舉隅法” 需要記住的是,復制指針并不同時復制指針所指向的數據。 3.5 空指針并非空字符串 出了一個重要的例外情況,在C語言中將一個整型轉換為一個指針,最后得到的結果都取決于具體的C編譯器實現。這個特殊的情況就是常數0,編譯器保證由0轉換而來的指針不等于任何有效的指針。 #define Null 0 需要記住的重要一點是,當常數0被轉換為指針使用時,這個指針絕對不能被解除引用(dereference)。 換句話說,當我們將0賦值給一個指針變量時,絕對不能企圖使用該指

16、針所指向的內存中存儲的內容。 3.6 邊界計算與不對稱邊界 在所有常見的程序設計錯誤中,最難于發覺的一類是“欄桿錯誤”,也常被稱為“差一錯誤”(off-by-one error)。 防止“欄桿錯誤”的兩個通用原則: 1 首先考慮最簡單情況下的特例,然后將得到的結果外推。 2 仔細計算邊界,絕不掉以輕心。 用第一個入界點和第一個出界點來表示一個數值范圍 能夠降低這類錯誤發生的可能性。 比方整數x滿足邊界條件x>=16且x<=37我們可以說x>=16且x<38,這里下界是“入界點”,即包括在取值范圍之中;而上界是“出界點”,即不包括在取值范圍之中。 另一種考慮不對稱邊界的方

17、式是,把上界視作某序列中第一個被占用的元素,而把下界視作序列中第一個被釋放的元素。 3.7 求值順序 C語言中只有四個運算符(&&, |, ?: 和 ,)存在規定的求值順序。運算符&&和運算符|首先對左側操作數求值,只在需要時才對右側操作數求值。運算符?:有三個操作數: 在a?b:c中,操作數a首先被求值,根據a的值首先被求值,根據a的值再求操作數b或c的值。而逗號運算符,首先對左側操作數求值,然后該值被“丟棄”,再對右側操作數求值。 3.8 運算符&&, | 和 ! 運算符&和運算符&&不同,運算符&兩側的操作數

18、必須被求值。 3.9 整數溢出 C語言中存在兩類整數算術運算,有符號運算與無符號運算。在無符號算術運算中,沒有所謂“溢出”一說:所有的無符號運算都是以2的n次方為模,這里n是結果中的位數。如果算術運算符的一個操作數是有符號整數,另一個是無符號整數,那么有符號整數會被轉換為無符號整數,“溢出”也不可能發生。當兩個操作數都是有符號整數時,“溢出”有可能發生。當一個運算的結果發生“溢出”時,作出任何假設都是不安全的。 3.10 為函數main提供返回值 一個返回值為整型的函數如果返回失敗,實際上是隱含地返回了某個“垃圾”整數。只要該數值不被用到,就無關緊要。 第4章 連接4.1 什么事連接器1-1

19、典型的連接器把有編譯器或匯編器生成的假設干個目標模塊,整合成為一個載入模塊或可執行文件的實體,該實體能被操作系統直接執行。其中某些目標模塊是直接作為輸入,某些事從庫文件中取得提供應連接器的。1-2連接器是獨立于C實現的, C中要提供lint程序捕獲連接器中與C有關的錯誤,一定要使用。1-3 程序中,每個外部變量未被聲明為static就是一個外部對象。4.2 聲明與定義2-1 int a要在所有函數體之外,它被稱為外部對象a的定義,并為a分配存儲空間,初始化為0。extern int a,不是對a的定義,顯式的說明a的存儲空間是其他地方分配的。從連接器角度看,是對外部對象的顯式引用,其他地方必須

20、存在語句int a這個外部變量。嚴格的說每個外部變量只能夠定義一次。如重復定義可能顯式程序錯誤或幾個源文件中共享a的一個實例。4.3 命名沖突與static修飾符3-1 static 把定義的變量和函數作用域限制在一個源文件中,對于其他源文件不可見。4.4 形參、實參與返回值4-1 任何C函數都有一個形參列表,列表中每個參數都是一個變量。調用函數時將實參列表傳遞給被調用函數。對于某些函數形參為空,被調用時實參列表也為空。4-2 任何C函數都用返回類型,要么是void,要么是函數生成結果的類型。main函數返回值是來告訴操作系統該函數執行的是成功還是失敗典型 0 代表成功,1代表失敗,如main

21、函數并無返回值,那么有可能看上去執行失敗或得到令人驚訝的結果。4-3 一個函數在調用之前要進行定義或聲明。否則它的返回值類型就默認為整形。4-4 一個函數只有在沒有float、short、char類型的參數時,才可以在函數聲明中省略參數類型的說明。這樣同樣依賴于調用者能提供數目正確的實參。4-5 printf 、scanf 在不同情況下可接受不同類型的參數,特別容易出錯。 例:#include <stidio.h> Main()int i;char c;for (i=0;i<5;i+)scanf(“%d”,&c);printf(“%d”,i);程序中scanf要求讀入

22、一個整數,傳遞給它一個指向整型的指針,而c卻被聲明成char型,得到的卻是一個指向字符的指針。Scanf不能分辨這種情況,它會在指向字符的指針所指位置存儲一個整數,這將把字符c附近的內存由編譯器決定被覆蓋。4.5 檢查外部類型5-1保證一個特定名稱的所有外部定義在每個目標模塊中都有相同的類型。5-2一個未聲明的標識符后跟一個開括號,那它將被視為一個返回整型的函數。例maindouble s; s=sqrt2; printf”%g/n”,s 根據上下語句aqrt函數返回 雙精度類型,但調用前無聲明默認返回值為整形,程序的結果將不可預測。4.6 頭文件6-1 為防止聲明類型的沖突及忘記聲明,可每個

23、外部變量只在一個地方進行聲明,這個地方就是頭文件中。6-2 BIG ENDIAN:最低位地址存放高位字節,可稱高位優先,內存從最低地址開始按順序存放高數位數字先寫。最高位字節放最前面。 LITTLE ENDIAN:最低位地址存放低位字節,可稱低位優先,內存從最低地址開始按順序存放低數位數字先寫。最低位字節放最前面。第5 章 庫函數 盡量使用系統頭文件。如庫文件的編寫者已經提供了精確描述庫函數的頭文件,不去使用它們就真是愚不可及。5.1 返回整數的getchar函數1-1 EOF是在stdio.h中被定義的值,不同于任何一個字符。當標準輸入文件中沒有輸入時,返回EOF,類型為整型,防止與某些合法

24、輸入字符被“截斷”后的取值相同,導致程序在文件復制的中途終止及防止取不到EOF這個值導致程序陷入死循環。5.2 更新順序文件'r' 開文件方式為只讀,文件指針指到開始處。  'r+' 開文件方式為可讀寫,文件指針指到開始處。  'w' 開文件方式為寫入,文件指針指到開始處,并將原文件的長度設為 0。假設文件不存在,則建立新文件。  'w+' 開文件方式為可讀寫,文件指針指到開始處,并將原文件的長度設為 0。假設文件不存在,則建立新文件。  'a' 開文件方式為寫入,文件指針指

25、到文件最后。假設文件不存在,則建立新文件。  'a+' 開文件方式為可讀寫,文件指針指到文件最后。假設文件不存在,則建立新文件。  'b' 假設操作系統的文字及二進位文件不同,則可以用此參數,UNIX 系統不需要使用本參數。 1-1為了保持與過去不能通史進行寫操作的程序的上下兼容性,一個輸入操作不能隨后直接緊跟一個輸出操作。如果要同步就要用到fseek函數改變文件狀態。即對于一個可寫可讀流,是不能在一次讀之后馬上進行一次寫,或者進行一次寫之后馬上進行一次讀的,這可能會發生問題,需要在兩個操作之間進行至少一次刷新。例:FILE *fp

26、;struct record rec;while( fread( (char *)&rec, sizeof(rec), 1, fp) = 1 ) /* handle rec here*/ if(/*we have to write rec back to file*/) fseek(fp, -(long)sizeof(rec), 1); fwrite( (char*)&rec, sizeof(rec), 1, fp); fseek(fp, 0L, 1); 5.3 緩沖輸出與內存分配1錯誤例程:#include <stdio.h> main()   

27、0; int c;     char bufBUFSIZ;     setbuf(stdout, buf);     while(c = getchar() != EOF)         putchar(c); main函數并不是進程的唯一要執行的代碼,作為庫在將控制交回到操作系統之前所執行的清理的一部分,main結束后進程仍會進行一些與系統有關的清理工作 但char bufBUFSIZ,在main中聲明時,是在棧上的,在main結束時會被收回 ,緩沖區已經被釋放了!所以在進行清理時對buf的

28、操作是違規的。有兩種方法可以防止這一問題。 1、用靜態緩沖區,或者將其顯式地聲明為靜態: static char bufBUFSIZ; 2、將整個聲明移到主函數之外。 3、動態地分配緩沖區并且從不釋放它: char *malloc(); setbuf(stdout, malloc(BUFSIZ); 注意在后一種情況中,不必檢查malloc()的返回值,因為如果它失敗了,會返回一個空指針。而setbuf()可以接受一個空指針作為其第二個參數,這將使得stdout變成非緩沖的。這會運行得很慢,但它是可以運行的。5.4 使用 errno檢查錯誤4-1 錯誤例如:/*調用庫函數*/Iferrno /*

29、處理錯誤*/ 這里沒有對errno進行重新設置, errno可能是前一個執行失敗的庫函數的值。在調用庫函數時,應首先檢測作為錯誤指示的返回值,確定程序執行已經失敗然后再檢查errno來弄清出錯原因。例:/*調用庫函數*/If返回的錯誤值 檢查errno5.5 庫函數 signal5-1使用signal庫函數:捕獲異步事件的一種方式#include <signal.h>signal( signal type, handler function);Signal type:系統頭文件signal.h中定義的某些常量。 Handler function:指定事件發生時,將調用的事件處理函數

30、。但一個信號可能在C程序執行期間的任何時刻上發生。甚至可能出現在某些復雜庫函數如malloc的執行過程中。因此從安全角度考慮,信號函數不應該調用上述類型的庫函數。信號非常復雜棘手,而且具有一些從本質上不可移植的特性。解決這個問題最好采取“守勢”,讓signal處理函數盡可能的簡單,并將它們組織在一起。5-2 有getchar putchar的函數要包含頭文件#include<stdio.h>,否則函數中getchar putchar宏出現的地方將被替換成getchar putchar函數,降低系統效率。第6章 預處理器6.1 不能無視宏定義中的空格1-1 define f xx-1

31、表示 f代表xx-1。6.2 宏并不是函數2-1 宏定義中的括號不是函數調用的意思,它們是預防引起與優先級有關的問題。因此,在宏定義中最好把每個參數都用括號括起來。2-2 錯誤例如效率低而且可能是錯誤的: biggest=x0; i=1; while(i<n) biggest=max(biggest,xi+);初始化數組x0=2; x1=3; x2=1;假設max被定義成宏則上式被預處理為 biggest=(biggest)>xi+)?(biggest);(xi+) 這里執行后比較和取值將進行兩次使xi+的值發生了變化。max宏的每個參數值都可能使用兩次,一次是在兩個參數比較時,一

32、次是作為結果返回時 解決類似問題:1、確保宏中的參數沒有副作用例:biggest=x0;for(i=1;i<n;i+) biggest=max(biggest,xi);2、在max宏之外的地方把每個參數存儲在一個臨時變量中,假設max宏不止一個程序文件,我們應把這些臨時變量聲明為 static,以防止命名沖突。 例:static int max_temp1,max_temp2; #defin emax(p,q)(max_temp1=(p),max_temp2=(q),max_temp1>max_temp2?max_temp1:max_temp2)3、讓max這類作為函數而不是宏。4

33、、直接編寫比較兩者大小的代碼。 例:biggest=x0;for(i=1;i<n;i+) if(xi>biggest) biggest=xi;6.3 宏并不是語句3-1考慮這樣一個例子#define assert(e) if(!(e) assert_error(_FILE_,_LINE_)則對以下代碼if(x>0 && y>0) assert(x>y);else assert(y>x);展開后得到,if(x>0 && y>0) if(!(x>y) assert_error("foo.c",

34、37);else if(!(y>x) assert_error("foo.c",39);注意到,else并不是與第一個if 匹配,這與我們的期望不符。解決方法是,將宏assert定義為一個表達式而不是一個語句:#define assert(e) (void)(e)|_assert_error(_FIL_,_LINE_)上述定義利用了對 | 兩側操作數依次順序求值的性質。6.4 宏并不是類型定義4-1 考慮下面的代碼#define T1 struct foo *typedef struct foo *T2;從兩個定義來看,T1和T2從概念上完全符同,都是指向結構foo的

35、指針。但是T1 a,b;T2 a,b;對于第一個聲明展開后,struct foo *a,b; ,a為指向結構的指針,而b并定義成一個結構,與期望不符。第二個聲明中a b都是指向結構的指針。第7章 可移植性缺陷7.1 應對C語言標準變更7.2 標識符名稱的限制2-1 某些C語言實現:1、把一個標識符中出現的所有字符都作為有效字符處理,另一些實現會自動截斷一個長標識符的尾部。2、標識符區分大小寫,另一種不區分。ANSI中 C實現必須區分前6個字符不同,且不區分大小寫字母。7.3 整數的大小3-1 short型整數肯定能被int型整數容納,int型整數肯定能被long int型整數容納。特定C并不需

36、要支持3中不同長度整數,但可能不會讓short大于int型整數,不會讓int大于long型整數。3-2 一個普通int類型整數足夠大以容納任何數組下標。3-3 字符長度由硬件特性決定。現大多數機器字符長度是8位,也有的是9位,越來越多的C語言字符長度是16位處理諸如日語之類的大字符集。 ANSI標準:long整型長度至少應該是32位,而short和int整型長度至少應該是16位。 可移植性最好的方法就是聲明該變量為long型。但在這種情況下定義一個新的類型更清晰:typedef long tenmil;這樣最壞的情況下只要改動類型定義即可。7.4 字符是有符號整數還是無符號整數4-1 大多數編

37、譯器把字符實現為8位,但并不是所有編譯器都按照同樣的方式來解釋這8位數,在把一個字符轉換成一個較大整數時這個問題需要重視。在其他情況下多余的位將被簡單的“丟棄”。在講char類型轉換成int類型時編譯器:1、將字符作為有符號數:編譯器在將char擴展到int類型時,同時復制符號位。8位字符的取值范圍是-128到1272、將字符作為無符號數:編譯器只需在多余位上填充0即可。8位字符的取值范圍是0到2554-2 如一字符的最高位是1,程序員可將這個字符聲明為無符號字符unsigned char,這樣無論聲明編譯器將該字符轉換為整數時都只需在多余位上填充0。4-3 unsignedc :執行中要先將

38、字符c轉換int型整數,在把int型整數轉換成無符號整數。這樣可能得不到與c等價的無符號整數。解決方式:使用unsigned charc 直接將字符c轉換成無符號整數。7.5 移位運算符5-1 在向右移位時如果被移位對象是無符號數,那么由0填充;如果是有符號數,那么是0或符號位的副本填充。 如果被移位對象的長度是n位,那么移位計數必須大于或等于0,而嚴格小于n。5-2 有符號整數的向右移位運算并不等同于除以2的某次冪。例-1>>1不等于0,而-1/2 等于0。5-3 移位運算符一般比除法代替的移位運算符運行速度要快。7.6 內存位置06-1 null指針并不指向任何對象,除用于賦值

39、或比較運算外,任何情況使用null指針都是非法的。6-2 在所有的C程序中,誤用null指針的效果都是未定義的。然而這樣的程序可能在某個C實現中能工作,只有轉移到另一臺機器上運行時才會暴露出問題來。所以檢查這類問題最簡單方法就是把程序移植到不允許讀取內存位置0的機器上運行。7.7 除法運算時發生的截斷7-1 假定a除以b,商為q,余數為r,那么我們認為的整數除法和余數操作應具備的a、b、q、r之間的關系: 1、q*b+r=a :定義余數關系2、如果我們改變a的正負號,我們希望會改變q的符號,但這不會改變q的絕對值。3、當b>0,我們希望r>0且r<b。在c實際運算中,上述3條

40、不能同時滿足,至少放棄3條中的1條。大多數是放棄第三條,而改為余數與被除數正負號相同。7-2 在程序設計時防止被除數n為負這樣的情形,并且聲明n為無符號數。7.8 隨機數的大小8-1 隨機數返回值:PDP-11和AT&T是 0215-1 ;VAX-11是0231-1這樣我們在程序中用到rand函數,在移植時就必須根據特定的C語言實現做出“剪裁”。 ANSI C標準中定義了一個常數RAND_MAX它的值等于隨機數的最大值。但早期的C實現通常沒有包含這個函數。7.9 大小寫轉換9-1 大多數toupper和tolower的使用都需要首先進行檢查以保證參數時合適的。所以最后AT&T重

41、寫的toupper和tolower函數大致如下例: int topper(int c)if(c>=a&&c<=z) return c+A-a; return c;這樣雖然防止了參數類型不合適的錯誤和宏定義中可能因為帶副總用的表達式多次求值造成的錯誤,但這樣的函數使開銷增大,效率降低。在AT&T的系統上使用toupper和tolower時不必擔憂傳入大小寫不合適的字母,但在其他一些C語言實現上,程序卻可能無法運行。9-2 引入新的宏,讓使用者在速度與方便之間根據程序選擇。#define _toupper(c) (c)+A-a):小寫轉換大寫#define _tolower(c) (c)+a-A):大寫轉換小寫7.10 首先釋放,然

溫馨提示

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

最新文檔

評論

0/150

提交評論