對C語言中無符號型別的建議

rexnie發表於2018-05-06

算術型別轉換

首先給出一段C程式碼:

int main(void)
{
	if (-1 < (unsigned char)1)
		printf("true, ANSI C semantics\n");
	else
		printf("false, K&R C semantics\n");
	return 0;
}
複製程式碼

這段程式分別在ANSI C和K&R C編譯器下編譯的話,執行的結果是不同的。-1的位模式是一樣的,但ANSI C編譯器將它解釋為有符號數,是個負數。K&R C編譯器卻將它解釋為無符號數,變成了一個非常大都正數。這是因為兩個標準在做算術型別轉換時使用的規則不同導致的。

在《The C Programming Language》A.6算術型別轉換 章節詳細描述了ANSI C的轉換規則。 許多運算子都會以類似的方式在運算過程中引起轉換,併產生結果型別。其效果是將所有運算元轉換為同一公共型別,並以此作為結果的型別。這種方式的轉換稱為普通算術型別轉換。

首先,如果任何一個運算元為long double型別,則將另一個運算元轉換為long double型別。

否則,如果任何一個運算元為double型別,則將另一個運算元轉換為double型別。

否則,如果任何一個運算元為float型別,則將另一個運算元轉換為float型別。

否則,同時對兩個運算元進行整形提升(integral promotion)。所謂整形提升,就是在一個表示式中,char,short,int型位域(bit-field),包括它們的有符號型別或者無符號型別,以及列舉型別等這些型別都需要提升為int或者unsigned int型別。如果原始型別的所有值都可用int型別表示,則其值將被轉換為int類;否則將被轉換為unsigned int型別。

整形提升之後,如果任何一個運算元為unsigned long int型別,則將另一個運算元轉換為unsigned long int型別。

否則,如果任何一個運算元為long int型別且另一個運算元為unsigned int型別,則結果依賴於long int型別是否可以表示所有的unsigned int型別的值。如果可以,則將unsigned int型別的運算元轉換為long int型別。如果不可以,則將兩個運算元都轉換為unsigned long int型別。

否則,如果任何一個運算元為long int型別,則將另一個運算元轉換為long int型別。

否則,如果任何一個運算元為unsigned int型別,則將另一個運算元轉換為unsigned int型別。

否則,將兩個運算元都轉換為int型別。

所以ANSI C採用的是值保留(value preversing)原則,儘量保證不會發生溢位。而K&R C的規則比較簡單,採用的是無符號保留(unsigned preversing)原則。就是當一個無符號型別與int或更小的整形混合使用時,結果型別是無符號型別,這個規則簡單,與硬體無關,但是有時會使一個負數丟失符號位。

結合上例,在ANSI C編譯器下,unsigned char的1被提升為int型別,所以if條件為真。但是在K&R C編譯器下,由於<操作符右邊是unsigned char型別,所以<操作符左邊會轉化為unsigned int,結果是unsigned int,所以if條件為假。

這裡提到K&R C標準,是為了說明在說明ANSI C型別轉換時,有個對比。在實際專案中,應該都遵從ANSI C的規範,這樣程式碼可最大限度地保證程式的可移植性。

給出另一段C程式碼:

int array[] = { 23, 34, 12,
	17, 204, 99, 16
};

#define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))

int main(void)
{
	int d = -1, x = 0;

	if (d <= TOTAL_ELEMENTS - 2)
		x = array[d+1];

	printf("x=%d\n", x);
	return 0;
}
複製程式碼

根據標準C的規則,因為sizeof是unsigned型別,所以<=操作符左邊會轉化為unsigned,因此x=0.需要修正該問題,可以做如下改動:

if (d <= (int)TOTAL_ELEMENTS - 2)
		x = array[d+1];
複製程式碼

關於無符號型別的建議

關於無符號型別的建議:

儘量不要在你的程式碼中使用無符號型別,以免增加不必要的複雜性。尤其是,不要僅僅因為無符號數不存在負值(如年齡,國債)而用它來表示數量。

儘量使用int那樣的有符號型別,這樣在涉及型別轉換時,不必邊界問題。

只有在使用bit-field和二進位制掩碼時,才使用無符號數。應該在表示式中使用強制型別轉換,使運算元均為有符號數或者無符號數,這樣不必由編譯器來選擇結果的型別。

關於ANSI C的整形提升的補充

C語言中的型別轉換比一般人想象中的要廣泛的多。看個例子:

printf("%d %d", sizeof('A'), sizeof(char));
複製程式碼

這行程式碼列印出儲存一個字元型別的長度。結果都是1嗎?

實際的結果是4(32bit機器上)和1。字元常量'A'是char型別,由於sizeof('A')作為printf的引數,函式的引數也算是表示式,所以發生了轉型提升。

再看個例項:

char c1, c2;
...
c1 = c1 + c2;
複製程式碼

這裡c1和c2都需要先進行整形提升,即轉化為int,然後兩個int值相加,最後對和的結果進行裁剪。

參考:

  1. 《The C Programming Language中文版(第2版.新版)》
  2. 《C專家程式設計》

相關文章