2010-08-03 13 views
21

Recentemente ho affrontato uno strano comportamento utilizzando l'operatore di spostamento a destra.Comportamento strano dell'operatore di spostamento a destra (1 >> 32)

Il seguente programma:

#include <cstdio> 
#include <cstdlib> 
#include <iostream> 
#include <stdint.h> 

int foo(int a, int b) 
{ 
    return a >> b; 
} 

int bar(uint64_t a, int b) 
{ 
    return a >> b; 
} 

int main(int argc, char** argv) 
{ 
    std::cout << "foo(1, 32): " << foo(1, 32) << std::endl; 
    std::cout << "bar(1, 32): " << bar(1, 32) << std::endl; 
    std::cout << "1 >> 32: " << (1 >> 32) << std::endl; //warning here 
    std::cout << "(int)1 >> (int)32: " << ((int)1 >> (int)32) << std::endl; //warning here 

    return EXIT_SUCCESS; 
} 

Uscite:

foo(1, 32): 1 // Should be 0 (but I guess I'm missing something) 
bar(1, 32): 0 
1 >> 32: 0 
(int)1 >> (int)32: 0 

Cosa accade con la funzione foo()? Capisco che l'unica differenza tra ciò che fa e le ultime 2 righe, è che le ultime due righe vengono valutate in fase di compilazione. E perché funziona "se uso un intero a 64 bit?

Tutte le luci a riguardo saranno molto apprezzate!


Sicuramente correlata, qui è quello che dà g++:

> g++ -o test test.cpp 
test.cpp: In function 'int main(int, char**)': 
test.cpp:20:36: warning: right shift count >= width of type 
test.cpp:21:56: warning: right shift count >= width of type 
+1

Mi chiedo solo, perché la "non una domanda vera" voto ravvicinato ?! – ereOn

+1

domanda molto interessante .. anche se pensavo che ruotare di un numero elevato si tradurrebbe in 0. Non sapevo che fosse UB. – Naveen

+3

odiatori che odieranno. :) È l'unica spiegazione per i downvotes drive-by. – tzaman

risposta

35

E 'probabile che la CPU è in realtà il calcolo

a >> (b % 32) 

in foo; Nel frattempo, il 1 >> 32 è un'espressione costante, in modo che il compilatore si piega la costante a tempo di compilazione, che dà qualche modo 0.

Poiché il campione (C++ 98 § 5,8/1) prevede che

il comportamento è indefinito se l'operando destro è negativo, o maggiore di o uguale alla lunghezza in bit dell'operando sinistro promosso.

non vi è alcuna contraddizione con foo(1,32) e 1>>32 dando risultati diversi.

 

D'altra parte, in bar hai fornito un valore senza segno a 64 bit, come 64> 32 è garantito il risultato deve essere 1/2 = 0. Tuttavia, se si scrive

bar(1, 64); 

si può ancora ottenere 1.


Edit: La logica di spostamento a destra (SHR) si comporta come a >> (b % 32/64) su x86/x86-64 (Intel # 253667, Pagina 4-404):

L'operando destinazione può essere un registro o una locazione di memoria. L'operando di conteggio può essere un valore immediato o il registro CL. Il conteggio è mascherato a 5 bit (o 6 bit se in modalità 64 bit e REX.W è utilizzato). L'intervallo di conteggio è limitato a 0 a 31 (o 63 se si utilizza la modalità 64 bit e REX.W). Una codifica opcode speciale è fornita per un conteggio di 1.

Tuttavia, su ARM (ARMv6 & 7, almeno), la logica di spostamento a destra (LSR) è implementato come (Armisa Pagina A2-6)

(bits(N), bit) LSR_C(bits(N) x, integer shift) 
    assert shift > 0; 
    extended_x = ZeroExtend(x, shift+N); 
    result = extended_x<shift+N-1:shift>; 
    carry_out = extended_x<shift-1>; 
    return (result, carry_out); 

dove (Armisa Pagina AppxB- 13)

ZeroExtend(x,i) = Replicate('0', i-Len(x)) : x 

Ciò garantisce uno spostamento a destra di ≥32 produce zero. Ad esempio, quando questo codice viene eseguito su iPhone, lo foo(1,32) darà 0.

Questi spettacoli che spostano un numero intero a 32 bit di ≥32 non è portabile.

+1

Grazie. Ho sempre pensato che spostando "troppo" risultava azzerato il valore, non UB. Quindi suppongo che l'utilizzo di un valore a 64 bit qui (con '32' come l'operando di spostamento a destra) sia un comportamento corretto e definito? – ereOn

+0

@ereOn: corretto. – kennytm

+3

'b% 32' sembra corretto; Ho provato un 'foo (16, 33)' e ho ottenuto '8' come risultato. Buona investigazione! – tzaman

0

Suppongo che il motivo sia che il tipo int detenga 32 bit (per la maggior parte dei sistemi), ma un bit è utilizzato per il segno poiché è un tipo firmato. Quindi solo 31 bit sono usati per il valore reale.

+0

Signedness è una questione di interpretazione a livello di lingua. La CPU "vede" i bit, non i valori o i segni. – DevSolar

6

OK. Quindi è in 5.8.1:

Gli operandi devono essere di tipo integrale o di enumerazione e vengono eseguite promozioni integrali. Il tipo del risultato è quello dell'operando sinistro promosso. Il comportamento non è definito se l'operando destro è negativo o maggiore o uguale a la lunghezza in bit dell'operando sinistro promosso.

Quindi si dispone di un comportamento non definito (tm).

0

L'avviso dice tutto!

Ma in tutta onestà sono stato morso dallo stesso errore una volta.

int a = 1; 
cout << (a >> 32); 

è completamente indefinito. In effetti il ​​compilatore in genere fornisce risultati diversi rispetto al tempo di esecuzione nella mia esperienza. Ciò che intendo è che se il compilatore può vedere per valutare l'espressione di spostamento in fase di esecuzione, potrebbe dare un risultato diverso all'espressione valutata in fase di esecuzione.

1

L'ho compilato su finestre a 32 bit utilizzando il compilatore VC9. Mi ha dato il seguente warning. Dal momento che sizeof(int) è 4 byte sul mio compilatore di sistema indica che lo spostamento a destra di 32 bit comporta un comportamento indefinito. Poiché non è definito, non è possibile prevedere il risultato. Solo per il controllo ho spostato a destra con 31 bit e tutti gli avvertimenti sono scomparsi e il risultato è stato anche come previsto (ad esempio 0).

+0

indefinito! = Imprevedibile. Potrebbe * significare ciò, ma non è necessariamente così. –

+0

@Nathan Ma per motivi pratici undefined dovrebbe essere generalmente trattato come imprevedibile. Altrimenti si accoppierà il codice a un ambiente di build/run-time molto specifico. – foraidt

+0

Ecco perché sia ​​Intel che Microsoft hanno così tanti problemi con la retrocompatibilità. Il software spesso (o abbastanza spesso) fa qualcosa di indefinito, solo per scoprire che si rompe su una futura CPU o sistema operativo. Gli utenti non sanno che il software è sbagliato e presumono che Intel o Microsoft l'abbiano interrotta, con conseguente cattiva stampa. Microsoft e Intel fanno di tutto per non rompere il codice legacy anche se è stato scritto male. –

-5

foo (1,32) esegue una rotazione di merda, quindi i bit che dovrebbero scomparire a destra riappaiono a sinistra. Se lo fai 32 volte, il bit singolo impostato su 1 torna alla sua posizione originale.

bar (1,32) è lo stesso, ma il bit è nel 64-32 + 1 = 33 ° bit, che è sopra i numeri rappresentabili per un int a 32 bit. Vengono presi solo i 32 bit più bassi e sono tutti 0.

1 >> 32 viene eseguito dal compilatore. Non ho idea del perché gcc usi uno spostamento non rotante qui e non nel codice generato.

Stessa cosa per ((int) 1 >> (int) 32)

+4

'>>' è _non_ un turno di rotazione, altrimenti '1 >> 1' darebbe' INT_MIN', cosa che probabilmente non lo è. Il problema qui è che lo spostamento di tanti o più bit quanti ce ne sono in un tipo è U.B. Che in questo caso si è manifestato come risultato equivalente a un turno di rotazione è una coincidenza. –

3

Cosa accade in foo è che la larghezza dello spostamento è maggiore o uguale alla dimensione dei dati che vengono spostati. Nello standard C99 che si traduce in un comportamento indefinito.Probabilmente è lo stesso in qualunque sia lo standard C++ di MS VC++.

La ragione di ciò è consentire ai progettisti del compilatore di sfruttare qualsiasi supporto hardware della CPU per i turni. Ad esempio, l'architettura i386 ha un'istruzione per spostare una parola di 32 bit per un numero di bit, ma il numero di bit è definito in un campo nell'istruzione largo 5 bit. Molto probabilmente, il compilatore sta generando l'istruzione prendendo la quantità di spostamento del bit e mascherandola con 0x1F per ottenere lo spostamento di bit nell'istruzione. Ciò significa che lo spostamento di 32 è lo stesso di uno spostamento di 0.

Problemi correlati