operatori - type casting in informatica




Firmato per la conversione non firmata in C-è sempre sicuro? (6)

Supponiamo che abbia il seguente codice C.

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Che conversioni implicite stanno succedendo qui e questo codice è sicuro per tutti i valori di u e i ? (Sicuro, nel senso che, anche se il risultato in questo esempio sarà traboccante di un enorme numero positivo, potrei riportarlo a un int e ottenere il risultato reale.)


Che conversioni implicite stanno succedendo qui,

Sarò convertito in un numero intero senza segno.

e questo codice è sicuro per tutti i valori di te e io?

Sicuro nel senso di essere ben definito sì (vedere https://.com/a/50632/5083516 ).

Le regole sono scritte in standard tipicamente difficili da leggere, ma essenzialmente qualunque sia la rappresentazione utilizzata nel numero intero con segno che il numero intero senza segno conterrà una rappresentazione a complemento del numero di 2.

Addizione, sottrazione e moltiplicazione funzioneranno correttamente su questi numeri risultanti in un altro intero senza segno contenente un numero complementare di due che rappresenta il "risultato reale".

la divisione e il cast verso i tipi interi senza segno più grandi avranno risultati ben definiti ma quei risultati non saranno le rappresentazioni del complemento a 2 del "risultato reale".

(Sicuro, nel senso che, anche se il risultato in questo esempio sarà traboccante di un enorme numero positivo, potrei riportarlo a un int e ottenere il risultato reale.)

Mentre le conversioni da firmato a non firmato sono definite dallo standard, il contrario è definito dall'implementazione sia gcc che msvc definiscono la conversione in modo tale da ottenere il "risultato reale" quando si converte un numero di complemento 2 memorizzato in un intero non firmato in un intero con segno . Mi aspetto che troverai qualsiasi altro comportamento su sistemi oscuri che non usano il complemento a 2 per interi con segno.

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx


Come è stato precedentemente risposto, è possibile eseguire il cast avanti e indietro tra firmato e non firmato senza problemi. Il border case per interi con segno è -1 (0xFFFFFFFF). Prova ad aggiungere e sottrarre da quello e scoprirai che puoi ricominciare e averlo corretto.

Tuttavia, se stai andando avanti e indietro, ti consiglio caldamente di nominare le tue variabili in modo tale che sia chiaro di quale tipo esse siano, ad es .:

int iValue, iResult;
unsigned int uValue, uResult;

È fin troppo facile essere distratti da problemi più importanti e dimenticare quale variabile è il tipo se sono nominati senza un suggerimento. Non si desidera eseguire il cast su un unsigned e quindi utilizzarlo come indice di array.


Quando si converte da firmato a non firmato ci sono due possibilità. I numeri che erano originariamente positivi rimangono (o sono interpretati come) lo stesso valore. Il numero inizialmente negativo sarà ora interpretato come numeri positivi più grandi.


Quando vengono aggiunte una variabile non firmata e una firmata (o qualsiasi operazione binaria), entrambe sono convertite implicitamente in unsigned, il che in questo caso determinerebbe un risultato enorme.

Quindi è sicuro nel senso che il risultato potrebbe essere enorme e sbagliato, ma non si romperà mai.


Risposta breve

Il tuo i verrà convertito in un numero intero senza segno aggiungendo UINT_MAX + 1 , quindi l'aggiunta verrà eseguita con i valori non firmati, determinando un result grandi dimensioni (in base ai valori di u e i ).

Risposta lunga

Secondo lo standard C99:

6.3.1.8 Conversioni aritmetiche usuali

  1. Se entrambi gli operandi hanno lo stesso tipo, non è necessaria alcuna ulteriore conversione.
  2. Altrimenti, se entrambi gli operandi hanno tipi interi con segno o entrambi hanno tipi interi senza segno, l'operando con il tipo di rango di conversione intero minore viene convertito nel tipo dell'operando con un rango più grande.
  3. Altrimenti, se l'operando che ha un numero intero senza segno ha rank superiore o uguale al rank del tipo di un altro operando, allora l'operando con tipo intero con segno viene convertito nel tipo dell'operando con tipo intero senza segno.
  4. Altrimenti, se il tipo dell'operando con tipo intero con segno può rappresentare tutti i valori del tipo dell'operando con tipo intero senza segno, allora l'operando con tipo intero senza segno viene convertito nel tipo dell'operando con tipo intero con segno.
  5. In caso contrario, entrambi gli operandi vengono convertiti nel tipo intero senza segno corrispondente al tipo di operando con tipo intero con segno.

Nel tuo caso, abbiamo un int ( u ) unsigned e firmato ( i ). Riferendosi a (3) sopra, poiché entrambi gli operandi hanno lo stesso valore, sarà necessario convertirli in un numero intero senza segno.

6.3.1.3 Numeri interi firmati e non firmati

  1. Quando un valore con tipo intero viene convertito in un altro tipo intero diverso da _Bool, se il valore può essere rappresentato dal nuovo tipo, non viene modificato.
  2. Altrimenti, se il nuovo tipo non è firmato, il valore viene convertito aggiungendo o sottraendo ripetutamente un valore superiore al valore massimo che può essere rappresentato nel nuovo tipo finché il valore non si trova nell'intervallo del nuovo tipo.
  3. Altrimenti, il nuovo tipo è firmato e il valore non può essere rappresentato in esso; o il risultato è definito dall'implementazione o viene generato un segnale definito dall'implementazione.

Ora dobbiamo fare riferimento a (2) sopra. Il tuo i sarà convertito in un valore senza segno aggiungendo UINT_MAX + 1 . Quindi il risultato dipenderà dal modo in cui UINT_MAX è definito sulla tua implementazione. Sarà grande, ma non traboccherà perché:

6.2.5 (9)

Un calcolo che coinvolge operandi senza segno non può mai overflow, perché un risultato che non può essere rappresentato dal tipo di intero senza segno risultante viene ridotto modulo il numero che è maggiore del valore più grande che può essere rappresentato dal tipo risultante.

Bonus: Conversione aritmetica Semi-WTF

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

Puoi usare questo link per provare questo in linea: http://codepad.org/yPhYCMFO

Bonus: effetto collaterale di conversione aritmetica

Le regole di conversione aritmetica possono essere utilizzate per ottenere il valore di UINT_MAX inizializzando un valore senza segno su -1 , ovvero:

unsigned int umax = -1; // umax set to UINT_MAX

Ciò è garantito per essere portatile indipendentemente dalla rappresentazione numerica firmata del sistema a causa delle regole di conversione sopra descritte. Vedi questa domanda SO per maggiori informazioni: è sicuro usare -1 per impostare tutti i bit su true?


Risposte orribili a bizzeffe

Ozgur Ozcitak

Quando si esegue il cast da firmato a non firmato (e viceversa) la rappresentazione interna del numero non cambia. Ciò che cambia è il modo in cui il compilatore interpreta il bit del segno.

Questo è completamente sbagliato.

Mats Fredriksson

Quando vengono aggiunte una variabile non firmata e una firmata (o qualsiasi operazione binaria), entrambe sono convertite implicitamente in unsigned, il che in questo caso determinerebbe un risultato enorme.

Anche questo è sbagliato. Gli interi non firmati possono essere promossi a ints se hanno una precisione uguale a causa dei bit di riempimento nel tipo unsigned.

SMH

L'operazione di aggiunta fa convertire int a un int unsigned.

Sbagliato. Forse lo fa e forse no.

La conversione da int unsigned a signed sign è dipendente dall'implementazione. (Ma probabilmente funziona come ti aspetti sulla maggior parte delle piattaforme in questi giorni).

Sbagliato. È un comportamento non definito se provoca un overflow o il valore è preservato.

Anonimo

Il valore di i viene convertito in unsigned int ...

Sbagliato. Dipende dalla precisione di un int rispetto a un int unsigned.

Taylor Price

Come è stato precedentemente risposto, è possibile eseguire il cast avanti e indietro tra firmato e non firmato senza problemi.

Sbagliato. Cercando di memorizzare un valore al di fuori dell'intervallo di un numero intero con segno si ottiene un comportamento non definito.

Ora posso finalmente rispondere alla domanda.

Se la precisione di int è uguale a unsigned int, verrai promosso a un int firmato e otterrai il valore -4444 dall'espressione (u + i). Ora, se tu e io abbiamo altri valori, potresti ottenere un overflow e un comportamento indefinito, ma con quei numeri esatti otterrai -4444 [1] . Questo valore avrà tipo int. Ma si sta tentando di memorizzare quel valore in un int unsigned in modo che venga quindi eseguito il cast su un int unsigned e il valore che il risultato avrà come risultato (UINT_MAX + 1) - 4444.

Se la precisione di unsigned int è maggiore di quella di un int, l'int firmato sarà promosso a un int unsigned restituendo il valore (UINT_MAX + 1) - 5678 che verrà aggiunto all'altro int uns 1234. Se dovessimo avere altri valori, che rendono l'espressione non compresa nell'intervallo {0..UINT_MAX} il valore (UINT_MAX + 1) verrà aggiunto o sottratto finché il risultato NON rientra nell'intervallo {0..UINT_MAX) e non si verificherà alcun comportamento indefinito .

Cos'è la precisione?

Gli integer hanno bit di riempimento, bit di segno e bit di valore. Gli interi senza segno non hanno ovviamente un segno. È inoltre garantito che il char senza segno non abbia bit di riempimento. Il numero di bit di bit di un intero è la precisione con cui ha.

[Trucchi]

La macro sizeof della sola macro non può essere utilizzata per determinare la precisione di un intero se sono presenti bit di riempimento. E la dimensione di un byte non deve essere un ottetto (otto bit) come definito da C99.

[1] L'overflow può verificarsi in uno dei due punti. Prima dell'aggiunta (durante la promozione) - quando si ha un int unsigned che è troppo grande per rientrare in un int. L'overflow può verificarsi anche dopo l'aggiunta, anche se l'int non firmato era compreso nell'intervallo di un int, dopo l'aggiunta il risultato potrebbe ancora traboccare.

Su una nota non correlata, sono uno studente neolaureato che cerca di trovare lavoro;)







type-conversion