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 int,long int,還可以是 long long int。其中 short int表示所佔記憶體比 int 小的資料型別,而 long int 表示所佔記憶體比int 大的資料型別。
在 C 語言並沒有限制 int 的大小,更沒有限制 short int 等帶限定符的資料型別的大小,只是規定了
short int<=int<=long int<=
long long int
注意哦,是小於等於,不是小於哦!
signed 和 unsigned
還有一對型別限定符是 signed 和 unsigned,它們用於限定 char 型別和任何 int 型別變數的取值範圍。signed 表示該變數是帶符號位的 (可以表示負數),而 unsigned 表示不帶符號位 (只能表示正數)。預設所有的整型變數都是 signed 的,也就是帶符號位的。
對於 int 型別的變數來說,有四種表示長度的限定符(除int本身外,還有 short,long 和 long 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
的要求;通過最後兩行可以看出,對於同一種資料型別,signed 和 unsigned只是最高位bit的意義,資料長度不會被改變的。
但是我們如果強制將無符號數賦值為負數呢?程式碼如下
輸出的結果如下圖所示
我們可以看到無符號數 b果然沒有輸出對應的 -1 ,但是為什麼輸出 65535 呢?這就與資料型別的取值範圍有關了。
2
取值範圍
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 編譯執行可以使用下面這條命令
其中的 lm 表示表式我們使用了<math.h> 這個標頭檔案,&&省略了原本的 -o 的操作,此時生成的可執行檔名為 a.out,通過上面的語句進行編譯執行得到如下的結果
可以看到 gcc 給出了 Warning 中指出了常量轉換溢位(overflow),然後我們可以驗證一下上面給出的結果是否正確。通過計算器可以知道 2^32 -1 的正確結果是 4294967295,與上面給出的結果不符。
出現這個的問題在於,在預設情況下 int 為有符號型,所以第一位是符號位,不能用來存放數字,所以如果我們將 32 位都拿來存放數字很容易溢位的現象。那如何進行修改呢?肯定不是修改一處啦,想好了也可以留言回覆哦~
偷偷告訴你們,點選 閱讀原文 可以看到參考答案哦~
3
參考
[1] “小甲魚” 視訊課程《帶你學C帶你飛》【第一季】P6 P7
原文釋出時間為:2018-10-09
原文作者: 獨孤呆博