在Go(我最熟悉的語言)中,數學運算的結果總是與運算元的資料型別相同,這意味著如果運算溢位,結果將不正確。例如:
func main() {
var a byte = 100
var b byte=9
var r byte = (a << b) >> b
fmt.Println(r)
}
這就列印出了0,因為在最初的<<9操作中,所有的位元都被移出了位元組的范圍,然后在>>9操作中,零又被移了進去。
然而,在C語言中不是這樣的:
int main() {
unsigned char a = 100;
unsigned char b = 9;
unsigned char r = (a << b) >> b。
printf("%d
", r)。)
return 0。
}
這段代碼列印出100。盡管這產生了 "正確 "的結果,但這出乎我的意料,因為我只期望在其中一個運算元大于一個位元組的情況下才會有晉升,但在這種情況下,所有運算元都是位元組。這就好像持有<<9操作結果的臨時變數比結果變數大,并且只有在完整的RHS被評估后才會被下移回位元組,因此在>>9操作恢復位元后。
顯然,如果在繼續之前明確地將>>9的結果存盤到一個位元組中,你會得到與Go中相同的結果:
int main() {
unsigned char a = 100;
unsigned char b = 9;
unsigned char c = a << b。
unsigned char r = c > > b;
printf("%d
", r)。)
return 0。
}
這不僅僅是位運算子的情況。我也用乘法/除法進行了測驗,它顯示了同樣的行為。
我的問題是:C的這種行為是否被定義了?如果是的話,在哪里?它是否真的為復雜運算式的中間值使用了一種特定的資料型別?或者這實際上是未定義的行為,就像在保存回記憶體之前在 32/64 位 CPU 暫存器中執行的操作的附帶結果?
uj5u.com熱心網友回復:
歡迎來到整數晉升! C語言的一個行為(一個經常被批評的行為,我想補充一下)是像char和short這樣的型別在對它們進行任何算術操作之前就被提升為int,并且其結果也是int。這意味著什么?
unsigned char foo(unsigned char x) {
return (x << 4) >> 4;
}
int main(void) {
if (foo(0xFF) == 0x0F) {
printf("Yay!
")。)
}
else {
printf("...嘿,等一下!
")。)
}
return 0;
}
不用說,上面的代碼列印出...嘿,等一下!。讓我們來發現原因:
// this line of code:
return (x << 4) >> 4;
//被轉換為這個(因為整數的推廣):
return ((int) x << 4) >> 4;
因此,這就是發生的情況:
x是無符號字符(8位),其值是0xFF,x << 4需要被執行,但首先x被轉換為int(32位),x << 4變成0x000000FF << 4,而結果0x00000FF0也是int,0x00000FF0 >> 4被執行,得到0x000000FF,0x000000FF被轉換為無符號字符(因為這是foo()的回傳值),所以它變成0xFF,foo(0xFF)會產生0xFF而不是0x0F。
如何防止這種情況?很簡單:將
x << 4的結果轉換為無符號char。在前面的例子中,0x00000FF0會變成0xF0。
unsigned char foo(unsigned char x) {
return ((unsigned char) (x << 4)) >> 4;
}
foo(0xFF) == 0x0F; }
注意:在前面的例子中,假定unsigned char是8位,int是32位,但這些例子基本上適用于任何CHAR_BIT == 8的情況(因為C17要求sizeof(int) * CHAR_BIT >= 16)。
P.S.:當然,這個答案并不像C語言官方標準檔案那樣詳盡。但是你可以在ISO/IEC 9899:2018標準的最新草案(又稱C17/C18)中找到C的所有(有效和定義的)行為描述。
uj5u.com熱心網友回復:
C 2018 6.5.7討論了移位運算子。第3段說:
整數晉升是在每個運算元上執行的......
6.3.1.1 2規定了整數晉升:
整數晉升是對每個運算元進行的。
...如果一個
int可以表示原始型別的所有值(受寬度限制,對于一個位域),該值被轉換為int;否則,它被轉換為無符號int。這些被稱為integer promotions。所有其他的型別都不會因為整數的推廣而發生變化。因此在
a << b中,a和b是unsigned char,a被提升到int,它至少是16位。(一個C實作可以將unsigned char定義為超過8位。它可能與int的寬度相同。在這種情況下,整數晉升將不會轉換a或b。注意,如果不應用整數晉升,那么評估
a << b時,b等于9的行為將不會被C標準所定義,因為移位運算子的行為沒有被定義為移位量大于或等于左運算子的寬度。6.5.5規定了乘法運算子。第3段說:
通常的算術轉換是在運算元上進行的。6.3.1.8規定了通常的算術轉換:
...首先,如果任何一個運算元的對應實數型別是
長雙數,那么另一個運算元將被轉換為一個對應實數型別是長雙數的型別,而不改變型別域[復數或實數]。否則,如果任一運算元的對應實數型別是
double,則另一運算元在不改變型別域的情況下被轉換為對應實數型別為double的型別。否則,如果任何一個運算元的對應實數型別是
float,則另一個運算元在不改變型別域的情況下被轉換為一個對應實數型別為float的型別。否則,對兩個運算元都進行整數晉升。然后,下面的規則被應用于晉升的運算元:
如果兩個運算元具有相同的型別,那么就不需要進一步轉換。
否則,如果兩個運算元都是有符號的整數型別或都是無符號的整數型別,那么具有較小整數轉換等級的運算元將被轉換為具有較大等級的運算元的型別。
否則,如果具有無符號整數型別的運算元的等級大于或等于另一個運算元型別的等級,那么具有有符號整數型別的運算元被轉換為具有無符號整數型別的運算元的型別。
否則,如果有符號整數型別的運算元的型別可以表示無符號整數型別的運算元的所有值,那么無符號整數型別的運算元被轉換為有符號整數型別的運算元的型別。
否則,兩個運算元都被轉換為與有符號整數型別的運算元的型別相對應的無符號整數型別。
等級有一個技術定義,主要與寬度(整數型別中的位數)相對應。
因此,在a * b中,a和b都是無符號char,它們都被提升為int(有上面關于寬的無符號char的注意事項),不需要進一步轉換。 如果一個運算元比int寬,例如long long int,而另一個是unsigned char,那么兩個運算元將被轉換為更寬的型別。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/319971.html
標籤:
