Funzione (informatica)

Un programma "Hello world" in linguaggio C, che contiene la funzione main

In informatica e in particolare nella programmazione, una funzione è un'unità di organizzazione del codice che permette di raggruppare una sequenza di istruzioni in un unico blocco, caratterizzato da un nome, dei parametri in ingresso (detti argomenti) e uno o più dati restituiti in uscita. Si tratta effettivamente di un'evoluzione del concetto di procedura.[1]

L'importanza, la praticità e i vantaggi di una funzione stanno nel fatto che può essere "chiamata" ("richiamata", "invocata" o "attivata") in diversi punti del programma di cui fa parte ogni volta in cui si ha la necessità di farlo come se fosse una singola istruzione senza la necessità di doverne riscrivere ogni volta il relativo codice implementando dunque il cosiddetto riuso di codice, cui si aggiunge una più facile manutenibilità del codice all'interno del programma e una più facile progettazione del software secondo la classica filosofia del divide et impera.

Quasi tutti i linguaggi di programmazione supportano le funzioni, fornendo una propria sintassi per definire una funzione, ovvero scriverne il codice, e una per richiederne l'esecuzione (invocazione o chiamata della funzione). In generale la sintassi comune della definizione di una funzione prevede:

  • un identificativo o nome;
  • per linguaggi tipizzati un tipo di ritorno ovvero una specifica sul tipo di dato che essa restituirà in output all'utente o ad altre funzioni che la invocano; quando una funzione non restituisce nulla (per esempio funzioni con tipo restituito void) la funzione avrà svolto altre elaborazioni agendo ad esempio sullo stato delle variabili del programma o visualizzando a video qualcosa senza restituire nulla. In linguaggi non tipizzati o a tipizzazione debole invece non è presente il tipo di ritorno.
  • la specifica (non sempre necessaria) dei cosiddetti parametri o operandi i cui valori saranno poi passati in fase di invocazione e su cui la funzione opererà in fase di elaborazione; anche in assenza di parametri il corpo della funzione potrà svolgere la sua elaborazione con altre istruzioni ad esempio agendo sullo stato delle variabili del programma o visualizzando a video qualcosa.
  • il corpo della funzione ovvero il nucleo dell'elaborazione costituita dal blocco, opportunamente delimitato, di una o più istruzioni, ciascuna terminata dal comando di terminazione, cicli iterativi, strutture condizionali ecc., il tutto concluso con la variabile eventualmente ritornata in output. Variabili definite nel blocco saranno necessariamente variabili locali cioè con visibilità solo all'interno del blocco stesso.

Identificatore

[modifica | modifica wikitesto]
Lo stesso argomento in dettaglio: Funzione anonima e Overloading.

Una funzione è una porzione di codice che può essere invocata da qualsiasi punto di un programma. Per l'invocazione occorre tipicamente richiamarne almeno il nome passando i valori degli eventuali parametri, cui si aggiungono eventualmente altri dettagli dipendenti dal particolare linguaggio di programmazione in uso.

Generalmente i parametri di una funzione sono definiti all'atto della definizione della stessa. La loro numerosità e il loro ordine è fissato. Alcuni linguaggi forniscono sistemi più o meno eleganti per realizzare funzioni con un numero variabile di argomenti. In tal caso la funzione si dice "variadica".[2]

Nei linguaggi tipizzati, anche il tipo di dato dei parametri formali deve essere definito, e il type checking deve essere svolto anche per verificare che i parametri effettivi siano di tipo compatibile con quello dei corrispondenti parametri formali. Il tipo di un parametro può essere anche una complessa struttura dati.

Parametri formali ed effettivi

[modifica | modifica wikitesto]

Le variabili della funzione 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 effettivi o parametri attuali.

Ciascuna istanza di una funzione che è in esecuzione in un certo momento possiede la propria copia dei parametri effettivi (e delle variabili locali).

Passaggio dei parametri

[modifica | modifica wikitesto]

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.

Passaggio per valore

[modifica | modifica wikitesto]

Con questo meccanismo il valore del parametro effettivo viene copiato nella variabile della funzione chiamata che rappresenta il parametro formale. Se la funzione chiamata lo modifica, la funzione chiamante non potrà vedere questa modifica. Si tratta quindi di un passaggio unidirezionale.

Passaggio per riferimento

[modifica | modifica wikitesto]

Con questo meccanismo la funzione invocata riceve come parametro un puntatore o riferimento al parametro effettivo. Se modifica il parametro passato per indirizzo, la modifica sarà visibile anche alla funzione chiamante. Il passaggio è quindi potenzialmente bidirezionale.

Valore di ritorno

[modifica | modifica wikitesto]

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 un'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.

Altre proprietà

[modifica | modifica wikitesto]

Variabili locali

[modifica | modifica wikitesto]

Una funzione può definire variabili, dette locali, che sono visibili solo durante l'esecuzione di una particolare istanza della funzione ovvero sono definite e allocate all'interno del blocco o corpo della funzione e sono deallocate al termine dell'esecuzione del blocco stesso o funzione stessa. Se più istanze di una funzione sono in esecuzione contemporaneamente, ciascuna avrà la sua copia di ciascuna variabile locale.

Implementazione

[modifica | modifica wikitesto]

Supporto nei linguaggi di programmazione

[modifica | modifica wikitesto]

Il concetto di funzione è utilizzabile in tutti i linguaggi di programmazione che implementano il paradigma procedurale. La maggioranza dei linguaggi di programmazione ad alto livello supporta esplicitamente le funzioni, prevedendo costrutti sintattici dedicati o che in alternativa accorpano diverse varianti del concetto:

Alcuni linguaggi tendono a utilizzare terminologie 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);
  • 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.

Supporto hardware

[modifica | modifica wikitesto]

Nella programmazione le funzioni sono uno strumento talmente importante e diffuso da richiedere una gestione della loro esecuzione estremamente efficiente allo scopo di mantenere bassi i tempi di chiamata della funzione e di ritorno del controllo al programma chiamante. Per questo motivo la gestione dell'allocazione delle variabili locali e del passaggio dei parametri vengono 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 assembly esistono istruzioni 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 a 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.

Riuso di codice

[modifica | modifica wikitesto]
Lo stesso argomento in dettaglio: Riuso di codice.

Una funzione dovrebbe eseguire una determinata operazione o risolvere un determinato problema (o al limite tutto il problema) all'interno dell'algoritmo risolutivo, contribuendo così 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 che implementano funzionalità di base spesso richiamate nel codice sorgente dal programmatore sono raccolte all'interno delle cosiddette librerie.

Lo stesso argomento in dettaglio: Arbitrary code execution.

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.

  1. ^ Funzione, in Enciclopedia della Matematica, Roma, Istituto dell'Enciclopedia Italiana, 2013.
  2. ^ Massimo Benerecetti, Funzioni con numero variabile di parametri: Funzioni Variadiche in C (PDF), su unina.it. URL consultato il 24 marzo 2022 (archiviato l'11 luglio 2019).
  3. ^ K&R 2007, p. 68.
  4. ^ (EN) Java Methods, su w3schools.com. URL consultato il 25 marzo 2024.
  5. ^ (EN) Defining Methods, su docs.oracle.com. URL consultato il 25 marzo 2024.
  6. ^ Basile, Chiacchio 2004, cap. 2.3.

Voci correlate

[modifica | modifica wikitesto]

Altri progetti

[modifica | modifica wikitesto]

Collegamenti esterni

[modifica | modifica wikitesto]
  Portale Informatica: accedi alle voci di Wikipedia che trattano di Informatica