Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
C/C++ - Esecuzione di un programma C
Forum - C/C++ - Esecuzione di un programma C

Pagine: [ 1 2 ] Precedente | Prossimo
Avatar
Godrek (Normal User)
Pro


Messaggi: 76
Iscritto: 04/08/2015

Segnala al moderatore
Postato alle 15:34
Venerdì, 16/12/2016
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.

PM Quote
Avatar
lumo (Member)
Expert


Messaggi: 449
Iscritto: 18/04/2010

Segnala al moderatore
Postato alle 15:47
Venerdì, 16/12/2016
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
Codice sorgente - presumibilmente Plain Text

  1. tipo_ritorno nome_funzione(...parametri...)
  2. {
  3.     codice
  4. }


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
Codice sorgente - presumibilmente C/C++

  1. 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 il 16/12/2016 alle 17:28
PM Quote
Avatar
nessuno (Normal User)
Guru^2


Messaggi: 6380
Iscritto: 03/01/2010

Segnala al moderatore
Postato alle 17:06
Venerdì, 16/12/2016
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à.
PM Quote
Avatar
pierotofy (Admin)
Guru^2


Messaggi: 6230
Iscritto: 04/12/2003

Segnala al moderatore
Postato alle 14:48
Sabato, 17/12/2016
Testo quotato


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: https://piero.dev
PM Quote
Avatar
pierotofy (Admin)
Guru^2


Messaggi: 6230
Iscritto: 04/12/2003

Segnala al moderatore
Postato alle 14:53
Sabato, 17/12/2016
Da notare inoltre che non ti serve dichiarare una funzione se l'ordine in cui scrivi la loro implementazione è sequenziale:

Codice sorgente - presumibilmente C++

  1. // somma e' implementato prima di main
  2. int somma(int a, int b)
  3. {
  4.     return a + b;
  5. }
  6.  
  7. // main chiama somma, tutto OK
  8. int main()
  9. {
  10.     int x = 10;
  11.     int y = 20;
  12.     int result;
  13.     result = somma(x, y);
  14.      
  15.     return 0;
  16. }




Codice sorgente - presumibilmente C++

  1. // main chiama somma, errore, non definito
  2. int main()
  3. {
  4.     int x = 10;
  5.     int y = 20;
  6.     int result;
  7.     result = somma(x, y);
  8.      
  9.     return 0;
  10. }
  11.  
  12. // somma e' implementato dopo main
  13. int somma(int a, int b)
  14. {
  15.     return a + b;
  16. }





Codice sorgente - presumibilmente C++

  1. int somma(int a, int b); // dichiarazione, il compilatore deve aspettarsi una funzione chiamata somma
  2.  
  3. // main chiama somma, OK
  4. int main()
  5. {
  6.     int x = 10;
  7.     int y = 20;
  8.     int result;
  9.     result = somma(x, y);
  10.      
  11.     return 0;
  12. }
  13.  
  14. // somma e' implementato dopo main
  15. int somma(int a, int b)
  16. {
  17.     return a + b;
  18. }



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

Questo codice:

Codice sorgente - presumibilmente C/C++

  1. 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. https://it.wikipedia.org/wiki/Compilatore

Ultima modifica effettuata da pierotofy il 17/12/2016 alle 14:57


Il mio blog: https://piero.dev
PM Quote
Avatar
Godrek (Normal User)
Pro


Messaggi: 76
Iscritto: 04/08/2015

Segnala al moderatore
Postato alle 16:30
Martedì, 20/12/2016
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 il 20/12/2016 alle 16:40
PM Quote
Avatar
lumo (Member)
Expert


Messaggi: 449
Iscritto: 18/04/2010

Segnala al moderatore
Postato alle 16:43
Martedì, 20/12/2016
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
Codice sorgente - presumibilmente C/C++

  1. 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 il 20/12/2016 alle 16:48
PM Quote
Avatar
pierotofy (Admin)
Guru^2


Messaggi: 6230
Iscritto: 04/12/2003

Segnala al moderatore
Postato alle 18:16
Martedì, 20/12/2016
Codice sorgente - presumibilmente Plain Text

  1. a:
  2.         .long   10
  3. somma(int):
  4.         push    rbp
  5.         mov     rbp, rsp
  6.         mov     DWORD PTR [rbp-4], edi
  7.         mov     edx, DWORD PTR a[rip]
  8.         mov     eax, DWORD PTR [rbp-4]
  9.         add     eax, edx
  10.         pop     rbp
  11.         ret
  12. main:
  13.         push    rbp
  14.         mov     rbp, rsp
  15.         sub     rsp, 16
  16.         mov     DWORD PTR [rbp-4], 15
  17.         mov     eax, DWORD PTR [rbp-4]
  18.         mov     edi, eax
  19.         call    somma(int)
  20.         mov     DWORD PTR [rbp-4], eax
  21.         mov     eax, 0
  22.         leave
  23.         ret



Testo quotato


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.

Testo quotato


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:

Codice sorgente - presumibilmente C/C++

  1. main:
  2.         push    rbp
  3.         mov     rbp, rsp
  4.         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:

Codice sorgente - presumibilmente Plain Text

  1. somma(int):
  2.         push    rbp
  3.         mov     rbp, rsp



Non alloca nulla, siccome non ci sono variabili locali.

Per convertire da C ad Assembly: http://godbolt.org/


Ultima modifica effettuata da pierotofy il 20/12/2016 alle 18:17


Il mio blog: https://piero.dev
PM Quote
Avatar
Godrek (Normal User)
Pro


Messaggi: 76
Iscritto: 04/08/2015

Segnala al moderatore
Postato alle 18:45
Martedì, 20/12/2016
Provo a cercarlo di spiegare passo passo:

Le regole sono le seguenti:

Come viene rappresentata (attraverso la pila di frame) l'esecuzione di un blocco:
- Quando si entra in un blocco si aggiunge un frame vuoto in cima alla pila
- Le dichiarazioni del blocco aggiungono associazioni sul frame in cima alla pila
- All'uscita del blocco si cancella il frame in cima alla pila

Come viene rappresentata (attraverso la pila di frame) quando una funzione viene chiamata:
- Se la funzione chiamata ha dei parametri formali viene aggiunto un frame in testa alla pila contenente i nomi dei parametri formali con associati i valori degli argomenti passati attraverso la chiamata di funzione
- Si esegue il corpo (il blocco) che definisce la funzione (quindi si aggiunge un frame vuoto in cima, .... , si cancella il frame in cima)
- Al termine della funzione si cancella il frame in testa alla pila (ovvero il frame contenente i parametri della funzione)


int a = 10;

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

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

Supponendo di partire da una pila vuota, secondo le regole elencate, la rappresentazione attraverso una pila di frame di tale programma è la seguente:

1° passo: int a = 10; - aggiungo un frame in testa alla pila contenente l'associazione: (a, 10)

2° passo: int somma(int b) - aggiungo una associazione sul frame in testa alla pila contenente l'associazione (un po astratta): (somma, definizione)

3° passo: int main() - aggiungo un frame vuoto in testa alla pila

4° passo: int x = 5; aggiungo una associazione sul frame in testa alla pila contenente l'associazione: (x, 15)

5° passo: x = somma(x) (chiamata della funzione somma) - aggiungo un frame in testa alla pila contenente i parametri di somma...

Questo è il punto:
- int somma (){...}
- int main(){...}

hanno entrambi la sintassi di una definizione di funzione, soltanto che quando dichiaro la funzione somma aggiungo prima una associazione: (somma, definizione) e solamente quando tale funzione viene chiamata aggiungo un frame in testa alla pila contenente le sue variabili locali, mentre quando dichiaro la funzione main non viene aggiunta una associazione: (main, definizione) ma viene aggiunto direttamente un frame contenente le sue variabili locali.
Quindi è come se il compilatore avesse bisogno di sapere (prima del suo uso) quali sono le variabili locali che appartengono alla funzione somma, mentre per quanto riguarda le variabili locali della funzione main non ne ha bisogno.

Ultima modifica effettuata da Godrek il 20/12/2016 alle 19:36
PM Quote
Pagine: [ 1 2 ] Precedente | Prossimo