2014-11-17 9 views
7

La mia domanda è simile a this ma un po 'più specifica. Sto scrivendo una funzione per leggere un intero senza segno a 32 bit da un istream rappresentato usando little endian. In C qualcosa di simile a questo dovrebbe funzionare:Comportamento non definito quando si utilizza iostream letto e firmato char

#include <stdio.h> 
#include <inttypes.h> 

uint_least32_t foo(FILE* file) 
{ 
    unsigned char buffer[4]; 
    fread(buffer, sizeof(buffer), 1, file); 

    uint_least32_t ret = buffer[0]; 
    ret |= (uint_least32_t) buffer[1] << 8; 
    ret |= (uint_least32_t) buffer[2] << 16; 
    ret |= (uint_least32_t) buffer[3] << 24; 
    return ret; 
} 

Ma se provo a fare qualcosa di simile utilizzando un istream mi imbatto in quello che penso è un comportamento indefinito

uint_least32_t bar(istream& file) 
{ 
    char buffer[4]; 
    file.read(buffer, sizeof(buffer)); 

    // The casts to unsigned char are to prevent sign extension on systems where 
    // char is signed. 
    uint_least32_t ret = (unsigned char) buffer[0]; 
    ret |= (uint_least32_t) (unsigned char) buffer[1] << 8; 
    ret |= (uint_least32_t) (unsigned char) buffer[2] << 16; 
    ret |= (uint_least32_t) (unsigned char) buffer[3] << 24; 
    return ret; 
} 

Si tratta di un comportamento indefinito nei sistemi in cui char è firmato e non c'è un complemento a due e non può rappresentare il numero -128, quindi non può rappresentare 256 caratteri diversi. In foo funzionerà anche se il carattere è firmato perché la sezione 7.21.8.1 dello standard C11 (bozza N1570) dice che fread utilizza unsigned char non char e unsigned char deve essere in grado di rappresentare tutti i valori compresi nell'intervallo compreso tra 0 e 255.

Vuol bar davvero causare un comportamento indefinito quando tenta di leggere il numero 0x80 e se è così c'è una soluzione ancora usando un std::istream?

Edit: Il comportamento non definito mi riferisco al è causato dal istream::read in buffer non il cast dal buffer a unsigned char. Ad esempio se è un segno + grandezza macchina e il carattere è firmato allora 0x80 è negativo 0, ma negativo 0 e positivo 0 deve sempre confrontare uguale secondo lo standard. Se questo è il caso, ci sono solo 255 diversi caratteri firmati e non è possibile rappresentare un byte con un carattere. I cast funzioneranno perché aggiungeranno sempre UCHAR_MAX + 1 ai numeri negativi (sezione 4.7 della bozza di C++ 11 standard N3242) quando si esegue il casting con firma non firmata.

+2

"è un comportamento non definito su sistemi in cui il carattere è firmato e non è un complimento di due e non può rappresentare il numero -128" - non del tutto. La conversione da firmata a non firmata è perfettamente definita. Non importa se 'signed char' non può rappresentare' -128' - sarà quindi interpretato come qualunque valore abbia in un'altra rappresentazione (-127 in complemento a 1 o negativo in segno + magnitudine). –

+0

Intendevo che il file può contenere il byte '0x80' e' istream :: read' può leggerlo non la conversione da firmata a non firmata. Ma penso che tu abbia ragione sulla conversione da firmata a non firmata. – qbt937

+0

Sì, può essere letto, come ho accennato qui sotto - tutte e tre le rappresentazioni 'char' firmate hanno una rappresentazione non-trap per il pattern di bit' 10000000'. –

risposta

3

Penso di avere la risposta: bar non causa un comportamento indefinito.

Nella risposta accettata di questo question, R .. dice:

su un sistema non-due-complemento, char firmato non sarà adatto per l'accesso alla rappresentazione di un oggetto. Questo perché o ci sono due possibili rappresentazioni di caratteri firmati che hanno lo stesso valore (+0 e -0), o una rappresentazione che non ha valore (una rappresentazione trap). In entrambi i casi, ciò ti impedisce di fare le cose più significative che potresti fare con la rappresentazione di un oggetto. Ad esempio, se si ha un intero senza segno a 16 bit 0x80ff, uno o l'altro byte, come un carattere firmato, sta per intercettare o confrontare uguale a 0.

Si noti che su tale implementazione (non-twos -complement), plain char deve essere definito come un tipo unsigned per accedere correttamente alle rappresentazioni di oggetti tramite char. Sebbene non esista alcun requisito esplicito, lo considero un requisito derivante da altri requisiti dello standard.

Questo sembra essere il caso perché la sezione 3.9 del paragrafo 2 del C++ 11 (progetto N3242) dice:

Per qualsiasi oggetto (diverso da un sotto-oggetto della classe base) di banalmente copiabile digitare T, indipendentemente dal fatto che l'oggetto contenga un valore valido di tipo T, i byte sottostanti (1.7) che costituiscono l'oggetto possono essere copiati in una matrice di caratteri char o unsigned. Se il contenuto dell'array di char o unsigned char viene ricopiato nell'oggetto, l'oggetto deve successivamente mantenere il suo valore originale.

Se char è stato firmato e aveva molteplici rappresentazioni di oggetti per un valore (come ad esempio 0 in segno + grandezza), poi se l'oggetto è stato copiato su un array di caratteri e poi di nuovo in oggetto, potrebbe non avere lo stesso valore afterwords perché il char array può cambiare in una diversa rappresentazione dell'oggetto. Ciò sarebbe in contraddizione con la citazione di cui sopra, quindi char deve essere senza segno se la macchina è signed char con rappresentazioni di oggetti multipli per la stessa rappresentazione di valore (ad esempio su una macchina segno + valore sia 0x80 che 0x00 rappresenterebbero 0). Ciò significa che bar è un comportamento definito perché l'unico caso in cui è un comportamento non definito richiederebbe che char sia firmato e che abbia una rappresentazione dispari che non soddisfi la citazione sopra riportata dallo standard.

Problemi correlati