2011-10-30 3 views
5

Questa domanda è stato ispirato dalle (inattese) risultati di questo codice:c standard e bitshifts

uint16_t t16 = 0; 
uint8_t  t8 = 0x80; 
uint8_t t8_res; 

t16 = (t8 << 1); 
t8_res = (t8 << 1); 

printf("t16: %x\n", t16); // Expect 0, get 0x100 
printf(" t8: %x\n", t8_res); // Expect 0, get 0 

Ma si scopre questo ha un senso:

6.5.7 operatori di spostamento bit a bit

Vincoli

Ciascuno degli operandi, deve possedere intero tipo

Così la linea originariamente confusa equivale a:

t16 = (uint16_t) (((int) t8) << 1); 

Un po IMHO non intuitivo, ma almeno ben definito.

Ok, grande, ma poi facciamo:

{ 
uint64_t t64 = 1; 
t64 <<= 31; 
printf("t64: %lx\n", t64); // Expect 0x80000000, get 0x80000000 
t64 <<= 31; 
printf("t64: %lx\n", t64); // Expect 0x0, get 0x4000000000000000 
} 

// edit: seguendo lo stesso argomento letterale come sopra, il seguente dovrebbe essere equivalente:

t64 = (uint64_t) (((int) t64) << 31); 

// qui la mia confusione/expectation [end_edit]

Ora, otteniamo il risultato intuitivo, ma non quello che sarebbe derivato dalla mia lettura (letterale) dello standard. Quando/come si svolge questa "ulteriore promozione automatica del tipo"? O c'è una limitazione altrove che un tipo non può mai essere retrocesso (che avrebbe senso?), In questo caso, come si fa le regole di promozione valgono per:

uint32_t << uint64_t 

Poiché la norma non dice entrambi gli argomenti sono promossi al int; entrambi gli argomenti dovrebbero essere promossi allo stesso tipo qui?

// edit:

Più specificamente, quale dovrebbe essere il risultato di:

uint32_t t32 = 1; 
uint64_t t64_one = 1; 
uint64_t t64_res; 

t64_res = t32 << t64_one; 

// fine modificare

La risposta alla domanda di cui sopra viene risolto quando ci rendiamo conto che le specifiche non richiede una promozione allo int in particolare, piuttosto a uno integer type, che uint64_t si qualifica come.

// PRECISAZIONE EDIT:

Ok, ma ora sono di nuovo confuso. In particolare, se uint8_t è un tipo intero, perché viene promosso a int? Non sembra essere correlato alla costante int 1, come il seguente esercizio dimostra:

{ 
uint16_t t16 = 0; 
uint8_t t8 = 0x80; 
uint8_t t8_one = 1; 
uint8_t t8_res; 

t16 = (t8 << t8_one); 
t8_res = (t8 << t8_one); 

printf("t16: %x\n", t16); 
printf(" t8: %x\n", t8_res); 
} 

t16: 100 
t8: 0 

Perché il (T8 < < t8_one) espressione promosso se uint8_t è un tipo intero?

-

Per riferimento, sto lavorando da ISO/IEC 9899: TC9, WG14/N1124 6 maggio 2005. Se questo è fuori di data e qualcuno potrebbe anche fornire un collegamento a una copia più recente , anche questo sarebbe apprezzato.

+3

non riesco bene a capire il motivo per cui si dice '// Si aspettano 0x0' dopo quel secondo turno data da 31 bit ... –

+0

Sono confuso come Greg è, ma ci si aspetterebbe che i tipi non sono mai retrocessi automaticamente (si strinsero), solo promosso (ampliato). –

+0

Dalla mia lettura dello standard, t64 = (uint64_t) (((int) t64) << 31) è una riga equivalente, nel qual caso il bit impostato dovrebbe essere perso per il troncamento int. – Pat

risposta

5

Il vincolo in §6.5.7 che "Ciascuno degli operandi deve avere un tipo intero." è un vincolo che significa che non è possibile utilizzare gli operatori di spostamento bit per bit su tipi non interi come valori in virgola mobile o puntatori. Non causa l'effetto che stai notando.

La parte che fa causa l'effetto è nel prossimo paragrafo:

  3. Le promozioni interi vengono eseguite su ciascuno degli operandi. Il tipo del risultato è quello dell'operando sinistro promosso.

I promozioni interi descritti in §6.3.1.1:

  2.Possono essere utilizzati in un'espressione dove può essere utilizzato un int o unsigned int:

  • Un oggetto o espressione con un tipo intero il cui intero conversione rango è inferiore o uguale al rango di int e unsigned int.
  • Un campo di bit di tipo _Bool, int, signed int o unsigned int.

Se un int può rappresentare tutti i valori del tipo originale, il valore è convertito in un int; in caso contrario, viene convertito in unsigned int. Queste sono chiamate promozioni per numero intero . Tutti gli altri tipi sono invariati dalle promozioni intere.

uint8_t ha un rango inferiore rispetto int, quindi il valore viene convertito in un int (poiché sappiamo che un int deve essere in grado di rappresentare tutti i valori di uint8_t, date le esigenze sulle gamme di questi due tipi).

Le regole di classificazione sono complesse, ma garantiscono che un tipo con un rango più elevato non può avere una precisione minore. Ciò significa, in effetti, che i tipi non possono essere "abbassati" a un tipo con minore precisione dalle promozioni intere (è possibile che uint64_t sia promosso a int o unsigned int, ma solo se l'intervallo del tipo è almeno quello di uint64_t).

Nel caso di uint32_t << uint64_t, la regola che dà il via è "Il tipo del risultato è quello dell'opuscolo a sinistra promosso". Quindi abbiamo un paio di possibilità:

  • Se int è almeno 33 bit, quindi uint32_t saranno promossi al int e il risultato sarà int;
  • Se int è inferiore a 33 bit e unsigned int è almeno 32 bit, allora uint32_t saranno promossi unsigned int e il risultato sarà unsigned int;
  • Se unsigned int è inferiore a 32 bit, uint32_t rimane invariato e il risultato sarà uint32_t.

Su implementazioni desktop e server comuni di oggi, int e unsigned int sono di solito 32 bit, e così la seconda possibilità si verificherà (uint32_t è promosso a unsigned int). In passato era comune per int/unsigned int essere 16 bit, e la terza possibilità si verificava (uint32_t lasciato senza protezione).

Il risultato del vostro esempio:

uint32_t t32 = 1; 
uint64_t t64_one = 1; 
uint64_t t64_res; 

t64_res = t32 << t64_one; 

sarà il valore memorizzato in 2t64_res.Si noti però che questo non è influenzata dal fatto che il risultato dell'espressione non è uint64_t - e l'esempio di un'espressione che sarebbe essere colpiti è:

uint32_t t32 = 0xFF000; 
uint64_t t64_shift = 16; 
uint64_t t64_res; 

t64_res = t32 << t64_shift; 

Il risultato qui è 0xf0000000.

Nota che, anche se i dettagli sono abbastanza intricata, si può far bollire il tutto a un abbastanza semplice regola che si dovrebbe tenere a mente:

In C, l'aritmetica non è mai finito nei tipi più strette di int/ unsigned int.

+0

Ah .... l'ultimo bit in §6.3.1.1 sec 2 lo ha davvero sigillato per me. Così come la tua regola di riepilogo finale. Il mio modello mentale di C era stato molto più di "promozione su richiesta" (da qui le domande uint8_t aggiunte alla fine). Questa risposta ha molto senso, grazie per il tuo tempo. -pat – Pat

7

penso che la fonte della tua confusione potrebbe essere che le due istruzioni seguenti sono non equivalente:

  • Ciascuno degli operandi avranno tipo intero
  • Ciascuno degli operandi avranno int tipo

uint64_t è un tipo intero.

+2

In effetti, questa è la fonte della confusione dell'OP. –

+0

Brillante! Grazie. – Pat

+1

@Pat: nota che 'uint8_t' è anche un tipo intero, quindi questa regola non è responsabile del comportamento che vedi nel tuo primo esempio. Come dice [Jens Gustedt] (http://stackoverflow.com/questions/7947982/c-standard-and-bitshifts/7948145#7948145), sono le "solite conversioni aritmetiche" che causano valori il cui tipo è più stretto di "int" da promuovere (a "int" o "unsigned"). – caf

3

Hai trovato la regola sbagliata nello standard :(Il relativo è qualcosa come "si applicano le normali promozioni tipo intero" .Questo è quello che ti colpisce per il primo esempio.Se un numero intero come uint8_t ha un valore inferiore di int è promosso a intuint64_t non ha un rango che è più piccolo di int o unsigned Modifica quindi nessuna promozione viene eseguita e viene applicato l'operatore << alla variabile uint64_t

:.. Tutti i tipi di interi più piccolo di int sono promossi per l'aritmetica, questo è giusto un fatto di vita :) Se il numero di promozione uint32_t viene promosso dipende dalla piattaforma, perché potrebbe avere lo stesso valore o superiore a int (non promosso) o un valore più piccolo (promosso).

Per quanto riguarda l'operatore <<, il tipo di operando di destra non è molto importante, ciò che conta per il numero di bit è quello di sinistra (con le regole precedenti). Più importante per quello giusto è il suo valore. Non deve essere negativo o superare la larghezza dell'operando sinistro (promosso).

+0

La domanda principale modificata in quanto le interruzioni di riga non sono consentite nei commenti, per favore dare un'occhiata? – Pat