Indirizzi di valore delle funzioni

0

Sto leggendo il libro di Yung-Hsiang Lu, Intermediate C Programming, e sto lavorando al capitolo sulla memoria stack. Quando si definisce l'indirizzo del valore, fornisce questo esempio di codice:

int f1(int k, int m) 
{
   return (k + m); 
} 

void f2(void) 
{ 
   int u; 
   u = f1(7, 2); 
   // RL A
}

Prosegue descrivendo come nell'esempio precedente, l'indirizzo di u viene memorizzato prima che f1 venga chiamato in quello che chiama l'indirizzo di valore perché è l'indirizzo in cui è memorizzata la funzione di ritorno di f1.

Nel frame di f2, u è memorizzato in un indirizzo (ad esempio 100) con un valore di tipo. Quindi quando viene chiamato f1, lo stack procede nel frame di f1 con una posizione di ritorno su RL A, un indirizzo di valore con valore 100 e indirizzi per mek. L'esecuzione di f1 produce il valore 9, che viene scritto nel valore spazzatura all'indirizzo 100. Quindi, dopo il frame pop di f1, lo stack di chiamate consisterà nel frame f2 con il simbolo u all'indirizzo 100 con il valore 9.

Fin qui tutto bene, ma dove non sono chiaro è l'indirizzo di valore del frame f1 se invece di u = f1 (7, 2) avessimo u = f1 (7, 2) + 5 o se eri una qualche altra funzione di f1 non uguale a f1. Non mi sembra che il valore di f1 verrebbe semplicemente scritto nel valore della spazzatura originale in questo caso, poiché u non è uguale a f1.

Quindi penso che forse non capisco cosa intenda esattamente con "l'indirizzo del valore".

Qualcuno può chiarire?

    
posta Hugo 20.05.2016 - 10:15
fonte

3 risposte

0

L'autore suona come se non capisse di cosa sta parlando - anche se il problema potrebbe essere nella traduzione piuttosto che nel pensiero dell'autore. O forse sta semplificando eccessivamente ai fini dell'esposizione, confondendo le persone che (come te) pensano effettivamente a ciò che stanno leggendo.

Qualsiasi descrizione di ciò che accade in termini hardware quando il codice C viene eseguito è privo di significato perché il progettista del compilatore può fare qualsiasi cosa gli piaccia a condizione che il risultato sia il risultato definito dalla lingua .

Nel caso specifico di restituire valori da funzioni, ecco alcune possibilità comuni:

  • f1 restituisce il valore in un registro macchina o registri. Ovviamente il valore deve essere abbastanza piccolo da stare in 1 o 2 o (in alcune architetture) anche in 4 registri. I registri sono molto veloci, il che è buono. Spetta quindi al chiamante decidere cosa fare con il valore dopo che f1 è ritornato. Ignorarlo è una possibilità (basta iniziare a usare il registro per qualcos'altro!); immagazzinarlo da qualche parte è un altro; passare immediatamente a un'altra funzione è un'altra; eseguire immediatamente un calcolo richiesto su di esso è ancora un altro. Un tema comune è che u potrebbe addirittura non esistere affatto come posizione di memoria se la memoria non è più necessaria per esso. Finché il compilatore sa dove il valore che stai chiamando u è - in questo registro o quel registro o temporaneamente inserito nello stack - non c'è bisogno di usare una locazione di memoria esplicita per esso.

  • La funzione chiamante alloca spazio di memoria sufficiente per contenere un valore dello stesso tipo di u . Passa l'indirizzo di questa memoria a f1 e f1 memorizza il risultato a quell'indirizzo. Questo sembra essere ciò di cui parla il tuo autore. L'allocazione della memoria può essere eseguita sottraendo un numero dal puntatore dello stack, per creare uno spazio che non verrà toccato dal normale utilizzo dello stack; oppure può essere fatto spingendo i valori sullo stack (con un effetto collaterale di riduzione del puntatore dello stack nello stesso modo); o potrebbe anche essere fatto allocando memoria heap: non c'è nulla in C che richiede l'uso di uno stack.

  • f1 alloca la memoria per un valore restituito e restituisce il suo indirizzo. È quindi compito del chiamante ricordare che questa memoria è allocata e liberarla quando non è più necessaria. Questo non è comune a livello di linguaggio C a causa della difficoltà per il compilatore di assicurarsi che la memoria allocata sia sempre liberata, ma non c'è nulla nel linguaggio che lo preclude. Ha il vantaggio che f1 potrebbe avere un'idea migliore del chiamante sulla quantità di memoria richiesta da questo particolare valore restituito.

Per quanto riguarda la tua domanda su "indirizzi di valore" in espressioni più complicate, la risposta è che il compilatore fa tutto ciò che è necessario.

  • Quando i valori vengono restituiti nei registri, non occorre fare nulla di speciale. Il valore restituito può essere manipolato direttamente (aggiungendo 5 ad esso, nel tuo esempio), oppure può essere passato a un'altra funzione spingendolo nello stack (se la funzione prende i suoi argomenti in pila) o spostato nella corretta registro (se la funzione prende la sua argomentazione dai registri, che era enormemente alla moda alla fine degli anni '80, ma ora è démodé).

  • Quando i valori vengono restituiti nella memoria riservata in precedenza, il compilatore riserva tranquillamente la quantità di memoria richiesta per tutti i valori di ritorno intermedi. Se lo desideri, puoi immaginarlo come un processo di semplificazione di tutte le espressioni complesse in chiamate a funzione singola i cui valori di ritorno sono variabili invisibili chiamate anonymous1 , anonymous2 e così via. Il compilatore sa quanta memoria riservare. Può anche fare un sacco di riutilizzo di questa memoria anonima perché sa esattamente quando ogni variabile invisibile inizia ad essere usata e precisamente quando smette di essere usata.

risposta data 19.07.2016 - 18:26
fonte
1

..., ad eccezione del fatto che un compilatore C moderno trasformerebbe il tuo f2() in questo:

void f2(void) 
{ 
   int u = 9; 
   // RL A
}

E se f1() è stato dichiarato statico, il compilatore non lo avrebbe nemmeno emesso nel file di output.

Tutto ciò presuppone che il tuo commento, // RL A , rappresenti un codice effettivo che utilizza u . Se è davvero solo un commento, il compilatore trasformerà il tuo esempio in questo:

void f2(void) 
{ 
}

Oppure, se f2() era un static , il compilatore non emetterebbe nemmeno un codice per esso.

    
risposta data 19.07.2016 - 19:18
fonte
0

Questo non è il modo in cui è compilato il codice C attuale. In realtà, la variabile u non ha mai assegnata una posizione di memoria reale. Invece, il compilatore memorizza i valori 2 e 7 nello stack e chiama f1 . f1 esegue il suo calcolo accedendo alle posizioni relative al puntatore di base (un registro nel processore, chiamato "ebp" su macchine x86). Quindi restituisce il risultato in un altro registro del processore ("eax" su macchine x86). f2 è libero di fare ciò che vuole con esso, ma nel codice che hai dato lo scarterebbe semplicemente piuttosto che memorizzarlo in una posizione permanente.

Uno schema simile a quello che descrivi è usato per restituire strutture, piuttosto che singoli valori - forse questo è ciò che viene descritto dal libro? C'è una buona descrizione di come funziona nella risposta a questo stackoverflow domanda .

    
risposta data 20.05.2016 - 10:27
fonte

Leggi altre domande sui tag