Archivi delle etichette: C

Breve paragone tra l’“overloading” in C++, C(11) e Fortran(90)

fvc

In C++ possiamo scrivere codice come questo:

#include <iostream>
 
int miafunz(int a, int b)
{
    std::cerr << "int, int\n";
    return a + b;
}
 
int miafunz(double a, int b)
{
    std::cerr << "double, int\n";
    return 2.0*a + 2.5*b;
}
 
int main()
{
    double d0 = 1.0;
    int di = 2;
    std::cout << miafunz(d0, di) << "\n";
    std::cout << miafunz(5, di) << "\n";
}

Lo compiliamo e lo eseguiamo:

fvc-compilesegui

Quello che accade dietro le quinte è che il compilatore in realtà genera due funzioni diverse e “capisce” qual è quella giusta da chiamare basandosi sul tipo degli argomenti.

Per il programmatore è tutto trasparente, mentre compilatore e linker hanno una gatta da pelare in più. Il problema è questo: le due funzioni sono in realtà diverse, pezzi di codice diverso, e ciascuna deve avere associato un simbolo diverso1, anche se il programmatore usa solo il nome che ha deciso (miafunz).

Se non è definita e imposta un’ABI specifica per tutte le implementazioni del C++, ci possono essere “incomprensioni” perché ogni compilatore “decora” i nomi delle funzioni come meglio crede. (Cfr. name mangling)

Detto altrimenti: il compilatore C++ si deve inventare due nomi per le due funzioni, laddove noi usiamo un unico nome. È abbastanza ragionevole ipotizzare che i nuovi nomi siano costruiti a partire dal nome da noi scelto, secondo un certo schema2 — ma non è affatto necessario che sia così. Nel caso di g++ (4.7.2),

fvc-readelfgcc

ed è lo stesso con clang++ (3.0), per la cronaca. È buono che ci sia una certa convergenza, ma purtroppo in realtà non possiamo farci affidamento. Invece di spiare l’ELF potete dire al g++ di fermarsi alla generazione del codice assembly (opzione -S) e dare uno sguardo ai simboli usati.

fvc-asmsym

Il C non ha di questi problemi

Il C non ha di questo problemi perché non permette l’overloading. Se scrivete

#include <stdio.h>

int miafunz(int a, int b)
{
    fprintf(stderr, "int, int\n");
    return a + b;
}

int miafunz(double a, int b)
{
    fprintf(stderr, "double, int\n");
    return 2.0*a + 2.5*b;
}


int main(void)
{
    double d0 = 1.0;
    int di = 2;
    printf("%d\n", miafunz(d0, di));
    printf("%d\n", miafunz(5, di));
    return 0;
}

il compilatore si ribella,

fvc-gccribelle

perché stiamo ridefinendo una funzione (ho usato gcc 5.2 perché evidenzia meglio gli errori, in stile clang). In C i simboli delle funzioni devono essere unici (all’interno del loro campo di visibilità3).

Potremmo scrivere

int miafunz_ii(int a, int b)
{
    fprintf(stderr, "int, int\n");
    return a + b;
}

int miafunz_di(double a, int b)
{
    fprintf(stderr, "double, int\n");
    return 2.0*a + 2.5*b;
}

ma poi starebbe a noi scegliere la funzione “giusta” per gli argomenti giusti. Fattibile, ma potenziale fonte di errori e comunque appesantisce la lettura (e la scrittura…). Un esempio più concreto è quello di certe funzioni matematiche per le quali in effetti nella libreria esistono diverse versioni a seconda del tipo di argomenti usati e restituiti; p.es. sin (double), sinf (float), sinl (long double).

Il C11 è venuto incontro a questa esigenza tramite una macro, _Generic, il cui uso però risulta limitato, cioè non dà la stessa elasticità dell’overloading del C++: in pratica è utile solo per tgmath.h (type generic math).

Riciclo un esempio da un mio post su un altro blog.

Il Fortran è diverso

Quando le persone sentono Fortran credono di essere tornati indietro nel tempo: è un linguaggio vecchio, ormai in disuso, è brutto, ecc… Non è così. Di sicuro è “di nicchia”. Ma comunque, a differenza di altri linguaggi di quell’epoca, è andato avanti e si è ammodernato. L’ultima versione dello standard è il Fortran 2008, ma è pianificato il Fortran 2015.

Una delle caratteristiche del Fortran moderno sono le interfacce. In pratica il Fortran ha lo stesso problema che il C(11) ha in parte risolto con _Generic; però la soluzione è molto più… generica!… elastica, elegante e (a mio immodesto avviso) intelligente.

Dettagli sintattici a parte, il programmatore non lascia al compilatore l’onere di generare i nomi delle funzioni: egli scrive le diverse funzioni come deve (p.es. miafunz_di, miafunz_ii) e specifica che miafunz è un’interfaccia a quelle funzioni (il compilatore di solito è nelle condizioni di poter dedurre da sé la funzione giusta in base al tipo degli argomenti, proprio come avviene per il C++; nei casi in cui non dovesse essere così, nell’interfaccia dovremmo specificare per intero gli argomenti e i loro tipi).

Riciclo di nuovo un esempio da un altro blog; nell’esempio my_sum è definita come interfaccia per le funzioni my_sum_r, my_sum_i, ecc…


  1. A codice oggetto generato, non è sempre necessario. Per esempio nell’esempio specifico in teoria non c’è nessun bisogno che i simboli delle funzioni rimangano, perché di fatto non ci interessa esportarli. Tant’è che tali simboli possono essere rimossi, per esempio con strip. Il discorso sarebbe diverso se stessimo scrivendo del codice che deve essere linkato (una libreria statica o dinamica, per esempio).

  2. Una soluzione ragionevole (perché intellegibile) è quella di codificare nel nome della funzione i tipi degli argomenti; vediamo infatti che miafunz(double,int) diventa _Z7miafunzdi e miafunz(int,int) diventa _Z7miafunzii.

  3. O «confine entro cui sono visibili». È la mia traduzione approssimativa di scope. Non voglio affrontare questioni linguistiche sottili, ma la traduzione “visibilità” non mi sembra del tutto corretta. La visibilità (visibility) è la proprietà di essere visibili (la proprietà di un oggetto di poter essere visto); il termine scope fa riferimento a quanto è “estesa” questa proprietà, cioè fin dove vale o non vale per un determinato oggetto; dunque si riferisce all’“area di pertinenza” di una proprietà (in generale, e della visibilità nello specifico). La traduzione “visibilità” di per sé non veicola l’idea di “estensione”. Prendiamo per esempio la frase «the name must be unique in its scope». La traduzione «il nome deve essere unico nella sua visibilità» non corrisponde all’originale; in italiano siamo costretti ad usare una perifrasi: «il nome deve essere unico» «all’interno dell’ambito in cui è visibile», o «all’interno dell’estensione della sua visibilità», o «entro la portata della sua visibilità», o «nel suo campo di visibilità» (forse l’opzione migliore), o altre espressioni simili. Per quanto mi riguarda, in casi come questi non ho nessun problema ad usare termini inglesi nell’italiano settoriale.

Rust: primo contatto

Rust, chi era costui?

The Rust logo

The Rust logo

Avevo già sentito nominare Rust come linguaggio di programmazione di sistema ma un colloquio al recente GuITmeeting a Roma, ha innescato la mia curiosità.

Tornato dallo splendido weekend romano tra passione storia e viaggio, ho consultato il sito ufficiale di Rust e la pagina corrispondente di Wikipedia.

Con questo post mostrerò i miei primi contatti con il linguaggio dopo aver letto il tutorial dal sito.

Installazione

Al momento, per Windows è disponibile un pacchetto pronto per l’installazione scaricabile dal sito ufficiale, ma per Linux o OS X è necessario compilare i sorgenti. L’operazione non è poi troppo difficile ma richiede un calcolatore con una discreta dotazione hardware (almeno 2GB di RAM), altrimenti si rischia di fare cosa “buona per riscaldare la stanza d’inverno” cit.

Per fortuna esistono un paio di repository su Launchpad da cui si possono installare le versioni del compilatore e degli strumenti necessari, adatti alla nostra versione di Ubuntu.
Per esempio quello di Hans Jørgen Hoel più aggiornato o quello di Kevin Cantu.

Io ho scelto quest’ultimo accontentandomi della versione 0.6 perché la mia Ubuntu è Lucid e nel primo repo non è presente la versione dedicata più recente: la 0.8. Ho installato Rust con i seguenti tre comandi:

$ sudo add-apt-repository ppa:kevincantu/rust
$ sudo apt-get update
$ sudo apt-get install rust

Prove

In Lua la keyword per definire una funzione è function, in Go è func ed in Rust è fn! Ecco quindi il classico Hello World!:

fn main() {
    println("Hello world!")
}

Il sorgente inserito in un file di testo va compilato con il comando (presupponendo di chiamare il sorgente ‘hello.rs’):

$ rust build hello.rs

Altro piccolissimo esempio: implementiamo la funzione gradino che restituisce 1 se l’argomento è positivo, 0 se zero e -1 se negativo:

fn main() {
	println(signum(123).to_str());
}

fn signum(x: int) -> int {
	if x < 0 {
 		-1
 	} else if x > 0 {
		 1
	} else {
		 0
	}
}

Perché manca il ‘return’?
In Rust l’istruzione condizionale if come gli altri costrutti del linguaggio, è un’espressione ciò significa che il condizionale produce un valore, che poi è preso come valore di ritorno della funzione.
Il simbolo del punto e virgola ; NON è il terminatore di riga necessario alla sintassi, ma bensì il modo per annullare l’espressione. In altre parole, con il punto e virgola istruiremo Rust a considerare l’espressione come uno statement. Il valore dell’espressione risulterà così non significativo.
Se infatti si inserisce il ; al termine del blocco dell’if (riga 12) si riceve questo errore:

0: 13:1 error: not all control paths return a value

La creazione di una variabile in Rust non è un’espressione, cioè in essa non viene restituito un valore ma viene compiuta un’azione. Ne segue che il ; finale risulta obbligatorio:

// creiamo una variabile intera
let a = 1000; // ok
let b = 2000  // error: expected `;`

Come avrete notato serve la chiave ‘let’ per creare una variabile (con la terminologia di Rust si dovrebbe dire: per creare uno slot), ma non è tutto.
Per default le variabili sono immutabili, ovvero non possono essere riassegnate. Solo aggiungendo il modificatore ‘mut’ la variabile diventa tale. In questo modo si deve esprimere nel codice cosa è destinato a cambiare, evitando errori:

let a = 1000;
let mut b = 2000;

a += 1; // error: re-assignment of immutable variable
b += 1; // ok

Prime impressioni

Rust è un linguaggio di programmazione sviluppato sotto l’egida di Mozilla Foundation che mira a diventare un C/C++ migliore. Per questo si confronta con il Go che viene giudicato in un’intervista allo stesso Autore di Rust, un buon linguaggio.

Il Go può attrarre sviluppatori dal mondo Python/Ruby. Nonostante che Rust disponga di più strutture dati del Go, sembra essere più tecnico, meno semplice da imparare, e forse potenzialmente migliore. Staremo a vedere se finalmente un nuovo linguaggio sostituirà od almeno affiancherà il C, ancora oggi saldamente sul trono dal 1978.