sabato 28 febbraio 2015

Modern C++ - Le funzioni

Salve a tutti e ben ritornati su queste pagine del corso di Modern C++, dopo una pausa dovuta ad impegni personali.

Le ultime puntate abbiamo parlato di puntatori e allocazione statica e dinamica: abbiamo visto anche come funzionano gli operatori * e & che permettono di accedere al valore contenuto nelle variabili mediante il loro indirizzo di memoria.

Oggi parliamo delle funzioni e del passaggio dei parametri per valore o per indirizzo.
Innanzitutto, una funzione si dichiara nel seguente modo:

[tipo di dato] nomeFunzione([lista di parametri])
{
    //corpo della funzione
}

dove il tipo di dato puo' essere built-in (char, int, float, double, bool), un vettore o stringa di caratteri (quindi std::string, std::vector<>, ecc.) oppure di tipo void, ovvero non ritorna alcun valore.
Il nome della funzione puo' esser definita in qualsiasi modo, secondo linee di stile personale o convenzionali, come ad esempio:
  • nome_funzione
  • NomeFunzione
  • nomeFunzione
  • _nomeFunzione
In genere, in C++ si preferisce utilizzare la prima lettera maiuscola solo per Strutture e Classi, mentre per le funzioni, si preferisce utilizzare il carattere minore e, preferibilimente, un nome esplicativo di cosa viene eseguito all'interno di una funzione, del tipo aggiungi_numero, salva_file, ecc., in modo da non dover scrivere commenti aggiuntivi in cui si descrive cosa viene eseguito all'interno della funzione.

Una funzione, al fine di operare, necessita (ma non sempre) di dati di input sui quali operare: una funzione che esegue la somma di due numeri interi e che restituisce il risultato, ha bisogno di esser definita nel seguente modo:

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

Come si vede dall'esempio, nella lista dei parametri, ne ho specificati 2, di tipo int e che si chiamano a e b. In uscita da questa funzione, si ottiene la somma dei due parametri, espressa dalla parola chiave return. Solo le funzioni di tipo void non necessitano della parola chiave return in modo esplicito, ma solo se si vuole uscire dalla funzione stessa in modo forzato (si pensi ad un ciclo infinito in cui si verifica una condizione per cui si debba interromperlo).

Ma come viene chiamata questa funzione? Si pensi al seguente esempio:

#include <iostream>
using namespace std;

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

int main()
{
   int risultato = somma(2,3);  //a = 2, b = 3
   cout << "Il risultato di 2+3 e': " << risultato << endl; //6!
   return 0;
}
In questo modo, il valore 2 viene copiato nella variabile a e il valore 3 viene copiato nella variabile b. Tale tipo di chiamata viene detto passaggio per valore, ovvero i parametri specificati, al termine dell'esecuzione della funzione, tornano al loro valore originale, precedente alla chiamata della funzione stessa. Nell'esempio, a = 2 quando la funzione somma() termina.

Se volessimo mantenere il valore nuovo di a anche dopo l'esecuzione della funzione somma()? Dovremmo ricorrere al passaggio per riferimento, ovvero passare il riferimento della variabile, in modo da modificarla senza eseguirne una copia temporanea:

#include <iostream>
using namespace std;

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

int main()
{
   int x = 2;
   int y = 3; 
   int risultato = somma(x, y);  //a = x, b = y
   //adesso x = 3
   cout << "Il risultato di x+y e': " << risultato << endl; //6!
   return 0;
}
 
Occorre quindi modificare la lista dei parametri, facendo precedere l'operatore & al nome della variabile.

Una alternativa al passaggio di parametri per riferimento e' il passaggio di parametri per indirizzo, ovvero mediante l'operatore *:

#include <iostream>
using namespace std;

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

int main()
{
   int x = 2;
   int y = 3;
   int risultato = somma(&x,&x);  //a = x, b = y
   //adesso x = 3
   cout << "Il risultato di x+y e': " << risultato << endl; //6!
   return 0;
}
 
Sia per il passaggio per riferimento o per indirizzo, occorre dichiarare delle variabili, in modo da poter poi salvarne il risultato dopo l'esecuzione della funzione.
Per quanto riguarda tipi di dato semplici (int, double, float), le prestazioni dei tre metodi si equivalgono, mentre per quanto riguarda strutture di dati grandi o complesse, e' preferibile utilizzare il passaggio per riferimento o indirizzo, mai quello per valore. Nel caso in cui non si voglia modificare il valore delle variabili all'interno del corpo di una funzione, occorre far precedere la parola chiave const:

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

in tal modo, si garantisce che a e b non possono esser modificate.

Alla prossima puntata :-)

lunedì 16 febbraio 2015

Lubit 5 Jean, le ragioni di un nome in codice e di un progetto.

Salve a tutti!

Il team Lubit Project è lieto di annunciarvi l'uscita di Lubit 5, a 32 e a 64 bit!

Lubit 5, nome in codice, Jean!

Giusto due parole per spiegarvi chi era Jean. Bisogna partire da una visita che, insieme ad un gruppo di amici, feci a San Leucio, una frazione del comune di Caserta, la cui fama è legata alla manifattura della seta e che trasformò quella che doveva essere una residenza di caccia, il Complesso Monumentale del Belvedere, in una vera e propria realtà industriale alla cui base vigevano principi di uguaglianza sociale ed economica.

Qui ebbi la fortuna di ammirare il Telaio Jacquard, il cui inventore pare sia stato Joseph Marie Jacquard. Questo telaio è la prima applicazione pratica delle schede perforate. Siamo agli inizi dell'800. Il telaio ebbe così tanto successo che Napoleone I conferì a Jacquard una pensione onoraria per aver brevettato la macchina. In realtà, però, il prototipo del telaio Jacquard è stato realizzato quasi tre secoli prima, nella seconda metà del secolo XV, da un tessitore catanzarese, conosciuto a Lione come Jean le Calabrais...

A Lione, nel '500, i maestri setaioli catanzaresi vi andarono per insegnare, non per apprendere, e non a caso uno dei più antichi telai, a schede perforate, prototipo del computer, oggi esposti proprio a Lione nel Museo della Seta, porta ancora inciso il nome del maestro “Jean le calabrais”. E con Lubit 5 vogliamo rendere omaggio proprio a lui, a Jean.


Lubit 5 è leggera, poco avida di risorse, fluida, estremamente elegante e raffinata nelle scelte grafiche. Pensata per ridare vita a pc obsoleti o per rendere ancora più performanti i computer di ultima generazione.

Molte sono le novità rispetto alle versioni precedenti e molti sono i tools propri di Lubit, cioè creati ex novo per Lubit.










.



Tra i programmi installati trovate anche Firefox 35, abiword, pidgin, pcmanfm in lubit 32 bit, thunar in lubit 64 bit, Synaptic, lxterminal, liferea, istantanea, gparted, bleachbit, GDEbi, Evince, Tint2, DeaDBeeF, Xfburn, Guvcview, Gnome-MPlayer, Nitrogen in Lubit 64 bit, LXAppearance, Volumeicon-alsa, gsimplecal, svariati codec audio e video che ci consetiranno di eseguire qualsiasi file multimediale già da Live, etc.

Lubit è stata rivisitata ex novo anche graficamente, ad iniziare dal tema del plymouth, dallo sfondo della scrivana, dallo sfondo di lightdm, per finire ai tasti dello spegnimento.


Perchè Lubit?

Perchè il mondo GNU/Linux ti dà le risorse, ti mette in condizione di acquisire le compentenze giuste e ti permette anche di creare tutto ciò che ritieni opportuno vada bene per te stesso e per la comunità. In definitiva, le quattro libertà, di cui tanto si parla, tengono a ribadire proprio questo concetto. Lubit nasce con l'intento di operare una inversione ad U. Non un ritorno al passato, ma una valorizzazione, alla luce dei progressi fatti, di ciò che GNU/Linux è stato. Il nostro scopo è quello di far sentire l'utente parte di un progetto, nel senso che l'utente viene messo nelle condizioni di operare e modificare ogni cosa, per questo amiamo definire Lubit un cantiere aperto. Lubit viene creata a casa, con le poche risorse che si hanno a disposizione, proprio perchè è un progetto artigianale. Ed è questa la ragione per cui ad ogni rilascio bisogna installarla, cioè non si può aggiornare la versione precedente. Con Lubit "sforniamo" informatica artigianale, cerchiamo di dare adito alla nostra creatività, creiamo applicazioni prevalentemente in Bash o in Python. Con Lubit, in qualche modo, bisogna sporcarsi le mani. Per alcuni versi si diventa protagonisti e non semplici utilizzatori cui viene chiesto di cliccare asetticamente i tasti del mouse.

Per la presentazione ufficiale vi rimandiamo al sito del progetto.

Per scaricare Lubit 5, sia la versione a 32 che a 64 bit, si vada a questa pagina: qui


Alla prossima ;)

lunedì 9 febbraio 2015

Anteprima Lubit 5

Salve!

Tra qualche giorno uscirà Lubit 5, nelle versioni 32 e 64 bit. Tanto lavoro fatto e tanto ancora da farne.

Intanto le applicazioni proprie di Lubit aumentano da rilascio in rilascio. Questa è la volta del pannello di controllo, del software center e di almeno altre 30 scripts, molti dei quali lavorano dietro le quinte per rendere più prestante il sistema. 

Si sta lavorando su Lubit 5 da fine settembre. Inizialmente pensavamo di far uscire solo la 64 bit, ma poi è successo che le novità sono diventate talmente tante  che non avrebbe avuto senso far uscire una Lubit 4 a 64 bit così diversa dalla versione a 32 bit, che è uscita a novembre.

Diciamo che quando siamo partiti con questo progetto, ormai due anni fa, non potevamo pensare affatto che ci saremmo dovuti cimentare anche con  questa architettura. Anzi, lo abbiamo sempre escluso. Poi è successo che... è successo che ci siamo decisi date le tantissime richieste in tal senso.

Il code name di Lubit 5 è Jean. Giusto due parole per spiegarvi chi era Jean.
Bisogna partire dal Telaio Jacquard, il cui inventore pare sia stato Joseph Marie Jacquard. Questo telaio è la prima applicazione pratica delle schede perforate. Siamo agli inizi dell'800. Il telaio ebbe così tanto successo che Napoleone I conferì a Jacquard una pensione onoraria per aver brevettato la macchina. In realtà, però, il prototipo del telaio Jacquard è stato realizzato quasi tre secoli prima, nella seconda metà del secolo XV, da un tessitore catanzarese, conosciuto a Lione come Jean le Calabrais...


Vabbè, vi lascio con due video di anteprima.






Alla prossima! 

venerdì 6 febbraio 2015

Modern C++ - Memoria statica e dinamica

Nel precedente post, abbiamo visto cosa sono i puntatori e come si possono utilizzare per accedere alle celle di memoria contenenti le informazioni utilizzate all'interno di un programma.

Oggi vediamo come funziona il meccanismo di creazione dei dati all'interno della memoria di un computer, utilizzando il C++. I puntatori assumono un ruolo fondamentale in questo contesto, facilitando il compito di tener traccia degli indirizzi delle celle di memoria nelle quali vengono creati i dati.

Innanzitutto, occorre dire che in C e C++ esistono due tipi di memoria: lo stack e l'heap, noto anche come free-store, come Stroustrup stesso lo definisce.

Stack


Lo Stack e' la porzione fissa di memoria, in termini di bytes o multipli di bytes, assegnata ad un programma. Quando una funziona viene eseguita, un blocco di questo stack viene "prenotato" al fine di poter creare variabili che sono utilizzate all'interno della funzione stessa. Quando la funzione termina la sua esecuzione, il blocco prenotato viene liberato e puo' esser utilizzato per eventuali successive esecuzioni della funzione stessa. Immaginate lo stack come una pila di piatti: un piatto corrisponde alla porzione di memoria prenotata da una funzione e il piatto stesso puo' contenere del cibo (ovvero i dati usati nella funzione). I piatti vengono accatastati uno sull'altro, seguendo l'ordine di utilizzo delle funzioni, e vengono liberati in ordine inverso, ovvero l'ultimo piatto aggiunto e' il primo ad esser rimosso. Usando una terminologia piu' tecnica, lo stack segue un ordinamento di tipo LIFO (Last In, First Out).
Caratteristica fondamentale dello stack e' quella di contenere strutture di dati di dimensione fissa: durante l'utilizzo di un programma, la dimensione dei dati non puo' crescere.
Si pensi a dichiarazioni di questo tipo:

int x{10}; //1 int x 4 bytes = 4 bytes
const char stringa[] = "Ciao!"; //5 char x 1 byte = 5 bytes
double elementi[200];   //200 double x 8 bytes = 1600 bytes

Creando le variabili in questo modo, ovvero assegnando loro una dimensione predefinita (tipo il vettore elementi che contiene 200 dati), fa si' che esse vengano immagazzinate nello stack e che esse non possano esser modificate nella dimensione (ad esempio, non possiamo aggiungere o rimuovere elementi dal vettore oppure dalla stringa, bensi' siamo costretti a creare nuove variabili in futuro). Questo tipo di allocazione viene definito allocazione statica della memoria. Lo stack costituisce un'area ad accesso rapido e in generale si tende a preferirlo all'heap poiche' offre prestazioni maggiori in termini di velocita' e spesso le informazioni contenute nello stack vengono immagazzinate nella cache della CPU se esse vengono utilizzate frequentemente. Lo svantaggio diretto deriva dal fatto che una variabile non puo' esser modificata nelle dimensioni: cio' significa che occorre definire a priori la dimensione dei vettori o delle stringhe e questo risulta esser spiacevole nel caso in cui parte di tali contenitori venga inutilizzato oppure risultino essere insufficienti a contenere informazioni (si pensi ad esempio ad un vettore di 10 elementi, ma l'utente inserisce da tastiera 11 elementi: dove va' a finire l'11esimo elemento?).


Heap


L'Heap e' la memoria utilizzata per l'allocazione dinamica delle variabili. Diversamente dallo stack, nell'heap si puo' prenotare e liberare un blocco di memoria in qualsiasi momento e la sua dimensione puo' esser definita a run-time, ovvero durante l'esecuzione del programma stesso. Ovviamente, il prezzo da pagare rispetto allo stack e' una gestione piu' complessa richiesta per tracciare i blocchi di memoria allocati o liberati, ma e' qui che intervengono i puntatori.
Al fine di allocare un nuovo blocco di memoria nell'heap, si utilizza l'operatore new mentre per liberare tale blocco si utilizza l'operatore delete. Buona norma prevede che per ogni nuovo blocco di memoria prenotato mediante new, ci sia un corrispondente uso dell'operatore delete.
Questo concetto vale per il linguaggio C e per il C++ classico. Per il Modern C++, la situazione e' stata notevolmente semplificata; lo vediamo con un esempio:

Nell'esempio potete notare i 2 diversi approcci di stile. Nella prima funzione, chiamata classic_cpp, eseguo l'allocazione dinamica mediante i cosidetti naked pointers, ovvero utilizzando l'operazione new senza alcuna protezione: cio' significa che utilizzo tale operazione senza aver la garanzia di poter allocare effettivamente una regione di memoria. Infatti, nel caso in cui l'operazione new fallisca (ad esempio se si richiede la creazione di un array di 100 milioni di elementi, in dipendenza del computer e della RAM a disposizione), il puntatore risulta invalido e ogni futuro tentativo di accesso a tale zona di memoria risulta non valido. La regola d'oro e' sempre la stessa: ad ogni new corrisponde un delete e buona prassi e' quella di assegnare il valore NULL o 0 (zero) al puntatore, questo nel caso del C++ old style: questo permette di usare i puntatori nelle condizioni:
if(p != NULL)
{
   //bla bla
}

Nel caso del Modern C++, in soccorso vengono i cosidetti smart pointers, che consentono una gestione facilitata dell'allocazione dinamica e che automaticamente liberano la porzione di memoria allocata: l'operazione new questa volta e' avvolta (wrapped) e protetta all'interno dello smart pointer il quale puo' lanciare un errore (una eccezione) nel caso in cui la memoria richiesta non e' disponibile e l'operazione di delete viene eseguita al suo interno, senza alcuna esplicita richiesta dello sviluppatore.
Lo smart pointer ha peculiari funzioni, tra cui .get() oppure ->, che restituiscono un riferimento (la classica &) al tipo di dato e .reset() che permette di azzerare il contenuto dello smart pointer. Per accedere al valore effettivo della variabile, si continua ad usare l'operatore di dereferenziazione *, come nel listato di esempio.
Gli smart pointers sono di due tipi: unique_ptr e shared_ptr. Uno unique_ptr rappresenta l'unico custode dell'area di memoria allocata e non puo' esser copiato: quando un unique_ptr viene distrutto, l'area di memoria allocata viene rilasciata automaticamente. La regol base e' che ci puo' esser un solo unique_ptr per ogni risorsa.
Lo shared_ptr invece permette la condivisione di tale area di memoria tra piu' puntatori, grazie ad un contatore interno: ogni copia dello shared_ptr fa si che questo contatore venga incrementato di 1 unita', mentre ogni distruzione di uno shared_ptr decrementa tale contatore di 1. Quando questo contatore arriva a 0, ovvero nessun shared_ptr utilizza tale area di memoria, allora l'area di memoria viene liberata automaticamente.
In breve:
  1. unique_ptr si usa quando la risorsa non deve esser condivisa tra piu' puntatori
  2. shared_ptr si usa quando si vuol condividere la risorsa tra piu' puntatori, che modificano la risorsa stessa
In Modern C++, quindi, e' preferibile utilizzare gli smart pointers per via di questa facilitazione nel meccanismo di gestione della memoria, senza dover richiedere esplicitamente l'operazione di delete, la quale, se dimenticata, porta al cosidetto leak, ovvero ad uno spreco di memoria. Sfortunatamente (o forse fortunatamente?) in C++ non esiste il garbage collector come in Java, quindi tocca allo sviluppatore tener traccia della memoria utilizzata.
Un altro fondamentale appunto e' il seguente: e' preferibile utilizzare strutture dinamiche come vector oppure string per contenere un numero variabile di elementi, che siano caratteri o numeri: gli array C-style non solo risultano dannosi nel caso di un errato accesso ad essi, ma portano ad un consumo di memoria maggiore, se non vengono utilizzati del tutto.

Perdonatemi per il post troppo lungo e magari un po' difficile da capire con una prima lettura ma l'argomento in se' risulta vasto e richiede anche una conoscenza di base dell'architettura di un calcolatore. Grazie all'uso di queste conoscenze potremo costruire applicazioni C++ piu' complesse e flessibili con il susseguirsi delle lezioni di questo corso! A presto!!