4. C語言 -- 一個由數據類型和取值範圍引發的 BUG

(。・∀・)ノ゙嗨,失蹤人口迴歸啦!!!

之前看到有人留言催更,老夫的心裏的竟然有一絲驚喜和興奮。上週說要改版嘛( 。_ 。) ✎然後我就緊趕慢趕出了這篇稿子,但是由於一些原因,在今天才與大家間面。

之前就有小夥伴留言說建議在上次的內容後面加上數據類型,反碼補碼等知識。我還很是激動的,居然猜出了我今天要講的內容。

首先強調,忘記了上節課內容的同學一定要回顧下哦!在《3. C語言 -- 叫你一聲你敢答應嘛》的 2.3 部分講到 char 字符型,佔用一個字節;而 int 整型,通常反映了所用機器中整數的最自然長度。那一個字節和機器中整數的最自然長度到底是多大的呢?今天就給大家介紹一下 C 語言中的數據類型和取值範圍。

1

數據類型

在 C 語言裏,數據類型即說明了它是什麼類型的數據,也說明了所需的內存的大小,C 語言允許使用的類型如下:

在基本類型中的整數類型、浮點數類型和字符類型在之前介紹過了;其中的_Bool是布爾型,只能取 0 和 1 兩個值;另一個是枚舉類型(enum),這個類型將在後面的部分進行介紹。其餘的數據類型,如指針類型、構造類型和空類型也將在後面的部分進行介紹。

1.1

數據類型的限定符

  • short , long, long long

我們可以爲這些基本數據類型加上一些限定符,比如表示長度的 short long。比如 int 經過限定符修飾之後,可以是 short intlong int,還可以是 long long int。其中 short int表示所佔內存比 int 小的數據類型,而 long int 表示所佔內存比int 大的數據類型。

在 C 語言並沒有限制 int 的大小,更沒有限制 short int 等帶限定符的數據類型的大小,只是規定了

short int<=int<=long int<=long long int

注意哦,是小於等於,不是小於哦!

  • signed 和 unsigned

還有一對類型限定符是 signedunsigned,它們用於限定 char 類型和任何 int 類型變量的取值範圍。signed 表示該變量是帶符號位的 (可以表示負數),而 unsigned 表示不帶符號位 (只能表示正數)。默認所有的整型變量都是 signed 的,也就是帶符號位的。

對於 int 類型的變量來說,有四種表示長度的限定符(除int本身外,還有 shortlonglong long),再加上符號位的限定signed unsigned,所以一共存在着 8 種int 類型的變量。

1.2

sizeof 運算符

sizeof 用於獲得數據類型或表達式的長度,它有三種使用方式:

  • sizeof(type_name); //sizeof(類型) 即某一種類型的變量所佔內存大小;
  • sizeof(object); //sizeof(對象) 即某一個對象所佔內存大小;
  • sizeof object; //sizeof 對象 查看對象佔用內存大小的另一種表達方式;

1.3 舉例說明

下面的程序將使用sizeof輸出每一種數據類型或者每一個變量在內存中所佔的大小,具體地是使用8 種 int 類型的變量進行說明。

在 64 位的 Ubuntu 使用 gcc 編譯執行上面的代碼可以看到如下的結果

如上圖所示,有許多的 Warning,根據提示可知,這是由於sizeof返回的是一個long unsigned int的變量,所以使用 %d作爲佔位符有可能溢出,修改方法是將上面的%d改爲%ld。

分析輸出的結果,通過第 1 行和第 2 行輸出可以看出對於某一種數據類的變量,變量和數據類型的大小是相同的,這是很顯然的;其次通過第 3 行到第 6 行可以看到,數據類型的長度滿足上面的不等式

short int <= int <= long int <=long long int

的要求;通過最後兩行可以看出,對於同一種數據類型,signedunsigned 只是最高位bit的意義,數據長度不會被改變的。

但是我們如果強制將無符號數賦值爲負數呢?代碼如下

輸出的結果如下圖所示

我們可以看到無符號數 b果然沒有輸出對應的 -1 ,但是爲什麼輸出 65535 呢?這就與數據類型的取值範圍有關了。

取值範圍

2.1

比特與字節

CPU能讀懂的最小單位是比特位,記爲bit,b,只能取 0 1 兩個數字;內存機構的最小尋址單位是字節,記爲Byte,B。如下圖所示,爲字節和比特之間的關係

因此一個字節所能存儲的最大數字是二進制的11111111。那這個二進制的數字對應十進制的數字是多少呢?是不是 255 呢?你可以先思考一下再看下面的內容~

2.2 符號位

對於的 11111111,如果它對應一個無符號變量,那麼其表示十進制的數字255(即 2^8 -1=255)。但是對於存放signed類型的數據,左邊第一位表示符號位。符號位爲0,表示正整數;爲1,表示負整數。一個8位的整型變量,除去左邊第一位符號位,剩下表示值的只有7個比特位。

事實上計算機是用補碼的形式來存放整數的值,其中正數的補碼是該數的二進制形式,而負數的補碼需要通過以下幾步獲得:

  • 先取得該數的絕對值的二進制形式,符號位置爲1;
  • 符號位不變,將第1步的值按位取反(即將 0 都變爲 1,1 都變爲 0);
  • 符號位不變,最後將第2步的值加1。

如下圖爲正數 7 和負數 -7 的補碼

一個字節的有符號數的取值範圍如下圖所示

其中我們可以看到負數最高可以到 -128,而正數最高只能到127,這是爲什麼呢?主要因爲 0 也佔據了整數中的一部分,所以導致正數最高只能到127。

那聰明的你現在一定知道將無符號整型賦值爲-1,打印輸出卻是 65535 的原因了吧~如果知道的話可以留言回覆哦~

2.3

基本數據類型的取值範圍

基本數據類型的取值範圍如下面的兩張圖所示,一張圖主要是字符型和整數型,另一張圖主要是小數型。

2.4 舉例說明

下面是一個通過 “計算指數值” 的程序來說明取值範圍這一概念,如下所示

在Ubuntu16.04下面使用 gcc 編譯執行可以使用下面這條命令

gcc -lm tmp.c && ./a.out

其中的 lm 表示表式我們使用了<math.h> 這個頭文件,&&省略了原本的 -o 的操作,此時生成的可執行文件名爲 a.out,通過上面的語句進行編譯執行得到如下的結果

可以看到 gcc 給出了 Warning 中指出了常量轉換溢出(overflow),然後我們可以驗證一下上面給出的結果是否正確。通過計算器可以知道 2^32 -1 的正確結果是 4294967295,與上面給出的結果不符。

出現這個的問題在於,在默認情況下 int 爲有符號型,所以第一位是符號位,不能用來存放數字,所以如果我們將 32 位都拿來存放數字很容易溢出的現象。那如何進行修改呢?肯定不是修改一處啦,想好了也可以留言回覆哦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章