Oppure

Loading
16/12/16 14:34
Godrek
Frequento il corso di Programmazione I all'università e stiamo trattando il linguaggio C.
Per l'esattezza ci stiamo occupando di rappresentare (per ora soltanto graficamente) i cambiamenti di stato che avvengono durante l'esecuzione di un programma C attraverso le pile di frame.


int somma(int a, int b)
{
    return a + b;
}

int main()
{
    int x = 10;
    int y = 20;
    int result;
    result = somma(x, y);
    
    return 0;
}


Per come il professore ha rappresentato l'esecuzione di questo programma, viene dichiarata (ma non eseguita) la funzione somma (in quanto verrà eseguita una volta chiamata dalla funzione main), dopodiché viene eseguita la funzione main.
La mia domanda è perché la funzione somma viene dichiarata e la funzione main no?
So già che la funzione main è la funzione principale che viene chiamata automaticamente dal sistema operativo non appena il programma viene eseguito, ma perché quando chiamo la funzione somma dalla main la funzione somma è già stata dichiarata, mentre la funzione main viene chiamata dal S.O. senza essere prima dichiarata ma viene direttamente eseguita e le dichiarazioni che essa contiene vengono aggiunte nello stato durante la sua esecuzione.
Se non si capisce bene, posso postare la rappresentazione.
aaa
16/12/16 14:47
lumo
Non si capisce molto bene cosa intendi perché usi una terminologia abbastanza imprecisa, andando per punti:

1) ti prego chiamale pile di cornici o stack frame ma pile di frame mi fa sanguinare internamente
2) la funzione somma viene eseguita dalla funzione main, e la funzione main dal sistema operativo

Un po' di terminologia essenziale:

Quando si fa somma(x, y) o qualsiasi altra forma f(parametri blabla) si dice che si fa una chiamata a funzione (function call) ed è quello il momento in cui si crea un nuovo stack frame in cui viene eseguita la funzione.

La scritta
tipo_ritorno nome_funzione(...parametri...)
{
    codice
}

Si chiama definizione della funzione, e non fa niente se non dire poi al compilatore che hai una funzione che ha un certo nome con certi parametri che puoi usare da altre parti nel programma. Se non metti nessuna chiamata a quella funzione, rimarrà inutilizzata non verrà mai eseguita.

L'unica eccezione è main, che viene chiamata implicitamente dal sistema operativo, e quindi non vedi da nessuna parte main() dentro al tuo codice.

La dichiarazione invece in C (ma non solo in C) è dichiarare solamente il tipo di qualcosa. Per le funzioni si scrive ad esempio
int somma(int x, int y);


Per ogni dichiarazione si deve avere una definizione associata (per le funzioni almeno), non ti spiego a cosa serve una dichiarazione ora ma ti assicuro che ha il suo scopo nonostante sembri ridondante.
Però non usare il termine in altri contesti altrimenti si fa fatica a capirsi.
Ultima modifica effettuata da lumo 16/12/16 16:28
aaa
16/12/16 16:06
nessuno
in qui al posto di in cui fa sanguinare me ...
Ricorda che nessuno è obbligato a risponderti e che nessuno è perfetto ...
---
Il grande studioso italiano Bruno de Finetti ( uno dei padri fondatori del moderno Calcolo delle probabilità ) chiamava il gioco del Lotto Tassa sulla stupidità.
17/12/16 13:48
pierotofy
La mia domanda è perché la funzione somma viene dichiarata e la funzione main no?


E' un requisito del linguaggio, ed è lì per questioni di velocità; ci dev'essere sempre un main(), quindi non serve dichiararlo a priori. Altre funzioni il compilatore non se le aspetta, quindi il programmatore, tramite la dichiarazione, avvisa il compilatore che "esiste una funzione, si chiama in questo modo (somma) e prende questi parametri (due interi)". Solitamente le dichiarazioni vengono incluse in un file di header (probabilmente non l'avete ancora studiato). Questo sistema permette al compilatore di fare il parsing dei sorgenti una volta sola anzichè due, se non facessi la dichiarazione delle funzioni il compilatore dovrebbe prima andare a scoprire tutte le funzioni che sono definite, e poi un altro passo per il resto della compilazione. Soprattutto in progetti più grandi, questo fa una differenza, e faceva una differenza soprattutto nel 1972 quando il linguaggio era stato progettato.

Il mio blog: piero.dev
17/12/16 13:53
pierotofy
Da notare inoltre che non ti serve dichiarare una funzione se l'ordine in cui scrivi la loro implementazione è sequenziale:

// somma e' implementato prima di main
int somma(int a, int b) 
{ 
    return a + b; 
} 

// main chiama somma, tutto OK
int main() 
{ 
    int x = 10; 
    int y = 20; 
    int result; 
    result = somma(x, y); 
     
    return 0; 
} 



// main chiama somma, errore, non definito
int main() 
{ 
    int x = 10; 
    int y = 20; 
    int result; 
    result = somma(x, y); 
     
    return 0; 
} 

// somma e' implementato dopo main
int somma(int a, int b) 
{ 
    return a + b; 
} 




int somma(int a, int b); // dichiarazione, il compilatore deve aspettarsi una funzione chiamata somma

// main chiama somma, OK
int main() 
{ 
    int x = 10; 
    int y = 20; 
    int result; 
    result = somma(x, y); 
     
    return 0; 
} 

// somma e' implementato dopo main
int somma(int a, int b) 
{ 
    return a + b; 
} 


1 e 3 compilano, 2 ti darà un errore.

Questo codice:

int somma(int a, int b); // dichiarazione, il compilatore deve aspettarsi una funzione chiamata somma


NON viene mai eseguito, e' una sintassi per aiutare il compilatore e scoprire quali funzioni sono dichiarate. C'e' un'importante differenza. it.wikipedia.org/wiki/…
Ultima modifica effettuata da pierotofy 17/12/16 13:57
Il mio blog: piero.dev
20/12/16 15:30
Godrek
Non ho ancora capito bene, forse mi sono spiegato male.
Quello che volevo sapere era:
Supponiamo di avere il seguente programma:

int a = 10;

int somma(int b)
{
return a + b;
}

int main()
{
int x = 15;
x = somma(x);
return 0;
}

Per quanto mi hanno detto:
Non appena viene avviato il programma, viene aggiunto in testa allo stack un frame contenente una associazione per a con associato il valore 10.
Dopo viene aggiunto sul frame in testa allo stack una associazione per la funzione somma con associata la sua definizione.
Successivamente viene eseguito il corpo della funzione main (chiamata implicitamente dal sistema operativo), e quindi viene aggiunto un frame in testa allo stack contenente una associazione per x con associato il valore 15, ecc...

La domanda era:
perché quando il programma incontra la definizione della funzione somma viene aggiunta una associazione allo stack contente la sua definizione (che poi sarà eseguita una volta chiamata attraverso la funzione main) mentre la funzione main non viene mai definita sullo stack ma direttamente eseguita e le variabili che essa contiene vengono dichiarate durante l'esecuzione della funzione main?
Mi sembra una contraddizione al fatto che in C qualsiasi cosa, prima di essere utilizzata, deve essere dichiarata.

Perché il compilatore ha bisogno di sapere, prima che essa venga eseguita, quanto spazio occorre per le variabili contenute nella funzione somma, mentre per la funzione main non ne ha bisogno?



Ultima modifica effettuata da Godrek 20/12/16 15:40
aaa
20/12/16 15:43
lumo
Infatti non è per niente così.

Hai provato a compilare ed eseguire il programma? Ti hanno spiegato circa cosa fa il compilatore?

Il discorso è questo: prima tutto il programma viene letto e tradotto in codice macchina. Gli stack frame li ritrovi poi implementati a livello di codice macchina.

Quello che succede è questo:
1) dici al sistema operativo di eseguire il programma
2) il sistema operativo cerca il simbolo main nell'eseguibile
3) trovato il main, viene creato un nuovo stack frame con x=15 (anche se, a dirla tutta, le variabili scompaiono nel codice macchina) e viene eseguito il corpo della funzione
4) quando si arriva alla riga x = somma(x); siccome somma è una funzione viene creato un nuovo stack frame, sopra quello di main, che ha b=x=15 e viene eseguito
5) lo stack frame precedente viene eliminato (con gli stack si parla di "pop", mentre il crearlo è un "push";) però il risultato della funzione somma viene mantenuto e viene salvato in x
6) lo stack frame principale viene terminato e dà al sistema operativo il valore 0, che indica esecuzione senza errori a runtime

Riguardo a quella variabile
int a = 10;

Probabilmente per semplificare ti hanno spiegato che le variabili del contesto globale sono in uno stack frame a parte e sopra di questo viene main e tutto il resto.
In realtà c'è un altro modo di salvarle che probabilmente studierai ad architettura degli elaboratori o qualche corso simile (si chiama data sector se non ricordo male).

Quindi, la prima cosa che devi capire è che il computer non esegue direttamente il codice C, se tu basi il tuo modello mentale di esecuzione solamente sul codice ad alto livello rischi di fare dei travisamenti totali come questo.

La seconda è che né main né somma creano un nuovo stack frame quando vengono lette (non ha senso se hai capito come viene eseguito il programma).
Ultima modifica effettuata da lumo 20/12/16 15:48
aaa
20/12/16 17:16
pierotofy
a:
        .long   10
somma(int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     edx, DWORD PTR a[rip]
        mov     eax, DWORD PTR [rbp-4]
        add     eax, edx
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 15
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    somma(int)
        mov     DWORD PTR [rbp-4], eax
        mov     eax, 0
        leave
        ret


mentre la funzione main non viene mai definita sullo stack ma direttamente eseguita e le variabili che essa contiene vengono dichiarate durante l'esecuzione della funzione main?



Non capisco cosa intendi per "definire una funzione sullo stack", ma penso che forse studiando l'assembly del tuo programma qui sopra forse troverai una risposta ai tuoi dubbi.

Perché il compilatore ha bisogno di sapere, prima che essa venga eseguita, quanto spazio occorre per le variabili contenute nella funzione somma, mentre per la funzione main non ne ha bisogno?


Il compilatore ha bisogno eccome di sapere quanto spazio occorre nella funzione main. Vedi la combinazione:

main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16


Che alloca 16 bytes (piu' di quelli necessari, per ragioni di allineamento della memoria) per contenere la variabile x (4 bytes).

Nota invece che:

somma(int):
        push    rbp
        mov     rbp, rsp


Non alloca nulla, siccome non ci sono variabili locali.

Per convertire da C ad Assembly: godbolt.org/

Ultima modifica effettuata da pierotofy 20/12/16 17:17
Il mio blog: piero.dev