Puntatori


I puntatori del C (e del C++) sono comodi, utili e facili da imparare, ed una volta imparati, oltre tutto, non si scordano più come l’equilibrio della bicicletta o dei pattini, e si usano con disinvoltura. Non potete ignorarli, sono troppo divertenti…

Indice

  1. Perché tanto odio?
  2. Cosa sono i puntatori
  3. Fico, non sembrano tanto antipatici
  4. Bibliografia & Links
  5. Copyright e Licenza

Perché tanto odio?

Se qualcuno di voi ha mai letto Totem Comics, ricorderà per forza un disegnatore di nome Edika. La sua sezione nella rivista era appunto intitolata “Perché tanto odio?”. Goliardia a parte, non sembra inopportuno parlare dei puntatori cominciando da questa domanda.
I puntatori sono antipatici a molti, un po’ come le sbucciature sulle ginocchia quando impariamo ad andare in bicicletta senza le rotelle, ma quando abbiamo imparato, tutti troviamo assolutamente divertente andare in bicicletta, e troveremmo le rotelle solo un impiccio.
L’antipatia delle sbucciature è solo un lontano ricordo, spesso rimosso, quando viene bilanciata dal divertimento delle curve a coltello o delle discese a manetta.
Preso il via, sta poi a noi decidere se usare la bicicletta solo per rilassanti passeggiate in pianura o per massacranti e gratificanti gare di ciclocross.
Quindi la tesi è: i puntatori sono solo antipatici all’inizio, ma poi è una goduria?
Si, io la vedo così.
Ah, beh, allora anche tu ammetti che i puntatori siano antipatici.
OK, i puntatori sono antipatici, ma solo all’inizio e non per colpa loro. Chiediamoci perché.
La colpa è di coloro che hanno scritto il C (ed il C++), ovvero per il modo in cui hanno definito la sintassi del linguaggio che opera sui puntatori.
Nella lingua italiana, o meglio nel linguaggio Italiano, gli stranieri trovano difficoltoso soprattutto l’uso di termini che assumono un senso diverso rispetto al contesto in cui sono utilizzati. Facciamo subito un esempio: ancora e ancora.
Anzi, dovremmo scrivere àncora ed ancóra, e comunque anche noi italiani avremmo primo, difficoltà a leggere e secondo a collocare queste due parole.
La prima, è il sostantivo che indica il pezzo di ferro con cui le navi attraccano in mare aperto, il secondo è l’avverbio che indica continuazione temporale, facciamo un esempio:
“La nave Alvaro è all’ancora a poche miglia dalla costa”, e “Acquisterei ancora un Mac anche se ne ho già due”.
“Aaah, ecco”?!
Antipatico no?
Beh, chi ha scritto il C (ed il C++) ha operato la stessa cattiveria nei confronti dei puntatori, per cui le tre funzioni che essi forniscono, le forniscono con due soli simboli.
Voi direte: avevano finito i simboli?
No, è proprio cattiveria…

Cosa sono i puntatori

Cosa volete che siano? Sono delle cose divertenti rese antipatiche dalla cattiveria di chi li ha invevntati.
Si, vabbè, non come sono, cosa sono.
V’accontento subito: sono delle variabili che contengono un indirizzo di memoria.
A cosa serve una variabile che contiene un indirizzo?
Beh, serve a fare tante cose, la prima delle quali è risparmiare memoria.
Ah, bella, io dichiaro una variabile che contiene un indirizzo di memoria, che magari contiene dei dati. Se usavo una variabile che identificava quei dati non facevo prima e sprecavo meno memoria?
No. Facciamo un esempio, ma solo per gradire. Mettiamo che voi abbiate definito una variabile che contiene un numero. Mettiamo anche che abbiate definito un’altra variabile, un puntatore, che contiene l’indirizzo della prima, che ricordiamo essere un numero; parleremo in questo caso di puntatore ad un numero, ed in particolare se il numero è un intero, parliamo di puntatore ad intero.
Se il numero è a virgola mobile, parleremo di puntatore a numero in virgola mobile, ecc.
OK, poi?
Poi cerchiamo di usare la prima variabile (il numero).
Cosa avviene? Avviene che se vogliamo sommare a quel numero un altro numero, dovremo usare una funzione (in C la funzione risponde al simbolo “+“) che prende il primo numero, prende il secondo numero e restituisce in una variabile la loro somma.
Lo immaginavate, vero? OK, allora facciamo io abbia dichiarato tre variabili:
int addendo1, addendo2, somma;
Adesso ammettiamo che in qualche modo io abbia assegnato ad addendo1 il valore 2, ad addendo2 il valore 3 ed li abbia sommati mettendo il risultato nella variabile somma:
somma = addendo1 + addendo2; // somma contiene il risultato di addendo1 + addendo2
Alla fine del tutto avrò tre variabili in memoria di cui una conterrà un valore interessante (somma) e due che non servono più ma sono ancora piene.
Questo avviene indipendentemente dal processo usato per arrivare alla somma, avrò comunque tre variabili piene e una sola che m’interessa.
Certo, avremmo potuto assegnare la somma ad una delle due variabili addendo, come in:
addendo1 = addendo1 + addendo2; // addendo1 contiene il risultato di addendo1 + addendo2
Così alla fine avremmo solo due variabili, di cui una utile.
Notate che finora non abbiamo sentito molto la mancanza della nozione “puntatore”…
Ora c’è un fatto.
Se a noi per un motivo che non sto qui a discutere non piacesse come la funzione somma del C calcola il suo risultato, potremmo decidere di scrivere noi una funzione di nome addizione che calcola la somma di due numeri e la restituisce in una variabile.
Immagino che una funzione di questo tipo avrebbe una forma simile a:
int addizione (int numero1, int numero2); // prototipo della funzione addizione
Potremmo chiedere all’utente il valore dei due numeri da sommare, metterli nelle solite variabili addendo1 e addendo2, poi chiamare la funzione che ho descritto per sommarli.
Accadrebbe, accade credetemi, che per fare questa somma benedetta io usi ad un certo punto cinque variabili di cui quattro uguali a due a due.
Quando chiamo la mia funzione, infatti, la macchina non le passa il valore contenuto in addendo1 e addendo2, ma una loro copia, cosa che in quel momento crea uno spreco di memoria del 100%, ovvero ne serve il doppio per fare la stessa cosa.
Alcuni hanno giustamente sottolineato che l’esempio fallisce, poiché quantitativamente potrebbe persino accadere che la dimensione dei puntatori sia maggiore della dimensione dei numeri in gioco, e che usare i puntatori in casi come questo sia persino penalizzante.
In effetti l’esempio vuole introdurre un concetto: chiamare una funzione passandogli dei valori, provoca la copia di questi valori.
Ora immaginate di passare come valori anziché due piccoli numeri interi due immagini, ad esempio per ottenere che una divenga la filigrana (o watermark) dell’altra.
In un caso come questo, senza troppo ragionare, si capisce che in un dato istante (al momento in cui chiamo la funzione) avrei in memoria (o peggio ancora in swap…) quattro immagini, uguali a due a due. E che cacchio, non si può evitare di sprecare tutta questa memoria?
Come no, usando i puntatori.
Usandoli, posso dire alla funzione di andarsi a leggere i valori dei parametri che le occorrono al loro indirizzo.
Capito il trucco?!

Fico, non sembrano tanto antipatici

No, infatti, immaginate appunto quando invece di passare ad una funzione due numeretti, gli passate delle strutture di memoria belle grandi e complesse, ed immaginate quanto spazio e tempo risparmiate, passando sempre un piccolo indirizzo, invece di tutta un’enciclopedia di codice binario.
E’ vero infatti che i puntatori sono variabili, ma è vero anche chi i puntatori sono variabili “piccole”.
Cosa voglio dire?
Mettiamo che un computer abbia la possibilità di trattare gli indirizzi a 64bit, ovvero che un indirizzo di memoria sia un numero che espresso in binario occupi al massimo 64 cifre binarie.
64 bit sono 8 byte.
Da questo discende che un puntatore a qualunque oggetto occupa al massimo quella dimensione.
Alcuni dati, come le immagini, raggiungono velocemente dimensioni nell’ordine dei MByte, ed un MByte sono già 1000 Kbyte.
Se io passassi ad una funzione la copia di una immagine, potrei passarle qualche MByte di dati (mettiamo anche solo un MByte), se le passo il suo indirizzo in memoria le passo al massimo (ne sono certo) 8 byte.
C’è una differenza di 125.000 volte.
Non si tratta quindi solo di contare il numero delle variabili, si tratta di pesarle.
Non solo, se tratto l’immagine con un filtro e l’immagine dopo il trattamento è raddoppiata di dimensione, il suo puntatore è sempre lo stesso quanto a dimensione.
Ma da dove deriva allora l’antipatia?
Vi servo subito, e cominciamo da come si dichiarano i puntatori in C (ed in C++):
int* pippo;
Ecco qui.
Delusi? vi aspettavate una cosa molto complicata? Beh, un po’ lo è. Provate a leggere quello che ho scritto.
Vediamo… int asterisco pippo puntevvirgola? E che vuol dire?
Eh, ve lo ho detto la cattiveria… Quella scritta si legge “pippo è un puntatore ad un intero”.
Beh, non sembra difficile…
Si, ma quale intero? Ah…
Infatti pippo, preso così non è che faccia molto, ed in effetti per il momento si è solo presentato, ha solo detto “sono pippo, un puntatore ad un numero intero”.
Per sapere a cosa “punta” pippo, dobbiamo assegnarlo (quella di prima è una dichiarazione, notate che non c’è l’uguale?), assegnarlo a cosa?
Ad un indirizzo, mi sembra ovvio, sennò che puntatore sarebbe? Mettiamo che abbiamo già dichiarato (ed assegnato):
int addendo1 = 2;
Scrivere: pippo = &addendo1;
Significa appunto dire che pippo (un puntatore ad intero) contiene l’indirizzo della variabile addendo1, o più brevemente possiamo dire che pippo punta addendo1.
A questo punto sappiamo che:

  • pippo è un puntatore ad un (tipo) intero
  • addendo1 contiene il numero 2
  • pippo contiene l’indirizzo di memoria di addendo1

Adesso arriva la parte violenta, sarebbe meglio che metteste i bambini a letto
Se chiedo a pippo cosa contiene, la risposta è scontata: un indirizzo di memoria.
Se io vedo pippo nel codice e magari so che è un puntatore perché ho visto che è stato dichiarato come puntatore, e mi domandassi a quale valore stia puntando in questo momento pippo, glielo devo chiedere, o meglio glielo chiedo e lo stampo a video, così lo posso leggere.
Se non fosse chiaro sto chiedendo a pippo: “qual è il valore contenuto all’indirizzo cui stai puntando adesso?”; insomma voglio che lui mi dica “2” tanto per dirsela tutta.
Beh, la domanda suona pressappoco così:
valore = *pippo; Ricorda qualcosa?
E certo che ricorda qualcosa, ricorda il modo in cui pippo è stato dichiarato!
Ah, si?! Leggetelo.
“pippo è un puntatore ad un uguale valore”?!
No, infatti questa non è una dichiarazione, è una assegnazione (c’è l’uguale), e quindi “asterisco pippo” non ci dice che pippo è un puntatore, ma ci dice che il valore contenuto alla posizione di memoria che pippo sta puntando da quando lo conosciamo, ovvero il numero 2 lo vogliamo mettere in una variabile di nome valore.
In pratica ecco la vera cattiveria, usare la stessa notazione (“asterisco pippo”) per fare due cose diverse:

  • int* pippo; ovvero pippo è un puntatore ad un (tipo) intero
  • valore = *pippo; ovvero il dato il cui indirizzo è contenuto da pippo il puntatore

Isomma:

  • int addendo1 = 2; // la variabile intera addendo1 è uguale a due
  • int* pippo; // pippo è un puntatore ad un (tipo) intero
  • pippo = &addendo1; // pippo punta all'indirizzo di addendo1
  • printf ("%d", *pippo); // stampa il numero 2
  • printf ("%p", pippo); /* stampa un numeraccio strano che probabilmente inizia per 0x e segue con una serie di numeri, che sono l'indirizzo di memoria a cui sta puntando pippo */

Perché tutto fosse più chiaro sarebbe bastato usare un terzo simbolo, come @ (at o chiocciola), per identificare il terzo membro della famiglia, e avremmo avuto uno strumento potente che non rompe l’anima a nessuno, guardate:

  • int* pippo; // pippo è un puntatore ad un (tipo) intero
  • pippo = &addendo1; // pippo punta all'indirizzo di addendo1
  • printf ("%d", @pippo); // stampa il valore puntato da pippo

Prometto, se un giorno dovessi scrivere un linguaggio, userei tre simboli per queste tre cose, i primi due presi dal C e il terzo, diverso dai primi due, per non farvi perdere tempo.
Ma ormai il danno è fatto, ed il C ed il C++ usano solo * e & per tutti e tre ed allora imparate una regoletta empirica ma efficace:

  • Se in una espressione compare &, si sta parlando di un indirizzo di memoria
  • Se in una espressione compare = seguito da un * e dal nome di una variabile si sta cercando il valore cui un puntatore si riferisce
  • Se in una espressione, che non sia un assegnamento, compare * si sta probabilmente dichiarando un puntatore

Tanto per dirsela tutta, in una espressione che non sia una dichiarazione, * e & producono risultati opposti, ovvero l’uno ci dà un valore e l’altro l’indirizzo di quel valore

Annunci

Impostazioni di default di visualizzazione dei browser

Sviluppando siti web, capita a volte che ci si innamori di loro.
Poi capita che gli altri guardandoli li trovino molto meno sexy; come mai?

A parte il discorso dei gusti (di cui non disputandum…), c’è il problema delle impostazioni di default, che in pochi cambiano.
Se voi che sviluppate siti le cambiate, sappiate che i vostri utenti NON vedono i siti come li avete pensati, ma come il loro browser – con le impostazioni dei default – li fa vedere loro.
Almeno in fase di test, reimpostate i browser alle loro preferenze di default.
Se non le ricordate, le trovate di seguito.

Firefox

Safari

CSS – Cascading Style Sheet: Caratteristiche nominali, posizionali e contestuali


Una delle possibilità dei CSS1 è quella di definire (o ridefinire) le caratteristiche degli elementi, in modi diversi. Tre di questi, ovvero la definizione nominale, quella posizionale e quella contestuale, sono quelle di cui parliamo in questa pagina. La prima, quella nominale, è il modo con cui “un elemento che si chiama in un certo modo assume determinate caratteristiche”. La seconda, è quella per cui “un elemento assume determinate caratteristiche per il fatto di trovarsi in una certa posizione all’interno del codice della pagina”. La terza è quella caratteristica che si manifesta per gli “elementi che si vengano a trovare in determinate circostanze”. Vediamo come…

Indice

  1. Selettori, dichiarazioni e regole (Contextual Selectors)

Selettori, dichiarazioni e regole (Contextual Selectors)

Come scrivere un CSS

Un CSS si compone di regole, quindi il linguaggio usato per i CSS è di tipo normativo ovvero detta il comportamento degli elementi della pagina di Markup, che può essere scritta in HTML, XHTML, ecc.
Una regola è la minima parte di cui si compone un CSS; un CSS ne può contenere un numero qualunque, anche nessuna; una regola appare in questo modo:

p
{
display: inline;
}

la regola è composta da un selettore (nell’esempio “p“), da una coppia di parentesi graffe (“{” e “}“) e da una o più dichiarazioni (nell’esempio: “display: inline;“).
Una dichiarazione è composta a sua volta da una proprietà (lo scritto a sinistra dei due punti, quindi nell’esempio “display“) e da un valore (lo scritto alla destra dei due punti, nell’esempio “inline“); sarà chiaro a questo punto che proprietà e valore devono essere divisi dal carattere due punti (“:“) e che la dichiarazione deve essere terminata da un punto e virgola (“;“).
E’ tutto.

Selettori

Selettore Significato
* selettore universale, ovvero tutti gli elementi (non a caso il carattere viene anche letto “all”)
h1
{...}
selettore di tipo: tutte le occorrenze dell’elemento h1
h1, h2
{...}
selettore di tipo: tutte le occorrenze degli elementi h1 e h2 (notate la virgola)
p em
{...}
selettore di tipo (posizionale): tutte le occorrenze dell’elemento em dentro un elemento p
.special
{...}
selettore di classe: tutti gli elementi aventi classe special
.special.extra
{...}
selettore di classe: tutti gli elementi aventi classe special e classe extra
h1.special
{...}
selettore di classe: tutte le occorrenze dell’elemento h1 aventi classe special
h1.special.extra
{...}
selettore di classe: tutte le occorrenze dell’elemento h1 aventi classe special e classe extra
elemento#questo {...} l’elemento elemento avente id “questo”
td[rowspan]
{...}
tutti gli elementi td aventi l’attributo rowspan assegnato
td[rowspan="2"] tutti gli elementi td aventi l’attributo rowspan assegnato a 2
h1 > em
{...}
occorrenze di un elemento em in un h1 come figlio

Uso delle regole nel linguaggio di Markup (HTML, XHTML, ecc.)

* il selettore universale non deve essere (per definizione) usato esplicitamente perché si applica a tutti gli elementi.
Un selettore di tipo viene usato nel documento di Markup in maniera naturale, poiché riguarda un TAG del linguaggio, ad esempio <h1>, <h2>, <p>, <li>, ecc.
Un selettore di tipo posizionale viene usato nel documento di Markup in maniera naturale, avendo cura di annidare gli elementi in modo che la loro posizione reciproca ricada nella regola CSS, ad esempio: p em {...} verrà utilizzata dal codice XHTML

(...)
<p>Lorem ipsum <em>dolor</em> sit amet</p>
(...)

ma NON dal codice

(...)
<p>Lorem ipsum</p> <em>dolor</em><p> sit amet</p>
(...)

perché l’elemento em NON si trova all’interno di un elemento p

Prettamente contestuali

a:link
a:hover
a:active
a:visited
a:focus

Prettamente posizionali

elemento:first-child

Pseudoelementi

elemento:before
elemento:after
elemento:active
elemento:visited
elemento:focus
elemento:first-line

* * *

  1. W3C – Recommendation – CSS2 5. Selectors