2012-02-09 19 views
8

最近,我們在一些舊代碼中發現了奇怪的行爲。這段代碼已經工作了很長時間,但在某些平臺(XBox 360,PowerPC)上打開了,編譯器優化功能打開了最大值。通常,我會懷疑未定義的行爲。兼容編譯器可以中斷uint32_t - > int16_t - > int32_t轉換嗎?

代碼看起來大致是這樣的:

#include <stdint.h> 
uint32_t sign_extend16(uint32_t val) 
{ 
    return (int32_t)(int16_t)val; 
} 

這是一個仿真的一部分,所以有問題的操作應該不會太陌生。通常情況下,我希望這隻考慮較低的16位,並將其擴展到32位。顯然,這是它長久以來的行爲。在x86_64,海灣合作委員會給了我這樣的結果:

0000000000000000 <sign_extend16>: 
    0: 0f bf c7    movswl %di,%eax 
    3: c3      retq 

然而,從我能理解標準的,將一個無符號的簽署是沒有定義它應該是不可能的代表符號一與價值簽名類型。

那麼編譯器是否可以假設無符號值必須在[0, 32767]的範圍內,因爲任何其他值都是未定義的?在這種情況下,投射到int16_t而另一投到int32_t什麼都不會做。在這種情況下,編譯器將代碼轉換爲簡單的移動是合法的嗎?

+1

'(int16_t)val'的行爲永遠不會被定義。如果'val'可以表示爲'int16_t',則其行爲是明確定義的,否則行爲是實現定義的。 – 2012-02-09 23:25:01

+0

@Maister你在x86_64上遇到的問題是什麼? 'movswl'指令不會簽署擴展名。當您通過32768值時,結果如何?在使用'gcc'的32位/ 64位系統上,返回值應該是'0xFFFF8000'。 – ouah 2012-02-09 23:58:17

+0

我可能不夠清楚。在x86_64上的行爲是預期的。它不會像xbox 360上預期的那樣工作。 – Maister 2012-02-10 00:02:40

回答

9

兩個整數類型之間的轉換永遠不會是未定義的行爲。

但是一些整數轉換是實現定義的。

在整數轉換Ç說:

(C99,6.3.1.3p3)「否則,新的類型是有符號和值不能在它來表示;或者其結果是實現定義一個或實現定義的信號被提出。「

在此情況下什麼呢gcc記錄在這裏:

http://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html

「對於轉換的類型的寬度N,值減小模2^N是內的範圍類型;沒有信號被提出「

2

由於ouah表示,超出範圍值的轉換給出了一個實現定義的資源ult(或者允許實現定義的信號被提升)。

例如,對於一個實現來說,將超出範圍的值轉換爲int16_t僅保留該值的低15位並始終將符號位設置爲0是完全合法的。因此它會將sign_extend16()函數簡單地解釋爲return val & 0x7fff;

然而,實現無法解釋您的功能,使得它只是返回val不變 - 實現定義的轉換到int16_t必須導致的int16_t範圍內某處的值,因此最終結果必須位於某處在[0, 32767][4294934528, 4294967295]

請注意,在那裏鑄造的int32_t是完全多餘的。

不依賴於實現定義的轉換

兩個備選方案(注意參數類型的val的變化):

uint32_t se16(uint16_t val) 
{ 
    return -((uint32_t)val << 1 & 0x10000) | val; 
} 


uint32_t se16(uint16_t val) 
{ 
    return (val^(uint32_t)32768) - (uint32_t)32768; 
} 

...可惜GCC優化器似乎並沒有注意到,這些只是低16位的符號擴展。

+0

'((int32_t)val - 32768)^(int32_t)( - 32768) '? – supercat 2013-08-26 18:23:26

+0

@supercat:是的,它也可以工作,就像'(val ^(uint32_t)32768) - (uint32_t)32768'一樣。儘管如此,我無法讓優化器生成一個「movswl」。 – caf 2013-08-27 05:57:24

-1

使用UNION:

uint32_t sign_extend16(uint32_t val){ 
    union{ 
     uint32_t a; 
     int32_t b; 
     int16_t c; 
    }o; 
    o.a=val; 
    o.b=o.c; 
    return o.a; 
} 
+2

這不是endian不可知的 – Christoph 2012-02-10 07:13:57

0

我已經在評論中提到的兩個版本:

#include <stdint.h> 

uint32_t sign_extend16_a(uint32_t val) 
{ 
    return (uint32_t)(int16_t)(uint16_t)val; 
} 

uint32_t sign_extend16_b(uint32_t val) 
{ 
    union { uint16_t u; int16_t i; } ui; 
    ui.u = (uint16_t)val; 
    return (uint32_t)ui.i; 
} 

主要生產用gcc 4.5.3上X86-64下面的輸出與-O1

.globl sign_extend16_a 
    .def sign_extend16_a; .scl 2; .type 32; .endef 
sign_extend16_a: 
    subq $8, %rsp 
    movswl %cx, %eax 
    addq $8, %rsp 
    ret 
.globl sign_extend16_b 
    .def sign_extend16_b; .scl 2; .type 32; .endef 
sign_extend16_b: 
    subq $8, %rsp 
    movswl %cx, %eax 
    addq $8, %rsp 
    ret 
相關問題