Funzione (informatica)
Da Wikipedia, l'enciclopedia libera.
In informatica, una funzione (detta anche subroutine, routine, procedura o sottoprogramma) è un costrutto che permette di raggruppare una sequenza di istruzioni che fanno parte di un programma. Una funzione può essere "chiamata" ("richiamata", "invocata", "attivata") in diversi punti del programma di cui fa parte come se fosse una singola istruzione.
Una funzione dovrebbe eseguire una determinata operazione o risolvere un determinato problema, e contribuire alla fattorizzazione del software. Ad esempio, una subroutine progettata per disporre in ordine crescente un insieme di numeri interi può essere richiamata in tutti i contesti in cui questa operazione sia utile o necessaria, e supplisce alla mancanza di una vera e propria "istruzione" dedicata allo scopo, consentendo al contempo di descrivere il corrispondente algoritmo di ordinamento in un unico punto del programma.
Le subroutine sono spesso raccolte in librerie.
Nei diversi linguaggi di programmazione, le funzioni vengono realizzate in modi e con terminologie parzialmente diverse.
- il termine subroutine è stato usato fino dagli albori della programmazione per riferirsi a sezioni di codice assembly o in linguaggio macchina (e viene usato per estensione in altri contesti ad esempio nelle prime versioni del Basic);
- i termini procedura e funzione vengono generalmente usati nel contesto dei linguaggi di programmazione ad alto livello; laddove non siano considerati sinonimi per funzione si intende un sottoprogramma il cui scopo principale sia quello di produrre un valore a partire da determinati dati di ingresso (cosa che stabilisce un'analogia con l'omonimo concetto della matematica), mentre una procedura è un sottoprogramma che non "produce" alcun particolare valore. Alcuni linguaggi (per esempio il C) adottano come modello "standard" quello della funzione, e considerano le procedure come caso particolare di funzione che ritorna un valore appartenente all'insieme vuoto.
- il termine sottoprogramma è anch'esso tipico dei linguaggi di programmazione ad alto livello, ed è talvolta usato come termine generale per riferirsi sia a procedure che a funzioni nel senso descritto sopra.
- Nella programmazione orientata agli oggetti, il ruolo della funzione è assunto dal metodo.
Indice |
[modifica] Funzionamento
Una funzione è una porzione di codice che può essere invocata da qualsiasi punto di un programma. L'esecuzione di quella parte di programma si interrompe fino a quando l'esecuzione di una funzione è terminata, e prosegue dall'istruzione successiva a quella di invocazione.
Quasi tutti i linguaggi di programmazione supportano le funzioni, fornendo una sintassi per definire una funzione, ovvero scriverne il codice, ed una per richiederne l'esecuzione (invocazione o chiamata della funzione).
Un altro strumento, che talvolta può essere usato al posto di una funzione, anche se con grosse limitazioni è la macro.
Una funzione può a sua volta richiamare un'altra funzione, restando in attesa che questa termini. Si parla in questo caso di funzione invocante e funzione invocata, o funzione chiamante e funzione chiamata. È anche possibile che una funzione richiami direttamente o indirettamente sé stessa. In questo caso, si dice che in un dato momento sono in esecuzione più istanze di una stessa funzione. Questa possibilità è essenziale per la programmazione ricorsiva.
[modifica] Passaggio di parametri
Il comportamento di una funzione può essere dipendente dai dati che le sono passati come parametri all'atto dell'invocazione; inoltre una funzione può restituire un dato all'uscita.
Esistono diverse modalità di passaggio dei parametri, o di accesso alle strutture definite all'interno del programma stesso: ogni linguaggio, infatti, gestisce il proprio ambiente dipendentemente dalle regole di scoping che implementa, e di cui è necessario tenere conto.
[modifica] Variabili globali
Il metodo più semplice di passare parametri ad una funzione è l'utilizzo di variabili globali, ovvero visibili da qualsiasi parte del programma. Questa pratica, quando attuata indiscriminatamente, è fortemente scoraggiata, perché rende meno leggibili i programmi, e può portare ad una scarsa leggibilità semantica del codice, con conseguenze spesso inaspettate.
[modifica] Parametri formali e attuali
I parametri che una funzione può ricevere sono definiti all'atto della definizione della funzione. In questo contesto, le variabili che fanno riferimento ai parametri ricevuti sono detti parametri formali.
Quando una funzione viene invocata, gli identificatori associati internamente ai valori che le vengono passati sono detti parametri attuali.
I parametri sono normalmente in numero determinato dalla definizione della funzione, ma alcuni linguaggi forniscono sistemi più o meno eleganti per realizzare funzioni con un numero variabile di argomenti.
Nei linguaggi tipizzati, anche il tipo di dato dei parametri formali deve essere definito, ed il type checking deve essere effettuato anche per verificare che i parametri attuali siano di tipo compatibile con quello dei corrispondenti parametri formali. Il tipo di un parametro può essere anche una complessa struttura dati.
Ciascuna istanza di una funzione che è in esecuzione in un certo momento possiede la propria copia dei parametri attuali (e delle variabili locali).
[modifica] Passaggio per valore
Il valore dei parametri attuali viene copiato nelle variabili della funzione chiamata che rappresentano i parametri formali. Se la funzione chiamata li modifica, la funzione chiamante non potrà vedere queste modifiche. Si tratta quindi di passaggio unidirezionale
[modifica] Passaggio per indirizzo o per riferimento
La funzione invocata riceve un puntatore o riferimento ai parametri attuali. Se modifica un parametro passato per indirizzo, la modifica sarà visibile anche alla funzione chiamante. Il passaggio è quindi potenzialmente bidirezionale.
[modifica] Valore restituito
Una funzione può restituire un valore (tipizzato) al chiamante. Questa modalità di passaggio di dati è unidirezionale, dal chiamato al chiamante.
Una chiamata di funzione è quindi un'espressione, che viene valutata per ottenere un valore. La valutazione di una espressione che contenga una chiamata di funzione comporta l'esecuzione della funzione stessa.
In alcuni linguaggi, il termine procedura indica una funzione senza valore di ritorno, in altri si usa un tipo di dato apposito, detto void
, per il valore restituito, a significare che la funzione non restituisce alcun valore.
Nei linguaggi funzionali il tipo di dato restituito può essere una funzione, che il chiamante potrà invocare.
[modifica] Variabili locali
Una funzione può definire variabili, dette locali, che sono visibili solo durante l'esecuzione di una particolare istanza della funzione. Se più istanze di una funzione sono in esecuzione contemporaneamente, ciascuna avrà la sua copia di ciascuna variabile locale.
Per conoscere le modalità con cui le dichiarazioni effettuate localmente ad una funzione possano interagire con altre funzioni (o sottoblocchi di codice), è necessario avere una conoscenza approfondita delle modalità con cui l'ambiente viene gestito dallo specifico linguaggio.
[modifica] Implementazione delle funzioni
Le funzioni sono uno strumento talmente importante e diffuso da essere normalmente supportate direttamente dall'hardware. L'esistenza dello stack nelle architetture hardware è appunto riconducibile alla necessità di supportare efficientemente le funzioni. Infatti, quando viene invocata una funzione il punto del codice in cui è stata invocata viene salvato sullo stack (indirizzo di ritorno), e anche i parametri e le variabili locali di una funzione vengono salvati sullo stack.
L'insieme di questi dati sullo stack è detto record di attivazione, e rappresenta una funzione in fase di esecuzione, che può essere sospesa in attesa del completamento di un'altra funzione che a sua volta ha invocato.
Il record di attivazione in cima allo stack è quello della funzione attualmente in esecuzione, sotto c'è quello della funzione che l'ha chiamata, e così via.
Lo stack può essere usato anche in altri modi, ad esempio per memorizzare temporaneamente valori intermedi nella valutazione delle espressioni aritmetiche.
In assembler esistono funzioni dedicate al supporto delle funzioni e dello stack, con un corrispondente diretto nel linguaggio macchina:
- PUSH: metti un valore sullo stack
- POP: leggi e togli dallo stack un valore
- JSR: jump to subroutine, ovvero salta ad una subroutine (salvando l'indirizzo di ritorno sullo stack con PUSH)
- RET: ritorno da una subroutine al chiamante (identificato eseguendo una POP dell'indirizzo di ritorno dallo stack)
Naturalmente, ciascuna funzione o pezzo di codice che usa lo stack ha la responsabilità di togliere tutto quello che ha messo sullo stack prima di terminare (e nulla più di quanto ha messo), altrimenti il valore di un parametro o di una variabile locale verrà impiegato come indirizzo di ritorno, con conseguenze impredicibili. La difficoltà di garantire manualmente questa correttezza è uno dei motivi che giustificano l'uso di linguaggi di alto livello, che tra l'altro gestiscono automaticamente la coerenza dello stack.
A causa di queste operazioni, l'invocazione di una funzione comporta un costo, seppur normalmente modesto, in termini di prestazioni.
[modifica] Sicurezza
Il meccanismo di implementazione delle funzioni, insieme all'aritmetica dei puntatori nel linguaggio C, viene sfruttato per costruire attacchi di tipo stack overflow o buffer overflow, che permettono di prendere il controllo di un programma in esecuzione fornendogli dati accuratamente artefatti.