in che modo il compilatore c gestisce il numero intero senza segno e con segno? Perché il codice assembly per operazioni aritmetiche non firmate e firmate è lo stesso?

Sto leggendo il libro: CS-APPe2. C ha un tipo int unsigned e firmato e nella maggior parte delle architetture usa l’aritmetica a due complementi per implementare il valore firmato; ma dopo aver appreso qualche codice di assemblaggio, ho trovato che pochissime istruzioni distinguono tra unsigned e signed. Quindi la mia domanda è:

  1. È responsabilità del compilatore distinguere tra firmato e non firmato? Se sì, come fa?

  2. Chi implementa l’aritmetica dei complementi a due: la CPU o il compilatore?

Aggiungi altre informazioni:

Dopo aver appreso qualche altra istruzione, in realtà ci sono alcuni di loro che distinguono tra firmato e non firmato, come setg, seta, ecc. Inoltre, CF e OF si applicano a unsigned e rispettivamente. Ma la maggior parte delle istruzioni aritmetiche intere trattano le firme non firmate e firmate allo stesso modo, ad es

int s = a + b 

e

 unsigned s = a + b 

genera la stessa istruzione

Quindi, quando si esegue l’ ADD sd , la CPU dovrebbe trattare s & d senza firma o firmata? Oppure è irrilevante, perché il pattern di bit di entrambi i risultati è lo stesso ed è compito del compilatore convertire il risultato del pattern di bit sottostante in unsigned o signed?

PS sto usando x86 e gcc

È abbastanza facile Operazioni come addizione e sottrazione non richiedono alcuna regolazione per i tipi firmati nell’aritmetica del complemento a due. Esegui un esperimento mentale e immagina un algoritmo usando solo le seguenti operazioni matematiche:

  • incrementare di uno
  • decremento di uno
  • confrontare con zero

L’addizione consiste nel prendere gli elementi uno per uno da un heap e metterli nell’altro heap fino a quando il primo è vuoto. La sottrazione sta prendendo da entrambi in una volta, finché quella sottratta non è vuota. Nell’aritmetica modulare, si tratta solo del valore più piccolo come valore più grande più uno e funziona. Il complemento a due è solo un’aritmetica modulare in cui il valore più piccolo è negativo.

Se vuoi vedere qualche differenza, ti consiglio di provare le operazioni che non sono sicure rispetto all’overflow. Un esempio è il confronto ( a < b ).

È responsabilità del compilatore distinguere tra firmato e non firmato? Se sì, come fa?

Generando assemblaggi diversi quando necessario.

Chi implementa l'aritmetica dei complementi a due: la CPU o il compilatore?

È una domanda difficile. Il complemento a due è probabilmente il modo più naturale per lavorare con numeri interi negativi in ​​un computer. La maggior parte delle operazioni per il complemento a due con overflow sono le stesse degli interi senza segno con overflow. Il segno può essere estratto da un singolo bit. Il confronto può essere concettualmente fatto per sottrazione (che è firmness-agnostic), sign bit estrazione e confronto a zero.

Sono le caratteristiche aritmetiche della CPU che consentono al compilatore di produrre calcoli in complemento a due, però.

unsigned s = a + b

Si noti che il modo in cui plus è calcolato qui non dipende dal tipo di risultato. Insead dipende dai tipi di variabili a destra del segno di uguale.

Quindi, quando si esegue l'aggiunta di sd, la CPU dovrebbe trattare s & d senza firma o firmata?

Le istruzioni della CPU non conoscono i tipi, quelli sono usati solo dal compilatore. Inoltre, non vi è alcuna differenza tra l'aggiunta di due numeri non firmati e l'aggiunta di due numeri firmati. Sarebbe stupido avere due istruzioni per la stessa operazione.

In molti casi non vi è alcuna differenza a livello di macchina tra operazioni firmate e non firmate e si tratta semplicemente di una questione di interpretazione del modello di bit. Ad esempio, considera la seguente operazione di parole a 4 bit:

 Binary Add Unsigned 2's comp ---------- -------- -------- 0011 3 3 + 1011 + 11 - 5 ------- -------- -------- 1110 14 -2 ------- -------- -------- 

Il modello binario è lo stesso per l’operazione firmata e non firmata. Si noti che la sottrazione è semplicemente l’aggiunta di un valore negativo. Quando viene eseguita un’operazione SUB, l’operando di destra è completato da due (bit invertito e incremento) e aggiunto (il circuito ALU responsabile è un sumtore ); non a livello di istruzione si capisce, ma a livello logico, anche se sarebbe ansible implementare una macchina senza un’istruzione SUB e comunque eseguire la sottrazione anche se in due istruzioni anziché una.

Ci sono alcune operazioni che richiedono istruzioni diverse a seconda del tipo, ed è responsabilità del compilatore generare genericamente il codice appropriato – possono essere applicate variazioni architettoniche.

Non c’è bisogno di differenziare gli input firmati e non firmati per la maggior parte delle operazioni aritmetiche / logiche. Spesso è necessario prendere in considerazione il segno solo quando si stampa, zero / firma estendendo o confrontando valori. In effetti la CPU non sa nulla del tipo di un valore. Un valore di 4 byte è solo una serie di bit, non ha alcun significato a meno che l’utente non indichi che è un float, una matrice di 4 caratteri, un int unsigned o firmato, ecc. Ad esempio quando si stampa una variabile char a seconda del tipo e delle proprietà di output indicate, stamperà il carattere, il numero intero senza segno o il numero intero con segno. È responsabilità del programmatore mostrare al compilatore come trattare quel valore e quindi il compilatore emetterà le istruzioni corrette necessarie per elaborare il valore.

Questo mi ha disturbato troppo a lungo. Non ho saputo come il compilatore funziona come un programma mentre si gestiscono le sue impostazioni predefinite e le istruzioni implicite. Ma la mia ricerca di una risposta mi ha portato a conclusioni seguenti:

Il mondo reale usa solo interi con segno, dalla scoperta di numeri negativi. questa è la ragione per cui int è considerato un intero con segno predefinito nel compilatore. Ignorerò totalmente l’aritmetica numerica senza segno poiché è inutile.

La CPU non ha indizi di interi firmati e non firmati. Sa solo i bit – 0 e 1. come interpreti il ​​suo output dipende da te come programmatore di assembly. Ciò rende noioso programmare l’assemblaggio. Gestire i numeri interi (firmati e non firmati) implicava molto il controllo dei flag. Ecco perché sono stati sviluppati linguaggi di alto livello. il compilatore prende tutto il dolore.

Come funziona il compilatore è un apprendimento molto avanzato. Ho accettato che al momento è fuori dalla mia comprensione. Questa accettazione mi ha aiutato ad andare avanti nel mio corso.

Nell’architettura x86:

aggiungere e sotto istruzioni modificare i flag nel registro eflags. Questi flag possono quindi essere utilizzati insieme alle istruzioni adc e sbb per creare aritmetica con maggiore precisione. In tal caso, spostiamo la dimensione dei numeri nel registro di ecx. Il numero di volte che viene eseguita l’istruzione loop è uguale alla dimensione dei numeri in byte.

L’istruzione secondaria prende il complemento a 2 del sottraendo, lo aggiunge al minuend, inverte il carry. Questo è fatto in hardware (implementato in circuito). L’istruzione secondaria “triggers” un altro circuito. Dopo aver usato l’istruzione secondaria, il programmatore o il compilatore controlla la CF. Se è 0, il risultato è positivo e la destinazione ha il risultato corretto. Se è 1, il risultato è negativo e la destinazione ha il complemento a 2 del risultato. Normalmente, il risultato viene lasciato nel complemento a 2 e letto come un numero firmato, ma le istruzioni NOT e INC possono essere utilizzate per cambiarlo. L’istruzione NOT esegue il complemento 1 dell’operando, quindi l’operando viene incrementato per ottenere il complemento a 2.

Quando un programmatore ha pianificato di leggere il risultato di un’istruzione add o sub come un numero firmato, deve guardare la bandiera OF. Se è impostato 1, il risultato è sbagliato. Dovrebbe firmare-estendere i numeri prima di eseguire l’operazione tra di loro.

Molto è stato detto sulla tua prima domanda, ma mi piace dire qualcosa sulla tua seconda domanda:

Chi implementa l’aritmetica dei complementi a due: la CPU o il compilatore?

Lo standard C non richiede numeri negativi per avere il complemento a due, non definisce affatto come l’hardware esprima numeri negativi. Il compito del compilatore è tradurre il tuo codice C in istruzioni della CPU che fanno ciò che il tuo codice richiede. Quindi, se il compilatore C creerà il codice per l’aritmetica a complemento a due o no dipende unicamente dal fatto se la CPU utilizza l’aritmetica a complemento a due o meno. Il compilatore deve sapere come funziona la CPU e creare il codice di conseguenza. Quindi la risposta corretta a questa domanda è: la CPU.

Se la tua CPU stava usando una rappresentazione del complemento a un altro, allora un compilatore C per quella CPU emetterebbe istruzioni sul complemento a uno. D’altra parte, un compilatore C può emulare il supporto per numeri negativi su una CPU che non conosce affatto numeri negativi. Dato che il complemento a due ti consente di ignorare se un numero è firmato o non firmato per molte operazioni, questo non è troppo difficile da fare. In tal caso sarebbe stato il compilatore a implementare l’aritmetica dei complementi a due. Questo potrebbe anche essere fatto su una CPU che ha una rappresentazione per i numeri negativi, ma perché il compilatore dovrebbe farlo e non solo usare la forma nativa che la CPU capisce? Quindi non lo farà a meno che non sia necessario.