1. 程式人生 > >有符號數和無符號數的轉換及思考

有符號數和無符號數的轉換及思考

1 有符號數和無符號數的表示

三者的最高位均為符號位.

我以前一直沒弄明白的是為何8位補碼的表示範圍是-128~127, 今天查閱了相關資料,於此記下。

仍然以8位為例:

原碼

原碼的表示範圍-127~-0, +0~+127, 共256個數字。

正0的原碼是0000 0000, 負0的原碼是1000 0000, 有正0負0之分, 不符合人的習慣, 待解決.

反碼

除符號位, 原碼其餘位取反而得
+0:0000 0000,-0:1111 1111 仍然有正0負0之分。

補碼

在反碼的基礎上加1而得

對原碼的兩種0同時末位加1

+0:0000 0000,-0:0000 0000(因為溢位導致8位全0)

消除了正0負0之別, 如此一來, 便節省出一個數值表示方式1000 0000, 不能浪費, 用來表示-128, -128特殊之處在於沒有相應的反碼原碼。也可以這樣考慮:

-11111 1111
-21111 1110(在-1的基礎上減1,直接將補碼減1即可)
-31111 1101(在-2補碼基礎上減1,以下類似)
-41111 1100
……
-1271000 0001
-1281000 0000

如此以來:8位補碼錶示範圍是-128~+127因為0只有一種形式所以,仍然是256個數

若8位代表無符號數, 則表示範圍是 : 0~255, 這就是為什麼高階語言講到資料型別,

比如C++中的short型別時(16位長)說其表示範圍是:-32768~+32767,而unsigned short表示的範圍則是:0~65535

2 有符號和無符號數的表示


在計算機中無符號數用原碼錶示, 有符號數用補碼錶示

w位補碼錶示的值為:

xw12w1+w2i=1xi2i

(xi為補碼的第i位, i從0開始)

最高位 也稱符號位,1表示負數,0表示正數,符號位為0時,和無符號數的表示是相同的,以下是4位補碼的示例:

0101=023+122+021+120=5

1101=123+122+021+120=3

w位的補碼錶示的數值範圍是[-2w-1, 2w-1-1]

如4位的補碼錶示的最小值是-8(1000), 最大值是7(0111).

只有理解了有符號數的補碼錶示, 才能真正理解無符號數和有符號數的轉換、有符號數的截斷和溢位等問題.

3 陷阱

在C語言中,如果一個運算包含一個有符號數和一個無符號數,那麼C語言會隱式地將有符號數轉換為無符號數,這對於標準的算術運算沒什麼問題,但是對於 < 和 > 這樣的關係運算符來說,它會出現非直觀的結果,這種非直觀的特性經常會導致程式中難以察覺的錯誤

看下面的例子:

int strlonger(char *s, char *t)
{
    return strlen(s) - strlen(t) > 0;
}

strlonger的陷阱

上面的函式看起來似乎沒什麼問題, 實際上當s比t短時,函式的返回值也是1, 為什麼會出現這種情況呢?

原來strlen的返回值型別為size_t,C語言中將size_t定義為unsigned int,當s比t短時,strlen(s) - strlen(t)為負數,但無符號數的運算結果隱式轉換為無符號數就變成了很大的無符號數.

為了讓函式正確工作,程式碼應該修改如下 :

return strlen(s) > strlen(t);

2002年, 從事FreeBSD開源作業系統專案的程式設計師意識到,他們對getpeername函式的實現存在安全漏洞.程式碼的簡化版本如下:

//void *memcpy(void *dest, void *src,  size_t n);

#define KSIZE 1024
char kbuf[KSIZE];


int copy_from_kernel(void *user_dest, int maxlen)
{
    int len = KSIZE < maxlen ? KSIZE : maxlen;
    memcpy(user_dest, kbuf, len);
    retn len;
}

你看出了問題所在嗎?

4 擴充套件、截斷和溢位

4.1 轉換

當資料型別轉換時,同時需要在不同資料大小,以及無符號和有符號之間轉換時,C語言標準要求先進行資料大小的轉換,之後再進行無符號和有符號之間的轉換.

C語言中的強制型別轉換保持二進位制位值不變,只是改變解釋位的方式

看以下程式碼:

short int v = -12345;
unsigned short uv = (unsigned short)v;
printf("v = %d, uv = %u\n”, u, uv);

輸出如下:

v = -12345, uv = 53191

由於-12345的16位補碼錶示與53191的16位無符號表示是完全一樣的,所以會得到以上輸出。

無符號數和有符號數之間的轉換是一一對應的關係,w位的有符號數s轉換無符號數u的對應關係為:

如4位有符號數7(0111)轉換為無符號數也是7,而4位有符號數-1(1111)轉換為無符號數是15。

類似地,w位的無符號數u轉換為有符號數s的對應關係為:

如4位無符號數5(0101)轉換為無符號數也是5,而4位無符號數13(1101)轉換為無符號數為-3。

其實只要知道無符號數和有符號數對二進位制位的解釋方式,無需記住上述的對應關係,也能算出轉換後的值。

4.2 擴充套件

  • 將無符號數轉換為更大的資料型別時, 只需簡單地在開頭新增0,這種運算稱為0擴充套件

  • 將有符號數轉換為更大的資料型別需要執行符號擴充套件,規則是將符號位擴充套件至所需要的位數

同樣對於如下的例子

//  如果是char, 那麼系統認為最高位是符號位, 而int可能是16或者32位, 那麼會對最高位進行擴充套件
signed char c7_1 = 0xff;
//  如果是unsigned char, 那麼不會擴充套件.
unsigned char c7_2 = 0xff;
//  最高位若為0時, 二者沒有區別, 若為1時,則有區別
printf("signed   %08x, %d\n", c7_1, c7_1);
printf("unsigned %08x, %d\n", c7_2, c7_2);

signed 0xffffffff, -1
unsigned 0xff, 255


* 如果是char, 那麼系統認為最高位是符號位, 而int可能是16或者32位, 那麼會對最高位進行擴充套件, 因此0xff在從signed char轉換為unsigned char時認為最高位1是符號位, 進行了擴充套件, 被擴充套件為0xffffffff, 值為-1

  • 如果是unsigned char, 那麼不會擴充套件, 0xff仍然成為0x000000ff, 值為255

最高位若為0時, 二者沒有區別, 若為1時,則有區別

示例程式

#include <stdio.h>
#include <stdlib.h>

///
int main(void)
{
    printf("=======test         -1=======\n");
    //  如果是char, 那麼系統認為最高位是符號位, 而int可能是16或者32位, 那麼會對最高位進行擴充套件
    signed char c7_1 = 0x00ff; //int i7_1 = (int)c7_1;
    //  如果是unsigned char, 那麼不會擴充套件.
    unsigned char c7_2 = 0x00ff; //int i7_2 = (int)c7_2;
    //  最高位若為0時, 二者沒有區別, 若為1時,則有區別
    printf("signed   %08x, %d\n", c7_1, c7_1);
    printf("unsigned %08x, %d\n", c7_2, c7_2);


    printf("\n=======test high bit 1=======\n");
    //  最高位為1
    signed char cc1 = 0x80;
    unsigned char cc2 = 0x80;
    printf("signed   %08x, %d\n", cc1, cc1);
    printf("unsigned %08x, %d\n", cc2, cc2);

    printf("\n=======test high bit 0=======\n");
    //  最高位為0
    signed char cc3 = 0x7f;
    unsigned char cc4 = 0x7f;
    printf("signed   %08x, %d\n", cc3, cc3);
    printf("unsigned %08x, %d\n", cc4, cc4);


    return EXIT_SUCCESS;
}

這裡寫圖片描述

如將4位的二進位制數1001(-7)擴充套件為8位的結果為11111001(-7).

4.3 截斷

將一個大的資料型別轉換為小的資料型別時,不管是無符號數還是有符號數都是簡單地進行位截斷

無符號數的數值大小可能因截斷而變化,而有符號數不僅數值大小可能變化,符號位也可能發生改變,如8位二進位制數00011001(25)轉換為4位數截斷的結果是1001(-7).

4.4 溢位

在進行整數的算術運算時,當結果變數的位數不足以存放實際實際結果的位數時,運算的結果就會因截斷而產生溢位,如果4位二進位制數運算1011(-5) + 1011(-5) = 10110(-10), 但如果結果也採用4位二進位制存放就會截斷為0110(6),產生溢位。