這裏經過分析一個練習題來總結:
考慮下列代碼,這段代碼試圖計算數組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。但實際上,運行時會遇到一個內存錯誤。請解釋爲何會發生這樣的狀況,而且說明如何修改代碼。學習
解:code
首先咱們發現參數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的冪次計算而後都加起來便可;element
而對於有符號數,使用補碼來表示,對於一個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是假的。這裏涉及到補碼和無符號數之間的轉換,有便捷的計算公式,我這裏就不說了。class