天天看點

有符号數和無符号數的轉換及思考1 有符号數和無符号數的表示2 有符号和無符号數的表示3 陷阱4 擴充、截斷和溢出

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特殊之處在于沒有相應的反碼原碼。也可以這樣考慮:

-1:   1111 1111
-2:   1111 1110(在-1的基礎上減1,直接将補碼減1即可)
-3:   1111 1101(在-2補碼基礎上減1,以下類似)
-4:   1111 1100
……
-127:1000 0001
-128:1000 0000
           

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

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

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

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

在計算機中無符号數用原碼表示, 有符号數用補碼表示

w位補碼表示的值為:

−xw−12w−1+∑w−2i=1xi2i

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

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

0101=−0∗23+1∗22+0∗21+1∗20=5

1101=−1∗23+1∗22+0∗21+1∗20=−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;
}
           
有符号數和無符号數的轉換及思考1 有符号數和無符号數的表示2 有符号和無符号數的表示3 陷阱4 擴充、截斷和溢出

上面的函數看起來似乎沒什麼問題, 實際上當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;
}
           
有符号數和無符号數的轉換及思考1 有符号數和無符号數的表示2 有符号和無符号數的表示3 陷阱4 擴充、截斷和溢出

如将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),産生溢出。

繼續閱讀