這里通過分析一個練習題來總結:
考慮下列代碼,這段代碼試圖計算陣列a中所有元素的和,其中元素的數量由引數length給出,
/* WARNING: This is buggy code */
float sum_elements(float a[], unsigned length) {
int i;
float result = 0;
for (i = 0; i <= length - 1; i++)
result += a[j];
return result;
}
當引數length等于0時,運行這段代碼應該回傳0.0,但實際上,運行時會遇到一個記憶體錯誤,請解釋為什么會發生這樣的情況,并且說明如何修改代碼,
解:
首先我們發現引數length的形式引數的型別為unsigned,是一個無符號數,而無符號數是非負的,比如一個位元組可以表示的無符號數范圍是0~255,在代碼中如果引數length等于0,則在回圈中會首先對length減1來判斷,而這個結果并不是一個負數,而是一個很大的正數,舉個例子,對于一個位元組,我們這里用十六進制來表示方便一點,0的十六進制為0x00,而0 - 1會得到0xFF,這個數表示為無符號數的大小為255,也就是一個位元組所能表示的最大無符號數,這就產生了錯誤,但是如果我們用有符號數來解讀0xFF,此時這個數的大小為-1,就正確了,所以我們的改正方法是將length宣告為int型別,
在C語言中,我們通過unsigned來宣告一個變數為無符號數,使用int來宣告有符號數,在計算機中使用補碼來表示有符號數,
在計算的時候,無符號數很容易,比如對于0101,它的無符號數大小為:0 * 2^3 + 1 * 2^2 + 0 * 2^1 + 1 * 2^0 = 5,很符合直覺,按照2的冪次計算然后都加起來即可;
而對于有符號數,使用補碼來表示,對于一個w位數的補碼,它的最高有效位稱為符號位,1代表負數,0代表正數,而且計算方法也不是將2的冪次都加起來,舉個例子,對于0101,它的補碼大小為:-0 * 2^3 + 1 * 2^2 + 0 * 2^1 + 1 * 2^0 = 5,很明顯這是一個正數,因為最高位是0,而對于1011,它的補碼大小為:-1 * 2^3 + 0 * 2^2 + 1 * 2^1 + 1 * 2^0 = -5,我們可以發現在計算補碼的時候,要算上符號位,如果是0,那么結果和無符號是一樣的,如果是1,則還要加上一個負的2的冪次,希望上面兩個例子能很清楚說明有符號和無符號數之間的區別和相似之處,不要混淆兩者,它們只是對一個二進制01序列的兩種不同的解讀方法,比如上面的1011,在無符號中,它的大小就是:1 * 2^3 + 0 * 2^2 + 1 * 2^1 + 1 * 2^0 = 11,
但就是因為兩種不同的解讀方法,有時候我們在使用的時候就會出現一些反直覺的錯誤,這就是由于我們對計算機中的表示不了解的后果,特別是一些隱式轉換,比如一個有符號數和一個無符號數運算,則有符號數會自動轉換為無符號數,那么如果這個有符號數剛好是一個負數,當它轉換為無符號數時,是不是就變成了一個較大的無符號正數,比如:-1 < 0U,這里U表示0是一個無符號數,這個運算式的結果是0,這很違反我們平時學習的數學,為什么-1小于0是假的呢?這就是因為-1自動轉換為無符號數了,我們這里還是取一個位元組,-1的補碼表示為1111,0的無符號數表示為0000,但是當-1轉換為無符號數后,1111就表示15,是一個較大的正數,所以15 < 0是假的,這里涉及到補碼和無符號數之間的轉換,有便捷的計算公式,我這里就不說了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/21187.html
標籤:其他
下一篇:《圖解HTTP》筆記
