Perché il compilatore C usa la memoria per semplici operazioni aritmetiche

-5

Assumi il seguente codice C :

#include <stdio.h>

int main()
{
    int a = 5;
    int b = 15;
    return a + b;
}

La compilazione mediante gcc crea un codice assembly che include quanto segue:

movl    $5, -8(%rbp)
movl    $15, -12(%rbp)
movl    -8(%rbp), %eax
addl    -12(%rbp), %eax

Perché il compilatore sposta 5 e 15 in una posizione di memoria (stack credo) prima di fare l'aggiunta? Perché non sta passando i valori direttamente a un registro della CPU ed eseguendo l'aggiunta lì?

    
posta Joes 23.09.2018 - 00:33
fonte

1 risposta

2

Il codice che stai mostrando non è ottimizzato. Se vuoi vedere un buon codice, usa -O2 o -O3. Lo scopo del codice non ottimizzato è di funzionare meglio con il debugger: il codice ottimizzato può essere più difficile nel debugger nel trovare dove sono le variabili (e quando!).

Il pushq %rbp fa parte del prologo di funzione, che è un codice che crea uno stack frame o un record di attivazione. Una cornice di stack rappresenta l'istanziazione di una funzione o metodo, che avviene nel momento in cui la funzione viene invocata da qualche chiamante. Una funzione può avere zero, una o più istanziazioni, quest'ultima essendo il caso durante la ricorsione; le funzioni istanziate generalmente hanno frame di stack.

Tuttavia, quando la funzione è banale - in particolare, non chiama un'altra funzione (potremmo chiamarla una funzione foglia) - quindi (a seconda di altri fattori per ISA) non ha bisogno di un frame dello stack. Questo è un altro aspetto che potrebbe rendere le cose più difficili per il debugger; quindi, a meno che non venga richiesta l'ottimizzazione, viene creato uno stack frame anche da funzioni banali che altrimenti potrebbero non averne bisogno. (Alcune analisi, di solito eseguite come ottimizzazione, sono anche necessarie per determinare se lo stack frame è richiesto o può essere eliminato.)

Il frame dello stack ha anche due forme: (1) dove viene usato solo il puntatore dello stack e (2) dove vengono usati due puntatori, un puntatore dello stack e un puntatore del frame. Quest'ultima forma tende a facilitare il debug, e alcune caratteristiche dello stack unwinding e / o del lancio di eccezioni.

Nel formato frame stack a due registri, bp è il secondo registro ed è noto come puntatore del frame. Per creare un frame stack di questo tipo, il prologo della funzione del callee contiene istruzioni che salvano bp del chiamante (puntatore del frame) utilizzando tale istruzione push. Ora salvato, il registro bp viene preso in carico per diventare il puntatore del frame del callee copiando l'attuale sp in esso. Successivamente, lo spazio di stack per il chiamato viene assegnato sottraendo (con lo stack in basso) una dimensione costante (specifica per quella funzione) dal puntatore dello stack, sp .

Viene così creato un frame completo per il callee. Il callee può quindi memorizzare alcuni degli altri registri della CPU che contengono i valori del chiamante nel suo frame.

All'uscita, c'è l'opposto del prologo della funzione chiamato epilogue della funzione, che rilascia il frame dello stack e ripristina i registri del chiamante (incluso il puntatore del frame). L'operazione finale del callee è quindi di tornare al chiamante (usando ret ), e l'unica cosa rimasta nello stack, l'indirizzo di ritorno, viene spuntata durante il trasferimento del controllo al chiamante.

    
risposta data 23.09.2018 - 02:17
fonte

Leggi altre domande sui tag