C語言 語義陷阱超詳細梳理總結
1 指針與數組
- C語言中只有一維數組。數組中的元素可以是任意類型的對象,這也是多維數組構建的理論基礎所在
- 對于一個數組,我們只能做兩件事:確定該數組的大小以及獲得該數組下標為0的元素的指針。任何一個數組下標運算都等同于一個對應的指針運算。
- 數組名代表首元素的地址,無法對其進行++或者–操作,換句話說,我們無法改變數組名(表示的值),因為數組名是個常量,無法進行修改。
2 非數組的指針
下面有一段程序,指出它的錯誤:
char *r; r = malloc(strlen(s)+strlen(t)); strcpy(r,s); strcat(r,t);
- malloc有可能無法提供請求的內存,這種情況下malloc函數會通過返回一個空指針來作為“內存分配失敗”事件的信號。
- 給r分配的內存在使用完畢后應該及時釋放。
- 前面的例程在調用malloc函數時并未分配足夠的內存,因為字符串還包含結束標志'\0'。
3 作為參數的數組聲明
1.下面列舉的兩種寫法是等價的:
char hello[] = "hello";
printf("%s\n",hello);//寫法1
printf("%s\n",&hello);//寫法2
原因:數組名hello代表數組hello首元素的地址。
2.下面的兩種寫法是等價的:
int strlen(char s[])
{
/*具體內容*/
}
int strlen(char *s)
{
/*具體內容*/
}
注意下面的兩種寫法:
extern char *hello; extern char hello[];
這兩種寫法雖然是都是正確的,但是不同的形式傳遞給我們的意思卻是完全不一致的,我們要根據具體情況進行使用。
4 空指針并非空字符串
注意:空指針不能對其進行解引用。
同時注意不能出現下述寫法:
if(strcmp(p,(char*)0)==0) ···
這種寫法是非法的,原因在于庫函數strcmp的實現中會包括一個操作,用于查看它的指針參數所指向的內容,即對空指針進行了解引用。
也不能出現下述寫法:
假設p是空指針
printf(p);
printf("%s",p);
//當然,這兩種寫法是等價的
這種行為是未定義的。
5 邊界計算與不對稱邊界
在我們寫循環(huán)是最好這樣來寫:
int i = 0; for(i = 0;i < 10; i++) ···
這樣寫能夠更好的看出循環(huán)的次數,即10次。
當數組中有10個元素時,下標的取值范圍為0到9,但是當我們不需要引用這個元素時只需要引用這個元素的地址時,我們可以這樣寫
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
for(int i = 0;&arr[i]<&(arr[10]);i++)
···
這樣可以順利打印出數組元素從1到10的數字,
ANSI C標準明確允許這種用法:數組中實際不存在的"溢界"元素的地址位于數組之外所占內存之后,這個地址可以用于進行賦值和比較。當然,如果要引用該元素,那就是非法的了。對于實際去讀取這個元素的值,這種做法的結果是未定義的,而且極少有編譯器能偶檢測出這個錯誤。當然,如果試圖去修改這個元素,必然會導致程序崩潰,屬于非法訪問了!
6 求值順序
C語言中只有四個運算符(&&、||、?:和,)存在規(guī)定的求值順序。==運算符&&和運算符||首先對左側操作數求值,只有在需要時才對右側操作數求值。==運算符?:有三個操作數:在a?b:c中。操作數a首先被求值,根據a的值再求操作數b或c的值(此時b或c兩個表達式根據前面a表達式的結果只會執(zhí)行一個)。逗號運算符則首先對左側操作數求值,然后"丟棄該值",再對右側操作數求值。
注意:分割函數的參數并非逗號運算符。例如,x和y在函數f(x,y)中的求值順序是未定義的,而在函數g((x,y))中卻是確定的先x后y的循序。在后一個例子中,函數g只有一個參數。這個參數的值是這樣求得的:先對x求值,然后“丟棄”x的值,接著求y的值。
這種求值順序的存在使得某些“錯誤”的程序變?yōu)榱苏_,且在執(zhí)行后得出正確的結果:
if(count!=0 && sum/count < smallaverage) ···
注意:C語言中其它所有的運算符對其操作數求值的順序是未定義的。特別是,賦值運算符并不保證任何求值循序。
例如:下面的這中從數組x中復制前n個元素到數組y中的做法是不正確的,因為它對求值順序做了太多的假設:
i = 0; while(i < n) y[i] = x[i++];
上面的代碼假設y[i]的地址將在i的自增操作指向之前被求值,但這是不一定的,這依賴于編譯器的具體實現。同樣,下面的這種寫法也是不正確的:
i = 0; while(i<n) y[i++] = x[i];
修改成下面這種寫法即可正常工作:
i = 0;
while(i<n)
{
y[i] = x[i];
i++;
}
當然,這種寫法也可以簡寫為:
for(i = 0;i < n;i++) y[i] = x[i];
7 整數溢出
無符號整數不會發(fā)生溢出,這是C語言所規(guī)定的,如果結果大于所能表示的最大值M,則模(M+1),也就是發(fā)生了截斷現象。
兩個有符號整數進行相加時會發(fā)生溢出,而且溢出的結果是未定義的。
下面是一種錯誤的檢查方式:
if(a + b < 0) complain();
因為當a+b卻是發(fā)生溢出時,所有關于結果如何假設都不再可靠。
下面是兩種正確的方式:
//方法一: if((unsigned)a + (unsigned) > INT_MAX) complain(); //方法二: if(a > INT_MAX - b) complain()
8 為函數提供返回值
C語言種常常通過return 返回一個值來告知操作系統的執(zhí)行是成功還是失敗,典型的處理方案是。返回值為0表示程序執(zhí)行成功,返回值為非0則表示程序執(zhí)行失敗。我們常常會在程序的末尾加上return 0操作。
到此這篇關于C語言 語義陷阱超詳細梳理總結的文章就介紹到這了,更多相關C語言 語義陷阱內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++ 中boost::share_ptr智能指針的使用方法
這篇文章主要介紹了C++ 中boost::share_ptr智能指針的使用方法的相關資料,希望通過本文能幫助到大家,需要的朋友可以參考下2017-10-10
Visual?Studio2022的完全卸載及安裝到D盤的操作方法
這篇文章主要介紹了Visual?Studio2022的完全卸載以及完全安裝到D盤,因為VS如果隨便寫在會有很多很多的亂七八糟的東西掉出來,所以我們選擇制式一點的卸載方式,需要的朋友可以參考下2022-09-09
Pthread并發(fā)編程之線程基本元素和狀態(tài)的剖析
本篇文章主要給大家介紹pthread并發(fā)編程當中關于線程的基礎概念,并且深入剖析進程的相關屬性和設置,以及線程在內存當中的布局形式,幫助大家深刻理解線程2022-11-11

