Domanda

In C ++, variabili locali vengono sempre allocati sulla pila. Lo stack è una parte della memoria ha permesso che l'applicazione può occupare. Quel ricordo è conservato nella RAM (se non scambiato su disco). Ora, fa un compilatore C ++ di creare sempre il codice assembler che memorizza le variabili locali nello stack?

Si prenda, ad esempio, il seguente codice semplice:

int foo( int n ) {
   return ++n;
}

In MIPS codice assembler, questa potrebbe essere la seguente:

foo:
addi $v0, $a0, 1
jr $ra

Come si può vedere, non ho bisogno di usare lo stack a tutti per n. Sarebbe il compilatore C ++ riconoscere che, e utilizzare direttamente i registri della CPU?

Modifica Wow, grazie mille per le vostre risposte quasi immediate ed estese! Il corpo della funzione di foo dovrebbe naturalmente essere return ++n;, non return n++;. :)

È stato utile?

Soluzione

Disclaimer: Non lo so MIPS, ma so un po 'X 86, e penso che il principio dovrebbe essere lo stesso ..

Nella consueta convenzione chiamata di funzione, il compilatore spingerà il valore n nello stack di passare alla funzione foo. Tuttavia, v'è la convenzione fastcall che è possibile utilizzare per dire a gcc per passare il valore attraverso i registri, invece. (MSVC ha anche questa opzione, ma non sono sicuro di quello che la sua sintassi è.)

test.cpp:

int foo1 (int n) { return ++n; }
int foo2 (int n) __attribute__((fastcall));
int foo2 (int n) {
    return ++n;
}

La compilazione del di cui sopra con g++ -O3 -fomit-frame-pointer -c test.cpp, ottengo per foo1:

mov eax,DWORD PTR [esp+0x4]
add eax,0x1
ret

Come si può vedere, si legge nel valore dallo stack.

Ed ecco foo2:

lea eax,[ecx+0x1]
ret

Ora si prende il valore direttamente dal registro.

Naturalmente, se la funzione inline il compilatore farà una semplice aggiunta nel corpo della funzione più ampia, a prescindere dalla convenzione di chiamata specificato. Ma quando non è possibile ottenere inline, questo sta per accadere.

Diniego 2: non sto dicendo che si dovrebbe continuamente di indovinare il compilatore. Probabilmente non è pratico e necessario nella maggior parte dei casi. Ma non date per scontato che produce codice perfetto.

Modifica 1:. Se si sta parlando di variabili locali semplici (non funzionare argomenti), allora sì, il compilatore li allocare nei registri o in pila come meglio ritiene opportuno

Modifica 2: Sembra che la convenzione di chiamata è specifica per l'architettura, e MIPS passerà i primi quattro argomenti sullo stack, come Richard Pennington ha dichiarato nella sua risposta. Quindi nel tuo caso non è necessario specificare l'attributo in più (che è in realtà un attributo specifico per x86).

Altri suggerimenti

Sì. Non c'è una regola che "le variabili sono sempre allocate sullo stack". Lo standard C ++ non dice nulla circa uno stack.It non presuppone che esista una pila, o che esistono registri. Dice proprio come il codice deve comportarsi, non come dovrebbe essere implementato.

Il compilatore memorizza solo le variabili nello stack quando si ha a che - quando devono vivere oltre una chiamata di funzione per esempio, o se si tenta di prendere l'indirizzo del loro

.

Il compilatore non è stupido. ;)

Sì, una buona, ottimizzando C / C ++ che ottimizzerà. E anche MUCH di più: Vedi qui: Felix von Leitners Compiler Indagine .

Un normale C / C ++ non metterà tutte le variabili nello stack in ogni caso. Il problema con la funzione foo() potrebbe essere che la variabile potrebbe ottenere passato tramite lo stack per la funzione (l'ABI del sistema (hardware / OS) definisce quello).

Con la parola chiave register di C si può dare il compilatore di un suggerimento che probabilmente sarebbe bene per memorizzare una variabile in un registro. Esempio:

register int x = 10;

Ma ricordate: Il compilatore è libero di non memorizzare x in un registro se vuole

!

La risposta è sì, forse. Dipende dal compilatore, il livello di ottimizzazione, e il processore di destinazione.

Nel caso dei mips, i primi quattro parametri, se piccola, sono passati in registri e il valore di ritorno viene restituito in un registro. Così il vostro esempio ha alcun obbligo di destinare nulla sullo stack.

In realtà, la verità è più strana della finzione. Nel tuo caso il parametro viene restituito invariato: il valore restituito è quello di n prima che l'operatore ++:

foo:
    .frame  $sp,0,$ra
    .mask   0x00000000,0
    .fmask  0x00000000,0

    addu    $2, $zero, $4
    jr      $ra
    nop

Dal momento che la funzione di esempio foo è una funzione identità (solo restituisce è argomento), il mio compilatore C ++ (VS 2008) elimina completamente questa chiamata di funzione. Se lo cambio a:

int foo( int n ) {
   return ++n;
}

i inline del compilatore questo con

lea edx, [eax+1] 

Si, I registri sono utilizzati in C ++. La MDR (registri di dati di memoria) contiene i dati che vengono recuperati e memorizzati. Ad esempio, per recuperare il contenuto della cella 123, avremmo caricare il valore 123 (in binario) in MAR ed eseguire un'operazione di recupero. Quando l'operazione è fatto, una copia del contenuto della cella 123 sarebbe in MDR. Per memorizzare il valore 98 nella cella 4, si carica un 4 nel MAR e 98 nel MDR ed eseguire un negozio. Quando l'operazione viene completata il contenuto della cella 4 sarà stato impostato a 98, scartando qualsiasi c'era precedentemente. I registri di dati e indirizzo di lavoro con loro per raggiungere questo obiettivo. In C ++ anche, quando si inizializza un var con un valore o chiedere il suo valore, lo stesso fenomeno accade.

E, un'altra cosa, Moderno compilatori anche eseguire l'allocazione dei registri, che è un pò più veloce di allocazione di memoria.

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