2012-01-30 86 views
5

看這個樣品C代碼(提取的測試情況爲例):無法理解移位運算符行爲在C代碼

main() { 
    unsigned long a, b; 
    int c; 
    c = 32; 
    a = 0xffffffff << 32; 
    b = 0xffffffff << c; 
    printf ("a=%x, b=%x\n", a, b); 
} 

打印:a=0, b=ffffffff

我無法理解爲什麼b不是零,就像一個。我在Microsoft C和GCC上進行了測試。

更新:我固定的愚蠢錯字(應該是< < c和當然不是< < B)。但我的問題仍然存在,例如結果仍然是一樣的。

回答

3

在C它是未定義的行爲,以左移比推動操作數的寬度。

(C99,6.5.7p3)「如果右操作數的值爲負值或大於或等於提升的左操作數的寬度,則行爲未定義。「

我假定在下面intunsigned int類型的實施例中是32位的

類型的無後綴十六進制整數常數的是在相應的列表中的第一,其中其值可表示爲:。intunsigned intlongunsigned longlong longunsigned long long

所以這裏0xFFFFFFFFunsigned int型和0xFFFFFFFF << 32是不確定的行爲。

+0

Downvoter,你能解釋一下爲什麼downvoted? – ouah 2012-01-30 21:52:10

+0

您爲問題的第一部分提供了大量細節,但沒有解釋部分b。 – BitBank 2012-01-30 22:32:34

+1

@BBBank因爲部分'a'和部分'b'是相同的問題(相同的左移是執行)與相同的解釋。在'a'和'b'賦值的RHS中的兩個表達式都是未定義的行爲。由於它是未定義的行爲,所以'a'和'b'的結果可能是相同的或者可能不同,或者程序可以將'rm -rf'作爲根分區。當然,我們可以觀察到不同的編譯啓發式,因爲在第一種情況下,常量被用作轉換的右操作數,但這是不相關的。 – ouah 2012-01-30 22:38:20

6

你永遠不會初始化b到任何你在這裏使用它之前:

b = 0xffffffff << b; 

因此,它可以是任何東西。 (我想你實際上意味着通過c轉移。)


這一邊:

的主要問題是,通過在數據類型或更多位的轉移#是不確定的行爲
所以如果字面0xffffffff是一個32位的整數,則該行:

a = 0xffffffff << 32; 

不是由標準限定。 (見註釋)。


你也應該得到一個編譯器警告:

warning C4293: '<<' : shift count negative or too big, undefined behavior 
+0

爲什麼* so如果無符號長整型只有32位*?如果'unsigned int'是32位,那麼'0xffffffff << 32'是未定義的行爲。 – ouah 2012-01-30 21:16:25

+0

@ouah,啊是的好點。正在修復... – Mysticial 2012-01-30 21:18:22

1

你有b = 0xffffffff << b;,但也許你的意思b = 0xffffffff << c;

0

爲什麼它應該是零?您可以通過一個垃圾數(第一個值爲b)將其移位爲0。

0

您沒有初始化B,所以它將具有任何(隨機)值在該特定內存位置的時間該程序運行。

0

當你到達b = 0xffffffff << b;行時,b的值還沒有被賦值。它看起來像它的值爲零,使指令b = 0xffffffff << b;。其中expalins給出的答案。

0

良好的做法是在移位後。在這種情況下,你知道發生了什麼。

Examply:

b << = 4; 
b &= 0xfffffff0; 
2

我打賭,修復你的錯字後,你會得到2個零作爲結果。這是爲什麼: 如果你的程序是(我敢打賭)編譯爲發佈,你有優化打開。這意味着編譯器將使用所謂的「常量摺疊」,CPU甚至不會執行這些轉換。在引擎蓋下,代碼將有2個零常量壓入堆棧並調用printf。這些操作的結果(轉換)將基本上成爲程序中的常數。因此,有沒有不確定的行爲,等等 - 你的a和b變爲恆定值,你會得到某物像:

push 0 
push 0 
push offset to printf format string 
call to printf 
+0

那麼顯然這個代碼只是一個示例我撞在一起,來分析在生產代碼中的問題,但你說得對:用'GCC -O3'我得到兩個零,並與海灣合作委員會'我-O0'得到0和爲0xffffffff 。 – haimg 2012-01-30 21:51:52

+0

因此,在調試模式下(不進行優化),您將得到第一種情況0(編譯器使其爲常數,因此爲0)。第二種情況顯示你正在使用x86 cpu,其中左移的位數被屏蔽(31),所以基本上你總是左移(操作數<<(count&0x1F)),因此(0xFFFFFFFF << 0)導致不變' b」。 (可以驗證在「英特爾架構軟件開發人員手冊第2卷:指令集參考」(非常有用的東西) – Artur 2012-01-30 22:04:05

2

沒有人提到的問題的另一個有趣的方面。在x86計算機上,移位量使用模數32,所以移位32的移位實際上是移位0.在ARM計算機上,移位值(如果來自寄存器)沒有改變,所以任何移位值爲32或目標寄存器中總是會更大。

特別是在你的問題中,編譯器對於常量值很聰明,所以它將(0xffffffff < < 32)轉換爲正確的值(0)。在第二個例子中,編譯器不能直接計算來自變量的移位量。在Intel機器上執行時,左移32將導致左移0。

+0

這是完全不相關的 – 2013-08-12 01:26:18

+0

這是正確的答案。 – xiaokaoy 2016-08-23 09:49:04