Domanda

Supponiamo di avere il seguente codice C.

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

unsigned int result = u + i;

Quali conversioni implicite stanno accadendo qui e questo codice è sicuro per tutti i valori di u E i?(Sicuro, nel senso che anche se risultato in questo esempio traboccherà in un enorme numero positivo, potrei riportarlo a an int e ottieni il risultato reale.)

È stato utile?

Soluzione

Risposta breve

Tuo i sarà convertito a un numero intero senza segno aggiungendo UINT_MAX + 1, l'addizione verrà eseguita con i valori senza segno, ottenendo un valore grande result (a seconda dei valori di u E i).

Risposta lunga

Secondo la norma 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 con rango di conversione intero minore viene convertito nel tipo dell'operando con rango maggiore.
  3. Altrimenti, se l'operando con tipo intero senza segno ha rango maggiore o uguale al rango del tipo dell'altro operando, 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. Altrimenti, entrambi gli operandi vengono convertiti nel tipo intero senza segno corrispondente al tipo dell'operando con tipo intero con segno.

Nel tuo caso, abbiamo un int senza segno (u) e firmato int (i).Facendo riferimento al punto (3) sopra, poiché entrambi gli operandi hanno lo stesso rango, your i dovrà essere convertito a un numero intero senza segno.

6.3.1.3 Interi con segno e senza segno

  1. Quando un valore di tipo intero viene convertito in un altro tipo intero diverso da _Bool, se il valore può essere rappresentato dal nuovo tipo, rimane invariato.
  2. Altrimenti, se il nuovo tipo non è firmato, il valore viene convertito aggiungendo o sottraendo ripetutamente uno in più rispetto al valore massimo che può essere rappresentato nel nuovo tipo finché il valore non rientra nell'intervallo del nuovo tipo.
  3. Altrimenti il ​​nuovo tipo viene firmato e il valore non può essere rappresentato al suo interno;o il risultato è definito dall'implementazione oppure viene generato un segnale definito dall'implementazione.

Ora dobbiamo fare riferimento al punto (2) sopra.Tuo i verrà convertito in un valore senza segno aggiungendo UINT_MAX + 1.Quindi il risultato dipenderà da come UINT_MAX è definito nell'implementazione.Sarà grande, ma non traboccherà, perché:

6.2.5 (9)

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

Bonus:Semi-WTF di conversione aritmetica

#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 provarlo online: https://repl.it/repls/QuickWhimsicalBytes

Bonus:Effetto collaterale della conversione aritmetica

È possibile utilizzare le regole di conversione aritmetica per ottenere il valore di UINT_MAX inizializzando un valore senza segno su -1, cioè:

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

È garantito che questo sia portabile indipendentemente dalla rappresentazione del numero con segno del sistema a causa delle regole di conversione sopra descritte.Vedi questa domanda SO per ulteriori informazioni: È sicuro usare -1 per impostare tutti i bit su vero?

Altri suggerimenti

La conversione da firmato a non firmato funziona non necessariamente basta copiare o reinterpretare la rappresentazione del valore con segno.Citando lo standard C (C99 6.3.1.3):

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, è invariato.

Altrimenti, se il nuovo tipo non è firmato, il valore viene convertito aggiungendo o sottraendo ripetutamente uno più del valore massimo che può essere rappresentato nel nuovo tipo fino a quando il valore non è nell'intervallo del nuovo tipo.

Altrimenti il ​​nuovo tipo viene firmato e il valore non può essere rappresentato al suo interno;Il risultato è definito dall'implementazione o viene sollevato un segnale definito dall'implementazione.

Per la rappresentazione del complemento a due, che è quasi universale oggigiorno, le regole corrispondono alla reinterpretazione dei bit.Ma per altre rappresentazioni (segno e grandezza o complemento a uno), l'implementazione del C deve comunque garantire lo stesso risultato, il che significa che la conversione non può semplicemente copiare i bit.Ad esempio, (unsigned)-1 == UINT_MAX, indipendentemente dalla rappresentazione.

In generale, le conversioni in C sono definite per operare sui valori, non sulle rappresentazioni.

Per rispondere alla domanda originale:

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

unsigned int result = u + i;

Il valore di i viene convertito in unsigned int, ottenendo UINT_MAX + 1 - 5678.Questo valore viene quindi aggiunto al valore senza segno 1234, ottenendo UINT_MAX + 1 - 4444.

(A differenza dell'overflow senza segno, l'overflow con segno richiama un comportamento indefinito.Il wraparound è comune, ma non è garantito dallo standard C e le ottimizzazioni del compilatore possono devastare il codice che fa supposizioni ingiustificate.)

Riferito a la Bibbia:

  • L'operazione di addizione fa sì che l'int venga convertito in un int senza segno.
  • Supponendo la rappresentazione in complemento a due e tipi di uguali dimensioni, lo schema di bit non cambia.
  • La conversione da unsigned int a Signed Int dipende dall'implementazione.(Ma probabilmente funziona come ti aspetti sulla maggior parte delle piattaforme al giorno d'oggi.)
  • Le regole sono un po' più complicate nel caso in cui si combinino segni e non segni di dimensioni diverse.

Quando vengono aggiunte una variabile senza segno e una con segno (o qualsiasi operazione binaria), entrambe vengono convertite implicitamente in senza segno, il che in questo caso comporterebbe un risultato enorme.

Quindi è sicuro nel senso che il risultato potrebbe essere enorme e sbagliato, ma non andrà mai in crash.

Quando si converte da firmato a senza segno ci sono due possibilità.I numeri originariamente positivi rimangono (o vengono interpretati come) lo stesso valore.I numeri originariamente negativi verranno ora interpretati come numeri positivi più grandi.

Come è stato risposto in precedenza, puoi passare avanti e indietro tra firmato e non firmato senza problemi.Il caso limite per gli interi con segno è -1 (0xFFFFFFFF).Prova ad aggiungere e sottrarre da ciò e scoprirai che puoi tornare indietro e renderlo corretto.

Tuttavia, se hai intenzione di fare avanti e indietro, ti consiglio vivamente di nominare le tue variabili in modo tale che sia chiaro di che tipo sono, ad esempio:

int iValue, iResult;
unsigned int uValue, uResult;

È fin troppo facile distrarsi da questioni più importanti e dimenticare quale variabile è di quale tipo se vengono nominate senza alcun suggerimento.Non vuoi eseguire il cast su un unsigned e quindi utilizzarlo come indice di array.

Quali conversioni implicite stanno accadendo qui,

verrò convertito in un numero intero senza segno.

e questo codice è sicuro per tutti i valori di u e i?

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

Le regole sono scritte in un linguaggio standard tipicamente difficile da leggere, ma essenzialmente qualunque sia la rappresentazione utilizzata nell'intero con segno, l'intero senza segno conterrà una rappresentazione in complemento a 2 del numero.

L'addizione, la sottrazione e la moltiplicazione funzioneranno correttamente su questi numeri risultando in un altro numero intero senza segno contenente un numero in complemento a due che rappresenta il "risultato reale".

la divisione e il casting su tipi interi senza segno più grandi avranno risultati ben definiti ma tali risultati non saranno rappresentazioni in complemento a 2 del "risultato reale".

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

Mentre le conversioni da con segno a senza segno 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 in complemento a 2 memorizzato in un intero senza segno in un intero con segno .Mi aspetto che troverai solo altri comportamenti su sistemi oscuri che non utilizzano il complemento a 2 per gli interi con segno.

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

Risposte orribili a bizzeffe

Ozgur Ozcitak

Quando si lancia da firmata a non firmata (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 una variabile non firmata e una firma viene aggiunta (o qualsiasi operazione binaria) entrambi vengono implicitamente convertiti in unsigni, il che in questo caso comporterebbe un risultato enorme.

Anche questo è sbagliato.Gli interi senza segno possono essere promossi a interi se hanno la stessa precisione a causa dei bit di riempimento nel tipo senza segno.

smh

L'operazione di addizione fa sì che l'INT venga convertito in un INT non firmato.

Sbagliato.Forse lo fa e forse no.

La conversione da INT non firmata a INT firmata è dipendente dall'implementazione.(Ma probabilmente funziona come ti aspetti sulla maggior parte delle piattaforme in questi giorni.)

Sbagliato.Si tratta di un comportamento indefinito se provoca overflow o se il valore viene conservato.

Anonimo

Il valore di I viene convertito in int senza segno ...

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

Prezzo Taylor

Come in precedenza, è possibile lanciare avanti e indietro tra firmato e non firmato senza problemi.

Sbagliato.Il tentativo di memorizzare un valore esterno all'intervallo di un intero con segno determina un comportamento indefinito.

Ora posso finalmente rispondere alla domanda.

Se la precisione di int è uguale a unsigned int, u verrà promosso a Signed Int e otterrai il valore -4444 dall'espressione (u+i).Ora, se tu ed io avessimo altri valori, potresti ottenere un overflow e un comportamento indefinito ma con quei numeri esatti otterrai -4444 [1].Questo valore avrà tipo int.Ma stai provando a memorizzare quel valore in un unsigned int in modo che venga poi convertito in un unsigned int e il valore che il risultato finirà per avere sarebbe (UINT_MAX+1) - 4444.

Se la precisione di unsigned int è maggiore di quella di un int, ilsigned int verrà promosso a unsigned int ottenendo il valore (UINT_MAX+1) - 5678 che verrà aggiunto all'altro unsigned int 1234.Se tu e i avessimo altri valori, che fanno sì che l'espressione non rientri 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 interi hanno bit di riempimento, bit di segno e bit di valore.Ovviamente gli interi senza segno non hanno un bit di segno.È inoltre garantito che il carattere senza segno non contenga bit di riempimento.Il numero di bit di valore di un numero intero indica la sua precisione.

[Trabocchetti]

La dimensione della macro da sola non può essere utilizzata per determinare la precisione di un numero 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.O prima dell'aggiunta (durante la promozione), quando hai un int senza segno che è troppo grande per adattarsi a un int.L'overflow può verificarsi anche dopo l'addizione anche se l'unsigned int era compreso nell'intervallo di un int; dopo l'addizione il risultato potrebbe comunque eccedere.


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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top