2015-08-12 11 views
6

mi sono imbattuto in questo pezzo di codice C:Perché prima bitshifting a sinistra e poi a destra, invece di E-ing?

typedef int gint 
// ... 
gint a, b; 
// ... 
a = (b << 16) >> 16; 

Per facilità di notazione supponiamo che b = 0x11223344 a questo punto. Per quanto posso vedere che fa il seguente:

  • b << 16 darà 0x33440000
  • >> 16 darà 0x00003344

Così, i 16 bit più alti vengono scartati.

Perché qualcuno dovrebbe scrivere (b << 16) >> 16 se b & 0x0000ffff funzionerebbe anche? La seconda forma non è più comprensibile? C'è qualche ragione per usare bitshifts in un caso come questo? C'è qualche caso limite in cui i due non potrebbero essere gli stessi?

+0

Cosa ha a che fare con C "istruzioni"? –

+1

@KerrekSB all'inizio un pensiero un compilatore non molto ottimale potrebbe usare più istruzioni per i bitshift, ma dopo aver riconsiderato, non dovrebbe essere così. Grazie. – Keelan

+0

Forse il programmatore voleva che il codice fosse meno leggibile! Questa è l'unica cosa che riesco a pensare! – CinCout

risposta

3

Supponendo che la dimensione di int è di 32 bit, allora non v'è alcuna necessità di utilizzare turni. Infatti, bit per bit & con una maschera sarebbe più leggibile, più portatile e più sicuro.

Si noti che lo spostamento a sinistra su numeri interi con segno negativo richiama il comportamento non definito, e che le cose con spostamento a sinistra nei bit di segno di un intero con segno potrebbero anche richiamare un comportamento non definito. C11 6.5.7 (sottolineatura mia):

Il risultato di E1 < < E2 è E1 sinistra spostata posizioni E2 bit; vuoti i bit sono pieni di zeri. Se E1 ha un tipo senza segno, il valore di il risultato è E1 × 2E2, modulo ridotto uno più del valore massimo rappresentabile nel tipo di risultato.Se E1 ha un tipo firmato e valore non negativo, e E1 × 2E2 è rappresentabile nel tipo di risultato, allora che è il valore risultante; in caso contrario, il comportamento è indefinito.


(L'unica logica possibile mi viene in mente, è qualche ottimizzazione prematura per una CPU a 16 bit che viene fornito con un povero compilatore. Quindi il codice sarebbe più efficiente se hai rotto l'aritmetica in blocchi di 16 bit, ma su un sistema del genere, int molto probabilmente sarebbe 16 bit, quindi il codice non avrebbe alcun senso allora.)

Come nota a margine, non ha alcun senso utilizzare il tipo int firmato. Il tipo più corretto e sicuro per questo codice sarebbe stato uint32_t.

+1

Mi piace questa risposta per aver lanciato il libro all'OP mentre cambiano la domanda con lo sgomento dei rispondenti, ma penso che dovresti criticare l'ipotesi che "int" sia a 32 bit, certamente non "portatile". Tuttavia, il '&' come è preferibile ai turni per evitare comportamenti indefiniti. – Veltas

+0

Lo spostamento a sinistra firmato non è definito dallo standard_. Poiché parte del punto di UB è di consentire il margine di implementazione, può ancora essere definito _ dalla piattaforma_. In pratica, gcc ha [un comportamento ben definito che include l'estensione del segno] (https://gcc.gnu.org/ml/gcc/2000-04/msg00152.html) – Useless

+0

Un altro razionale che è possibile utilizzare è quello per un operando sinistro senza segno i 16 bit superiori vengono cancellati indipendentemente dalla dimensione dell'operando. Non riesco a pensare a un caso per questo immediatamente dove potresti cambiare la dimensione dell'operando. Ad ogni modo sto cancellando la mia risposta, dato che è ridondante dopo le modifiche dell'OP. – Veltas

1

Per un tipo intero senza segno (ad esempio, il primo uint32_t abbiamo pensato che veniva usata),

(b << 16) >> 16 

è identico a b & (1<<16 - 1).

Per un tipo integrale con segno però,

(b << 16) 

potrebbe diventare negativo (cioè, la bassa int16_t sarebbero stati considerati negativi quando assunto da solo), nel qual caso

(b << 16) >> 16 

sarà (probabilmente) ancora negativo a causa di firmare l'estensione. In tal caso, non è la stessa della & maschera, in quanto i bit migliori saranno impostati anziché zero.


O questo comportamento è intenzionale (nel qual caso il typedef commentato-out è fuorviante), o si tratta di un bug. Non posso dirlo senza leggere il codice.

Oh, e il comportamento di spostamento in entrambe le direzioni è come mi aspetto gcc comportarsi su x86, ma non posso commentare come portatile è fuori che. Il turno di sinistra potrebbe essere UB come sottolinea Lundin, e firmare l'estensione sul lato destro è l'implementazione definita.

+0

Non volevo commentare che typedef, le scuse. – Keelan

+0

Oh, pensavo che il typedef 'uint32_t' fosse nel codice e commentato. Nessun problema, comunque. – Useless

+1

"sarà ancora negativo a causa dell'estensione del segno" No, in questo caso il codice richiama il comportamento non definito e definito dall'implementazione. Non si può dire cosa farà questo codice. – Lundin

1

Quindi, i 16 bit più alti vengono scartati.

non lo sono. Sebbene sia formalmente definito dall'implementazione come l'operazione di spostamento verso destra viene eseguita sui tipi firmati, la maggior parte dei compilatori lo fa in modo da replicare il bit del segno.

Così, i 16 bit più alti sono riempiti dal valore replicato del 15 bit come il risultato di questa espressione.

+0

Lo spostamento a destra dei tipi firmati con valori non negativi è definito nelle specifiche, in realtà. – davmac

+0

È definito solo dall'implementazione nel caso in cui il tipo sia sia firmato che negativo, il che non è il caso in questo caso. Per arrivare con questo codice, si dovrebbe prima prevedere l'esito del comportamento indefinito dei dati di spostamento a sinistra nei bit del segno. – Lundin

+0

@Lundin: la domanda non esita i risultati negativi per lo spostamento verso destra. – Veltas

Problemi correlati