算術型別轉換
首先給出一段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值相加,最後對和的結果進行裁剪。
參考:
- 《The C Programming Language中文版(第2版.新版)》
- 《C專家程式設計》