Non è una scorciatoia.
Il +=
simbolo apparve nel linguaggio C negli anni '70 e, con l'idea C di "assemblatore intelligente", corrisponde a una modalità di istruzione e indirizzo chiaramente diversa:
Cose come " i=i+1
", "i+=1
"e" ++i
", sebbene a livello astratto producano lo stesso effetto, corrispondono a basso livello a un diverso modo di lavorare del processore.
In particolare quelle tre espressioni, supponendo che la i
variabile risieda nell'indirizzo di memoria memorizzato in un registro CPU (chiamiamolo D
- pensiamolo come un "puntatore a int") e l' ALU del processore accetta un parametro e restituisce un risultato in un "accumulatore" (chiamiamolo A - pensaci come un int).
Con questi vincoli (molto comuni in tutti i microprocessori di quel periodo), molto probabilmente la traduzione sarà
;i = i+1;
MOV A,(D); //Move in A the content of the memory whose address is in D
ADD A, 1; //The addition of an inlined constant
MOV (D) A; //Move the result back to i (this is the '=' of the expression)
;i+=1;
ADD (D),1; //Add an inlined constant to a memory address stored value
;++i;
INC (D); //Just "tick" a memory located counter
Il primo modo di farlo è non ottimale, ma è più generale quando si opera con variabili anziché costanti ( ADD A, B
o ADD A, (D+x)
) o quando si traducono espressioni più complesse (tutte si riducono in un'operazione a bassa priorità in uno stack, chiamano la priorità alta, pop e ripetere fino a quando tutti gli argomenti sono stati eliminati).
Il secondo è più tipico di "macchina a stati": non stiamo più "valutando un'espressione", ma "gestendo un valore": usiamo ancora la ALU, ma evitiamo di spostare i valori perché il risultato ci consente di sostituire il parametro. Questo tipo di istruzione non può essere utilizzato dove sono richieste espressioni più complicate: i = 3*i + i-2
non può essere utilizzato sul posto, poiché i
è richiesto più volte.
Il terzo - anche più semplice - non considera nemmeno l'idea di "addizione", ma usa un circuito più "primitivo" (in senso computazionale) per un contatore. L'istruzione è in cortocircuito, carica più velocemente ed esegue immediatamente, poiché la rete combinatoria richiesta per il retrofit di un registro per renderlo un contatore è più piccola, e quindi più veloce di quella di un full-adder.
Con i compilatori contemporanei (fare riferimento a C, ormai), che consente l'ottimizzazione del compilatore, la corrispondenza può essere scambiata in base alla convenienza, ma c'è ancora una differenza concettuale nella semantica.
x += 5
si intende
- Trova il luogo identificato da x
- Aggiungi 5 ad esso
Ma x = x + 5
significa:
- Valuta x + 5
- Trova il luogo identificato da x
- Copia x in un accumulatore
- Aggiungi 5 all'accumulatore
- Memorizza il risultato in x
- Trova il luogo identificato da x
- Copia l'accumulatore su di esso
Certo, l'ottimizzazione può
- se "trovare x" non ha effetti collaterali, i due "trovare" possono essere fatti una volta (e x diventa un indirizzo memorizzato in un registro puntatore)
- le due copie possono essere eluse se si applica ADD al
&x
posto dell'accumulatore
rendendo così il codice ottimizzato in coincidenza con x += 5
quello.
Ma questo può essere fatto solo se "trovare x" non ha effetti collaterali, altrimenti
*(x()) = *(x()) + 5;
e
*(x()) += 5;
sono semanticamente diversi, poiché x()
gli effetti collaterali (ammettere x()
è una funzione che fa cose strane in giro e restituire un int*
) saranno prodotti due o una volta.
L'equivalenza tra x = x + y
e x += y
è quindi dovuta al caso particolare in cui +=
e =
sono applicati a un valore l diretto.
Per passare a Python, ha ereditato la sintassi da C, ma poiché non c'è traduzione / ottimizzazione PRIMA dell'esecuzione in linguaggi interpretati, le cose non sono necessariamente così intimamente correlate (poiché c'è un passaggio di analisi in meno). Tuttavia, un interprete può fare riferimento a diverse routine di esecuzione per i tre tipi di espressione, sfruttando diversi codici macchina a seconda di come si forma l'espressione e del contesto di valutazione.
Per chi ama più dettagli ...
Ogni CPU ha una ALU (unità aritmetica-logica) che è, nella sua essenza, una rete combinatoria i cui input e output sono "collegati" ai registri e / o alla memoria a seconda del codice operativo dell'istruzione.
Le operazioni binarie sono in genere implementate come "modificatore di un registro accumulatore con un input preso" da qualche parte ", dove un posto può trovarsi - all'interno del flusso di istruzioni stesso (tipico per il contendente manifest: ADD A 5) - all'interno di un altro registro (tipico per il calcolo dell'espressione con provvisori: ad es. ADD AB) - all'interno della memoria, a un indirizzo fornito da un registro (tipico del recupero dei dati, ad es .: ADD A (H)) - H, in questo caso, funziona come un puntatore a dereferenziazione.
Con questo pseudocodice, x += 5
è
ADD (X) 5
mentre lo x = x+5
è
MOVE A (X)
ADD A 5
MOVE (X) A
Cioè, x + 5 fornisce un temporaneo che viene successivamente assegnato. x += 5
opera direttamente su x.
L'implementazione effettiva dipende dal set di istruzioni reale del processore: se non esiste un ADD (.) c
codice operativo, il primo codice diventa il secondo: impossibile.
Se esiste un tale codice operativo e l'ottimizzazione è abilitata, la seconda espressione, dopo aver eliminato i movimenti inversi e regolato il codice operativo dei registri, diventa la prima.
x += 5
rispetto ax = x + 5
? O è davvero solo zucchero sintattico come suggerisci?