C 語(yǔ)言給程序員提供了相當大的自由度并允許不同數值類(lèi)型可以自動(dòng)轉換。由于某些功能
性的原因可以引入顯式的強制轉換,例如:
1. 用以改變類(lèi)型使得后續的數值操作可以進(jìn)行
2. 用以截取數值
3. 出于清晰的角度,用以執行顯式的類(lèi)型轉換
為了代碼清晰的目的而插入的強制轉換通常是有用的,但如果過(guò)多使用就會(huì )導致程序的
可讀性下降。正如下面所描述的,一些隱式轉換是可以安全地忽略的,而另一些則不能。
存在三種隱式轉換的類(lèi)別需要加以區分。
1. 整數提升(Integralpromotion)轉換:
整數提升描述了一個(gè)過(guò)程,借此過(guò)程數值操作總是在 int 或long (signed 或unsigned )整型操作數上進(jìn)行。其他整型操作數(char 、short 、bit-field和enum)在數值操作前總是先轉化為int 或unsigned int 類(lèi)型。這些類(lèi)型稱(chēng)為 small integer 類(lèi)型。
整數提升的規則命令,在大多數數值操作中,如果int 類(lèi)型能夠代表原來(lái)類(lèi)型的所有值,
那么small integer 類(lèi)型的操作數要被轉化為 int 類(lèi)型;否則就被轉化為 unsigned int 。
注意,整數提升:
1) 僅僅應用在smallinteger 類(lèi)型上
2) 應用在一元、二元和三元操作數上
3) 不能用在邏輯操作符(&&、|| 、!)的操作數上
4) 用在switch 語(yǔ)句的控制表達式上
整數提升經(jīng)常和操作數的“平衡”(balancing ,后面提到)發(fā)生混淆。事實(shí)上,整數提升發(fā)生在一元操作的過(guò)程中,如果二元操作的兩個(gè)操作數是同樣類(lèi)型的,那么也可以發(fā)生在二元操作之上。由于整數提升,兩個(gè)類(lèi)型為unsigned short 的對象相加的結果總是 signed int 或unsigned int類(lèi)型的;事實(shí)上,加法是在后面兩種類(lèi)型上執行的。因此對于這樣的操作,就有可能獲得一個(gè)其值超出了原始操作數類(lèi)型大小的結果。例如,如果int 類(lèi)型的大小是32位,那么就能夠把兩個(gè)short(16位)類(lèi)型的對象相乘并獲得一個(gè) 32位的結果,而沒(méi)有溢出的危險。另一方面,如果int類(lèi)型僅是16位,那么兩個(gè)16位對象的乘積將只能產(chǎn)生一個(gè) 16位的結果,同時(shí)必須對操作數的大小給出適當的限制。
2. 賦值轉換:
賦值轉換發(fā)生在:
1) 賦值表達式的類(lèi)型被轉化成賦值對象的類(lèi)型時(shí)
2) 初始化表達式的類(lèi)型被轉化成初始化對象的類(lèi)型時(shí)
3) 函數調用參數的類(lèi)型被轉化成函數原型中聲明的形式參數的類(lèi)型時(shí)
4) 返回語(yǔ)句中用到的表達式的類(lèi)型被轉化成函數原型中聲明的函數類(lèi)型時(shí)
5) switch-case 標簽中的常量表達式的類(lèi)型被轉化成控制表達式的提升類(lèi)型時(shí)。這個(gè)轉換僅用于比較的目的。
每種情況中,必要時(shí)數值表達式的值是無(wú)條件轉換到其他類(lèi)型的。
3. 平衡轉換(Balancingconversions):
平衡轉換的描述是在ISO C[2] 標準中的“UsualArithmetic Conversions”條目下。這套規則提供一個(gè)機制,當二元操作符的兩個(gè)操作數要平衡為一個(gè)通用類(lèi)型時(shí)或三元操作符(? : )的第二、第三個(gè)操作數要平衡為一個(gè)通用類(lèi)型時(shí),產(chǎn)生一個(gè)通用類(lèi)型。平衡轉換總是涉及到兩個(gè)不同類(lèi)型的操作數;其中一個(gè)、有時(shí)是兩個(gè)需要進(jìn)行隱式轉換。整數提升(上面描述的)的過(guò)程使得平衡轉換規則變得復雜起來(lái),在整數提升時(shí),small integer 類(lèi)型的操作數首先要提升到int 或unsigned int 類(lèi)型。整數提升是常見(jiàn)的數值轉換,即使兩個(gè)操作數的類(lèi)型一致。
與平衡轉換明顯相關(guān)的操作符是:
1) 乘除 *、/ 、%
2) 加減 +、-
3) 位操作 &、^、|
4) 條件操作符 (… ? … : …)
5) 關(guān)系操作符 >、>=、< 、<=
6) 等值操作符 == 、!=
其中大部分操作符產(chǎn)生的結果類(lèi)型是由平衡過(guò)程產(chǎn)生的,除了關(guān)系和等值操作符,它們
產(chǎn)生具有int 類(lèi)型的布爾值。 要注意的是,位移操作符(<<和>>)的操作數不進(jìn)行平衡,運算結果被提升為第一個(gè)操作數的類(lèi)型;第二個(gè)操作數可以是任何有符號或無(wú)符號的整型。
類(lèi)型轉換過(guò)程中存在大量潛在的危險需要加以避免:
1) 數值的丟失:轉化后的類(lèi)型其數值量級不能被體現
2) 符號的丟失:從有符號類(lèi)型轉換為無(wú)符號類(lèi)型會(huì )導致符號的丟失
3) 精度的丟失:從浮點(diǎn)類(lèi)型轉換為整型會(huì )導致精度的丟失
對于所有數據和所有可能的兼容性實(shí)現來(lái)說(shuō),唯一可以確保為安全的類(lèi)型轉換是:
1) 整數值進(jìn)行帶符號的轉換到更寬類(lèi)型
2) 浮點(diǎn)類(lèi)型轉換到更寬的浮點(diǎn)類(lèi)型
當然,在實(shí)踐中,如果假定了典型類(lèi)型的大小,也能夠把其他類(lèi)型轉換歸類(lèi)為安全的。
普遍來(lái)說(shuō),采取的原則是,利用顯式的轉換來(lái)辨識潛藏的危險類(lèi)型轉換。
類(lèi)型轉換中還有其他的一些危險需要認清。這些問(wèn)題產(chǎn)生于C 語(yǔ)言的難度和誤解,而不
是由于數據值不能保留。
1. 整數提升中的類(lèi)型放寬:整數表達式運算的類(lèi)型依賴(lài)于經(jīng)過(guò)整數提升后的操作數的類(lèi)型??偸悄軌虬褍蓚€(gè)8 位數據相乘并在有量級需要時(shí)訪(fǎng)問(wèn) 16 位的結果。有時(shí)而不總是能夠把兩個(gè)16 位數相乘并得到一個(gè) 32 位結果。這是 C 語(yǔ)言中比較危險的不一致性,為了避免混淆,安全的做法是不要依賴(lài)由整數提升所提供的類(lèi)型放寬。
考慮如下例子:
INT16U u16a = 40000; /* unsigned short / unsigned int */
INT16U u16b = 30000; /* unsignedshort / unsigned int */
INT32U u32x; /* unsigned int / unsigned long */
u32x = u16a + u16b; /*u32x = 70000 or 4464 ? */
期望的結果是70000,但是賦給 u 的值在實(shí)際中依賴(lài)于 int 實(shí)現的大小。如果 int 實(shí)現的大小是32 位,那么加法就會(huì )在有符號的 32 位數值上運算并且保存下正確的值。如果 int 實(shí)現的大小僅是16 位,那么加法會(huì )在無(wú)符號的 16 位數值上進(jìn)行,于是會(huì )發(fā)生折疊(wraparound )現象并產(chǎn)生值4464(70000%65536 )。無(wú)符號數值的折疊(wraparound)是經(jīng)過(guò)良好定義的甚至是有意的;但也會(huì )存在潛藏的混淆。
2. 類(lèi)型計算的混淆:程序員中常見(jiàn)的概念混亂也會(huì )產(chǎn)生類(lèi)似的問(wèn)題,人們經(jīng)常會(huì )以為參與運算的類(lèi)型在某種方式上受到被賦值或轉換的結果類(lèi)型的影響。例如,在下面的代碼中,兩個(gè)16 位對象進(jìn)行 16 位的加法運算(除非被提升為 32 位int),其結果在賦值時(shí)被轉換為INT32U 類(lèi)型。
u32x = u16a + u16b;
并非少見(jiàn)的是,程序員會(huì )認為此表達式執行的是32 位加法——因為 u32x 的類(lèi)型。
對這種特性的混淆不只局限于整數運算或隱式轉換,下面的例子描述了在某些語(yǔ)句中,
結果是良好定義的但運算并不會(huì )按照程序員設想的那樣進(jìn)行。
u32a = (INT32U_t) (u16a * u16b);
f64a = u16a / u16b ;
f32a = (float32_t) (u16a / u16b) ;
f64a = f32a + f32b ;
f64a = (float64_t) (f32a + f32b) ;
3. 數學(xué)運算中符號的改變:整數提升經(jīng)常會(huì )導致兩個(gè)無(wú)符號的操作數產(chǎn)生一個(gè)(signed )int類(lèi)型的結果。比如,如果 int 是32位的,那么兩個(gè) 16 位無(wú)符號數的加法將產(chǎn)生一個(gè)有符號的32 位結果;而如果 int 是16 位的,那么同樣運算會(huì )產(chǎn)生一個(gè)無(wú)符號的 16 位結果。
4. 位運算中符號的改變:當位運算符應用在無(wú)符號短整型時(shí),整數提升會(huì )有某些特別不利
的反響。比如,在一個(gè)unsigned char 類(lèi)型的操作數上做位補運算通常會(huì )產(chǎn)生其值為負的(signed )int 類(lèi)型結果。在運算之前,操作數被提升為 int 類(lèi)型,并且多出來(lái)的那些高位被補運算置1。那些多余位的個(gè)數,若有的話(huà),依賴(lài)于int 的大小,而且在補運算后接右移運算是危險的。
為了避免上述問(wèn)題產(chǎn)生的危險,重要的是要建立一些準則以限制構建表達式的方式。這里首先給出某些概念的定義。
表達式的類(lèi)型是指其運算結果的類(lèi)型。當兩個(gè) long 類(lèi)型的值相加時(shí),表達式具有 long 類(lèi)
型。大多數數值運算符產(chǎn)生其類(lèi)型依賴(lài)于操作數類(lèi)型的結果。另一方面,某些操作符會(huì )給出
具有int 類(lèi)型的布爾結果而不管其操作數類(lèi)型如何。所以,舉例來(lái)說(shuō),當兩個(gè)long 類(lèi)型的項用系運算符做比較時(shí),該表達式的類(lèi)型為int。 術(shù)語(yǔ)“基本類(lèi)型”的定義是,在不考慮整數提升的作用下描述由計算表達式而得到的類(lèi)型。當兩個(gè)int 類(lèi)型的操作數相加時(shí),結果是 int 類(lèi)型,那么表達式可以說(shuō)具有 int 類(lèi)型。 當兩個(gè)unsigned char 類(lèi)型的數相加時(shí),結果也是 int 類(lèi)型(通常如此,因為整數提升的原因),但是該表達式基本的類(lèi)型按照定義則是unsignedchar 。 術(shù)語(yǔ)“基本類(lèi)型”不是C 語(yǔ)言標準或其他C 語(yǔ)言的文本所認知的,但在描述如下規則時(shí)它很有用。它描述了一個(gè)假想的對C 語(yǔ)言的違背,其中不存在整數提升而且常用的數值轉換一致性地應用于所有的整數類(lèi)型。引進(jìn)這樣的概念是因為整數提升很敏感且有時(shí)是危險的。整數提升是C 語(yǔ)言中不可避免的特性,但是這些規則的意圖是要使整數提升的作用能夠通過(guò)不利用發(fā)生在smallinteger 操作數上的寬度擴展來(lái)中和。
當然,C 標準沒(méi)有顯式地定義在缺乏整數提升時(shí) small integer 類(lèi)型如何平衡為通用類(lèi)型
盡管標準確實(shí)建立了值保留(value-perserving)原則。
當int 類(lèi)型的數相加時(shí),程序員必須要確保運算結果不會(huì )超出 int 類(lèi)型所能體現的值。如
果他沒(méi)有這樣做,就可能會(huì )發(fā)生溢出而結果值是未定義的。這里描述的方法意欲使得在做small integer 類(lèi)型的加法時(shí)使用同樣的原則;程序員要確保兩個(gè)unsigned char 類(lèi)型的數相加的結果是能夠被unsigned char 體現的,即使整數提升會(huì )引起更大類(lèi)型的計算。換句話(huà)說(shuō),對表達式基本類(lèi)型的遵守要多于其真實(shí)類(lèi)型。
整數常量表達式的基本類(lèi)型
C 語(yǔ)言的一個(gè)不利方面是,它不能定義一個(gè) char 或short 類(lèi)型的整型常量。比如,值“5 ”
可以通過(guò)附加的一個(gè)合適的后綴來(lái)表示為int、unsigned int 、long或unsigned long 類(lèi)型的常量;但沒(méi)有合適的后綴用來(lái)創(chuàng )建不同的char 或short 類(lèi)型的常量形式。這為維護表達式中的類(lèi)型一致性提出了困難。如果需要為一個(gè)unsigned char 類(lèi)型的對象賦值,那么或者要承受對一個(gè)整數類(lèi)型的隱式轉換,或者要實(shí)行強制轉換。很多人會(huì )辯稱(chēng)在這種情況下使用強制轉換只能導致可讀性下降。在初始化、函數參數或數值表達式中需要常量時(shí)也會(huì )遇到同樣的問(wèn)題。然而只要遵守強類(lèi)型(strong typing)原則,這個(gè)問(wèn)題就會(huì )比較樂(lè )觀(guān)。
解決該問(wèn)題的一個(gè)方法是,想象整型常量、枚舉常量、字符常量或者整型常量表達式具
有適合其量級的類(lèi)型。這個(gè)目標可以通過(guò)以下方法達到,即延伸基本類(lèi)型的概念到整型常量
上,并想象在可能的情況下數值常量已經(jīng)通過(guò)提升想象中的具有較小基本類(lèi)型的常量而獲得。
這樣,整型常量表達式的基本類(lèi)型就可以如下定義:
1. 如果表達式的真實(shí)類(lèi)型是(signed )int,其基本類(lèi)型就是能夠體現其值的最小的有符
號整型。
2. 如果表達式的真實(shí)類(lèi)型是unsigned int ,其基本類(lèi)型就是能夠體現其值的最小的無(wú)符
號整型。
3. 在所有其他情況下,表達式的基本類(lèi)型與其真實(shí)類(lèi)型相同。
在常規的體系結構中,整型常量表達式的基本類(lèi)型可以根據其量級和符號確定如下:
無(wú)符號值
0U | to | 255U | 8 bit unsigned |
256U | to | 65535U | 16 bit unsigned |
65536U | to | 4294967295U | 32 bit unsigned |
有符號值
-2147483648 | to | -32769 | 32 bit signed |
-32768 | to | -129 | 16 bit signed |
-128 | to | 127 | 8 bit signed |
128 | to | 32767 | 16 bit signed |
32768 | to | 2147483647 | 32 bit signed |
注意,基本類(lèi)型是人造的概念,它不會(huì )以任何方式影響實(shí)際運算的類(lèi)型。這個(gè)概念的提
出只是為了定義一個(gè)在其中可以構建數值表達式的安全框架。
后面章節中描述的類(lèi)型轉換規則在某些地方是針對“復雜表達式”的概念。術(shù)語(yǔ)“復雜
表達式”意味著(zhù)任何不是如下類(lèi)型的表達式:
1) 非常量表達式
2) lvalue (即一個(gè)對象)
3) 函數的返回值
應用在復雜表達式的轉換也要加以限制以避免上面總結出的危險。特別地,表達式中的
數值運算序列需要以相同類(lèi)型進(jìn)行。
以下是復雜表達式:
s8a + s8b
~u16a
u16a >> 2
foo (2) + u8a
*ppc + 1
++u8a
以下不是復雜表達式,盡管某些包含了復雜的子表達式:
pc[u8a]
foo (u8a + u8b)
++ppuc
** (ppc + 1)
pcbuf[s16a * 2]
1) 有符號和無(wú)符號之間沒(méi)有隱式轉換
2) 整型和浮點(diǎn)類(lèi)型之間沒(méi)有隱式轉換
3) 沒(méi)有從寬類(lèi)型向窄類(lèi)型的隱式轉換
4) 函數參數沒(méi)有隱式轉換
5) 函數的返回表達式?jīng)]有隱式轉換
6) 復雜表達式?jīng)]有隱式轉換
限制復雜表達式的隱式轉換的目的,是為了要求在一個(gè)表達式里的數值運算序列中,所有的運算應該準確地以相同的數值類(lèi)型進(jìn)行。注意這并不是說(shuō)表達式中的所有操作數必須具備相同的類(lèi)型。
聯(lián)系客服