2012-02-09 24 views
8

Son zamanlarda bazı eski kodlarda garip davranışlar keşfettik. Bu kod çağlar için çalıştı, ancak bazı platformlarda (XBox 360, PowerPC) derleyici optimizasyonları maksimuma çıkarıldı. Genellikle, tanımlanmamış davranışlardan şüphelenirim.Uygun bir derleyici uint32_t -> int16_t -> int32_t dönüşümü yapabilir mi?

Kod böyle kabaca görünür:

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

Söz konusu operasyon çok garip olmamalı böylece bir emülatör bir parçası. Normal olarak, bunun yalnızca 16 bitlik değerleri dikkate almasını ve bunu 32 bit'e kadar uzatmasını beklerim. Görünüşe göre, bu yaş için sahip olduğu davranıştı. onunla işaretsiz birinin değerini temsil etmek mümkün olmamalıdır

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

Ancak, ben bir tanımlanmamıştır imzalı imzasız bir dönüştürme, standardın anlayabileceği kadarıyla: x86_64 günü, GCC bana bu sonucu verir imzalı tip.

Daha sonra başka bir değerin tanımsız olacağı için, derleyicinin imzasız değerin [0, 32767] aralığında olması gerektiğini varsayması mümkün olabilir mi? Bu durumda, int16_t'a bir döküm ve yine int32_t'a başka bir oyuncu hiçbir şey yapmaz. Bu durumda, derleyicinin kodu basit bir harekete çevirmesi yasal olabilir mi?

+1

'(int16_t) val'ın davranışı asla tanımsız değildir. Davranış, 'val' bir int16_t 'olarak gösterilebiliyorsa, davranış tanımsal olarak tanımlanmışsa iyi tanımlanır. –

+0

@Maister, x86_64'te tam olarak sorun nedir? movswl talimatı uzatma imzaladı. 32768 değerini geçtiğinizde elde ettiğiniz sonuç nedir? "Gcc" ile 32 bit/64 bit sistemlerde, dönüş değeri 0xFFFF8000 olmalıdır. – ouah

+0

Yeterince açık olmayabilirdim. X86_64 üzerinde davranış beklenmektedir. Ancak xbox 360'da beklendiği gibi davranmaz. – Maister

cevap

9

İki tamsayı türü arasında dönüştürme, asla tanımlanmamış bir davranış değildir.

Ancak bazı tamsayı dönüşümleri tanımlanmış uygulamalardır. tamsayı dönüşümleri üzerinde

Cı söyler:

(C99, 6.3.1.3p3) "Aksi takdirde, yeni tip imzalanır ve değeri, bir temsil edilemez, ya da sonuç uygulama tanımlı veya bir Uygulama tanımlı sinyal yükseltildi. " Bu durumda, ilgili gcc ne

burada belgelenmiştir: genişlik bir N türüne dönüşüm için

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

", değeri modulo 2 azalır^N aralığında olması tür, sinyal yok "

2

ouah'un belirttiği gibi, aralık dışı bir değerin dönüştürülmesi, uygulama tanımlı bir çözünürlük sağlar. ult (veya uygulama tanımlı bir sinyalin yükseltilmesine izin verir). Örneğin, bir uygulama dışı menzil değerinin int16_t'a dönüştürülmesinin değerin alt 15 bitini koruduğunu ve her zaman işaret bitini her zaman 0'a ayarladığını söylemek bir uygulama için kesinlikle yasal olacaktır. sign_extend16() işlevinizi return val & 0x7fff; olarak yorumlayabilir. Ancak

, basitçe val değişmeden döndüren şekilde sizin işlevini yorumlamak değil olabilir bir uygulama - int16_t için uygulama tanımlı dönüşüm yere int16_t aralığında bir değere neden olmalı, nihai sonuç bir yerde olmalı, böylece [0, 32767] veya [4294934528, 4294967295].

Ayrıca, int32_t dökümünün tamamen gereksiz olduğunu unutmayın. Uygulama tanımlı dönüşümler güvenmeyin

iki alternatif vardır (val argümanı tip değişikliği dikkat edin):

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; 
} 

... ama maalesef gcc optimizasyoncusu fark görünmüyor Bunlar sadece 16 bitlik bir işaret uzantısıdır.

+0

Ne hakkında ((int32_t) val - 32768)^(int32_t) (- 32768) '? – supercat

+0

@supercat: Evet, bu da çalışıyor (olduğu gibi (val^(uint32_t) 32768) - (uint32_t) 32768'. Bununla birlikte, optimiser'i bunlardan herhangi biriyle tek bir "movswl" üretmeye alamıyorum. – caf

-1

kullanma birlik:

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

bu endian-agnostik değil – Christoph

0

Zaten yorumlarda söz ettik iki sürümü:

#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; 
} 

-O1 ile x86-64 üzerinde gcc 4.5.3 ile şu çıktıyı üretir:

.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