Java Tutorial - Parte 1 0.1
Un tutorial per esempi
Caricamento in corso...
Ricerca in corso...
Nessun risultato
L'elaborazione elettronica

Storia dei calcolatori

Fin dalla antichità l'uomo ha cercato di produrre macchine che rendessero più agevole il calcolo aritmetico: nonostante la grande potenzialità del cervello umano, il calcolo aritmetico è, comunque, una operazione complessa da portare a termine; provate per esempio ad eseguire una divisione con dividendi di sei o sette cifre oppure divisori con tre o quattro cifre e vi renderete conto che il tempo impiegato per l'operazione è abbastanza lungo.

Le macchine usate nei secoli passati come l'abaco o il regolo calcolatore avevano un funzionamento esclusivamente meccanico e non possono nemmeno lontanamente essere paragonate ai moderni elaboratori perchè la loro struttura più intima era completamente diversa: possedevano infatti una architettura analogica in contrapposizione alla archituttura digitale delle moderne macchine.

Possiamo perciò affermare che l'era del calcolo elettronico comincia nella prima metà del secolo scorso quando furono inventate le prime macchine con architettura digitale come l'E.N.I.A.C. ed il Mark I: a quell'epoca non era ancora stato inventato il transistor e questi calcolatori erano principalmente composti da valvole termoioniche collegate tra loro mediante contatti saldati manualmente.
Questi calcolatori erano enormi, pesavano qualche tonnellata ed erano estremamente difficili da programmare ma, soprattutto, molto lenti nelle elaborazioni e soffrivano, a causa della loro architettura, di surriscaldamento.

L'avvento del transistor rivoluzionò totalmente questo settore: al giorno d'oggi un microprocessore può contenere milioni di transistor in pochi centimetri quadrati con un peso di pochi grammi: il basso consumo elettrico, inoltre, è stato la soluzione al problema del surriscaldamento anche se alcuni moderni processori devono dissipare il loro calore perchè spinti a frequenze di clock elevatissime.

La struttura di un calcolatore

Trascurando i calcolatori a funzionamento meccanico, tutti gli altri, fin dal primo calcolatore a valvole, si basano sulla stessa struttura principale, sebbene molto semplificata, descritta nella figura sottostante:

La CPU

La CPU (Central Processing Unit = Unità Centrale di Elaborazione) è il vero cervello del calcolatore. E' composta da migliaia o addirittura milioni di transistor ed è in grado di eseguire le istruzioni che il programmatore ha stabilito. La CPU è composta da diversi registri che prelevano i dati e le istruzioni dalla memoria principale, detta RAM. I dati vengono elaborati nei registri del processore ed il risultato viene posto in un altro registro oppure in memoria RAM. Il numero di registri della CPU è molto limitato, per esempio le moderne CPU x86 hanno:

  • almeno 4 registri ad uso generico
  • 2 registri indice
  • 6 registri di segmento
  • da 4 a 6 registri di stato
  • da 4 a 10 registri dedicati alla virgola mobile

E' ovvio che i dati ed i programmi devono essere memorizzati da qualche altra parte poichè sarebbe impossibile contenerli tutti nei registri della CPU.

La RAM

La RAM (Random Access Memory = Memoria ad Accesso Casuale) è la memoria principale del calcolatore direttamente accessibile alla CPU ed è quindi nella RAM che vengono memorizzati i dati ed i programmi da dove verranno prelevati dalla CPU ed inseriti nei suoi registri per la elaborazione. La CPU è solo in grado di indirizzare la memoria RAM quindi per poter prelevare dati ed istruzioni, questi ultimi devono risiedere in RAM. Essendo elettronica, la memoria RAM è velocissima: il tempo di accesso ad essa si misura in nanosecondi. Il suo più evidente svantaggio è che è volatile: quando viene a mancare la corrente elettrica, la RAM si scarica e perde tutti i dati.

Le memorie di massa

Per ovviare alla perdita dei dati da parte della RAM, è necessario che dati e programmi siano memorizzati su un supporto permanente. Questi tipi di supporti non sono elettronici come la RAM ma magnetici oppure ottici in modo che essi possano preservare il loro stato anche in assenza di corrente elettrica. Le memorie di massa sono gli hard disk, i CD-ROM e DVD-ROM, le memorie flash (quelle delle penne USB), etc.
Dati e programmi vengono caricati dalle memorie di massa nella RAM per poter poi essere eseguiti dalla CPU. Anche se non così veloci come la RAM, le moderne memorie di massa hanno tempi di accesso piuttosto bassi, che si misurano in millisecondi.

Dispositivi di I/O

Per interagire con il calcolatore, cioè per poter inserire dati e programmi e per vederne i risultati, sono necessari dispositivi particolari detti di I/O (Input/Output). Appartengono a questa categoria il monitor (dispositivo di solo output ad eccezione dei moderni monitor touch screen), la tastiera ed il mouse (dispositivi di solo input), tavolette grafiche, penne, altoparlanti e telecamere, joystick etc. Di tutti i dispositivi collegati al bus di sistema i dispositivi di I/O sono i più lenti (pensate ad esempio alla tastiera) anche se alcuni di essi, in particolare i monitor, sono piuttosto veloci.

DMA controller

Sia i dispositivi di I/O che le memorie di massa (che possono essere equiparati, da un punto di vista logico, ai dispositivi di I/O) sono collegati alla CPU attraverso delle porte di I/O. Lo svantaggio principale in questa organizzazione è che le operazioni di I/O coinvolgono la CPU per poter caricare i dati dal dispositivo alla memoria principale (la RAM) e viceversa. Mentre per alcuni di questi dispositivi (per esempio la tastiera) questo non è un problema (l'utente più veloce riesce a battere una media di 2-3 caratteri al secondo), per altri, come le memorie di massa che hanno un throughput (=ampiezza di trasferimento) di decine o anche centinaia di kilobytes al secondo questo potrebbe impegnare la CPU per molto tempo.
Per sollevare la CPU da questa incombenza, i dispositivi particolarmente veloci come per esempio gli harddisk, sono collegati ad uno speciale circuito detto Direct Memory Access (Accesso Diretto alla Memoria) che permette alla CPU di assegnare ad esso un blocco di memoria dove eseguire il trasferimento di grandi quantità di dati senza alcun intervento da parte della CPU.

I mattoni fondamentali del calcolo elettronico

I mattoni fondamentali di ogni programma per computer in qualunque linguaggio esso sia realizzato sono quattro:

  • le istruzioni, ossia le azioni da compiere sui dati per ottenere un certo risultato come per esempio visualizza, disegna oppure calcola
  • le variabili, ossia i dati sui quali deve essere svolta la aione: cosa si deve visualizzare, disegnare o calcolare?
  • le condizioni, ossia la possibilità di verificare se un evento si è verificato o meno; per esempio se l'ora attuale è compresa tra le 8 e le 12, il programma deve visualizzare buongiorno, altrimenti visualizzerà buonpomeriggio.
  • i cicli e le procedure, ossia la possibilità di ripetere due o più volte (anche milioni di volte) le istruzioni; per esempio la somma di n valori di temperatura giornaliera per calcolarne la media

Il mondo digitale

Sicuramente avrete sentito parlare della diatriba analogico vs. digitale e sulle argomentazioni che vorrebbero il mondo analogico qualitativamente migliore e più rispondente alla realtà rispetto al digitale ma (trascurando la diatriba, che non è lo scopo di questa guida), che cosa significa digitale?
La parola deriva dal vocabolo inglese digit che significa cifra, numero; a sua volta il vocabolo inglese deriva dal latino digitus che significa dito.

In informatica, tutto ciò che attiene l'elaborazione elettronica attraverso i computer è digitale nel senso inglese del termine e significa che i computers possono comprendere e manipolare esclusivamente quantità numeriche ovverossia, solo ed esclusivamente numeri.
Se per caso state pensando che ciò non è vero, in quanto state leggendo questa guida scritta in un alfabeto umano, ebbene avrete una delusione: poichè i computer possono solo gestire quantità numeriche, l'uomo ha stabilito una corrispondenza tra un codice numerico e le lettere dell'alfabeto.
Questa codifica prende il nome di character set (=insieme di caratteri) così, ad esempio, si è stabilito che il codice 65 corrisponda alla lettera 'A' maiuscola, il codice 66 alla 'B' maiuscola, e così via.

Il set di caratteri

I due codici che ho scritto sopra sono effettivamente in uso e rappresentano il celebre codice ASCII (American Standard Code for Information Interchange= Codice Standard Americano per lo Scambio delle Informazioni) ed è uno dei primi codici usati nei computer anche se, per noi europei, questo codice viene spesso chiamato US-ASCII per fare espressamente riferimento al codice usato negli Stati Uniti.
La codifica US-ASCII è composta da 128 codici, da ZERO a 127, e non è in grado di rappresentare gli alfabeti europei come quello latino, che usa le lettere accentate, oppure quello greco od il cirillico.

Per questo motivo sono nati altri standards di codifica basati su US-ASCII che estendono il character set usando i codici da 128 a 255 per codificare le lettere degli alfabeti europei. Questi codici sono stati standardizzati da un organismo internazionale, (ISO = International Standards Organization), e, benchè noi europei ci riferiamo a questo codice come al codice ASCII, in realtà, quando lo facciamo, ci riferiamo al set di caratteri specifico del nostro alfabeto.
Per essere precisi, il codice ASCII usato dagli alfabeti europei, essendo diverso tra le nazioni, dovrebbe essere chiamato con il nome stabilito dall'ISO come, per esempio:

  • ISO-8859-1 per l'alfabeto latino (quello usato anche in Italia)
  • ISO-8859-5 per l'alfabeto cirillico
  • ISO-8859-7 per l'alfabeto greco

Nei moderni linguaggi come Java, la distinzione tra i vari set di caratteri non è più un problema: verso la fine degli anni '90 il consorzio Unicode ed ISO si unirono per stabilire uno standard di caratteri a cui viene data la codifica ISO 10646 e che usa 31 bits per la codifica dei caratteri degli alfabeti; con i suoi oltre 2 miliardi di codici, lo standard Unicode è in grado di rappresentare tutti gli alfabeti del mondo, compresi quelli arcaici.

Il font

Comunque, anche associando un codice numerico alle lettere dell'alfabeto non si spiega come mai leggendo un qualsiasi testo in alfabeto latino è possibile leggere le lettere che compongono l'alfabeto e non il codice numerico.
Questo è permesso grazie al cosidetto font che stabilisce una relazione tra il codice numerico, per esempio il 84 che corrisponde alla 'T' maiuscola, e la sua immagine sullo schermo. Ancora una volta la immagine di una lettera, per esempio la 'T' maiuscola, è una interpretazione numerica di un simbolo umano. In pratica, il simbolo viene suddiviso in piccoli quadrati detti pixel ognuno dei quali può valere ZERO (bianco) o 1 (nero) e quello che noi vediamo altro non è che una serie di ZERO e di UNO come possiamo vedere nella figura sottostante, che è un ingrandimento di una porzione di schermo:

Le immagini

Anche le immagini a colori, come le fotografie, sono rappresentate dai computer come quantità numeriche. Ogni fotografia è composta da piccolissimi "puntini" detti pixel (contrazione di picture element = elemento dell'immagine) nei quali il colore del singolo puntino è determinato dalla combinazione dei tre colori fondamentali: il rosso, il verde ed il blu, da cui ha origine la codifica RGB (Red, Green, Blu). Ogni singolo pixel viene rappresentato con una quantità numerica da ZERO a 255 di questi tre colori: così, per esempio, per avere un pixel rosso intenso la tripletta di colori sarà: red=255, green=0, blu=0.

Se il pixel deve essere viola scuro allora la tripletta prevederà una componente del rosso ed una del blu, ma la quantità di colore sarà più bassa come per esempio: 128,0,128.
Anche i browser sono in grado di rappresentare i colori a seconda delle componenti RGB. Per esempio, lo sfondo sottostante è stato ottenuto con le quantità indicate più sopra per il colore viola (red=128, green=0, blu=128):

 

Bit e byte

Una ulteriore complicazione nel gestire i dati ed i programmi per computer è data dal fatto che, oltre a comprendere solo quantità numeriche, il computer non è in grado di ragionare secondo il nostro sistema di numerazione: quello decimale. Poichè il sistema decimale si basa su dieci simboli numerici - da ZERO a NOVE - ma il computer non è in grado di distinguere se non due soli stati - ON/OFF, acceso/spento, UNO/ZERO - esso usa un sistema di numerazione diverso dal nostro, basato su due sole cifre (appunto lo ZERO e l'UNO) detto sistema binario.
Ogni singolo interruttore elettronico presente nel computer può assumere il valore ZERO (spento) oppure UNO (acceso) ed è chiamato bit che è la contrazione di binary digit, ossia, cifra binaria.

L'insieme di 8 bit formano un byte che, per la maggior parte dei computer moderni, è anche detto char (un carattere dell'alfabeto). Non a caso, infatti, il codice ASCII (ad eccezione del US-ASCII, che usa 7 bit) utilizza 8 bit, ossia 1 byte, per codificare gli alfabeti umani. Questo non si applica a Java poichè, come scritto in precedenza, esso usa la codifica Unicode per rappresentare i caratteri degli alfabeti: Maggiori info su questo argomento in I tipi di dato in Java.

Tutto ciò che gira su un computer e, in generale, su qualsiasi apparecchio elettronico, è in formato numerico e non solo, un formato che usa il sistema di numerazione binario. Tutto ciò che non è numerico deve essere convertito in un numero (o una serie di numeri) per poter essere elaborato da un computer: gli alfabeti, le immagini, i filmati, i suoni. Questo processo si chiama digitalizzazione ed è un procedimento eseguito da particolari dispositivi detti DSP (Digital Signal Processor = Processore di Segnali Digitali).

I linguaggi

In questa sezione il lettore avrà una paronamica sui vari linguaggi di programmazione per computer, i loro vantaggi e svantaggi, lo scopo per cui sono stati sviluppati e, perchè no, il lettore potrebbe decidere di imparare un linguaggio diverso da Java perchè più rispondente alle proprie necessità.
Per prima cosa risponderemo subito ad alcune domanda che vi assillano fin da quando avete iniziato a leggere questa guida:

Domanda: Qual'è il migliore linguaggio di programmazione?
Risposta: Non esiste il migliore in assoluto!

Domanda: Perchè così tanti linguaggi di programmazione?
Risposta: Per soddisfare esigenze diverse

Domanda: Qual'è il linguaggio più veloce in assoluto?
Risposta: Il codice macchina

Domanda: Qual'è il linguaggio più facile da imparare?
Risposta: Dipende. Alcuni linguaggi sono nati proprio per i principianti e si presume siano i più adatti a questo scopo.

Linguaggi orientati alla macchina

Come abbiamo avuto modo di comprendere nei capitoli precedenti, il computer esegue le istruzioni che vengono impartite dal programmatore sui dati da egli stesso forniti e ottiene dei risultati. Sia le istruzioni, che i dati, che i risultati vengono gestiti dal computer esclusivamente in formato numerico e, di più, questi numeri sono espressi nell'unico sistema che il computer è in grado di capire: il sistema binario.
Per complicare ancora di più la situazione, le istruzioni che il computer è in grado di capire sono molto elementari: nel vocabolario del computer non esistono parole come visualizza, stampa oppure ruota una immagine.

Qui si seguito, potete vedere uno spezzone di codice cosidetto disassemblato di un programma:

004016CC push %esi
004016CD push %ebx
004016CE sub $0x10c,%esp
004016D4 and $0xfffffff0,%esp
004016D7 mov $0x0,%eax
004016DC add $0xf,%eax
004016DF add $0xf,%eax

Evidenziamo il fatto che il codice disassemblato non è il codice binario puro ma una traduzione in un linguaggio umano delle istruzioni che la CPU ha nel suo vocabolario e che comprende direttamente. Questo linguaggio - che per inciso non ha niente di umano - si chiama assembly da cui deriva il nome disassemblato.
Per pura informazione, ogni riga del codice assembly vista più sopra è formata da tre colonne:

  • la prima colonna è l'indirizzo di memoria RAM dove è posta l'istruzione; questo numero è espresso in esadecimale
  • la seconda colonna contiene l'azione da compiere; essa viene chiamata opcode (OPeration CODE = codice dell'operazione)
  • la terza colonna rappresenta il dato su cui compiere l'azione ed è chiamata operando

Il fatto che la CPU possa eseguire solo istruzioni così semplici non deve far pensare che essa sia limitata: è vero che le istruzioni sono elementari ma tante istruzioni elementari messe insieme e ripetute per molte volte possono compiere azioni decisamente complesse. Se a questo aggiungiamo che una CPU è in grado di eseguire centinaia di milioni di queste istruzioni in un secondo allora possiamo comprendere perchè un computer è in grado di eseguire calcoli che per un cervello umano sarebbero assolutamente fuori portata.

Linguaggi orientati al problema

Per nostra fortuna, l'uomo si è ben presto reso conto che programmare un computer in assembly è un compito alquanto arduo. Sono nati perciò i linguaggi cosidetti problem oriented (=orientati al problema da risolvere) in contrapposizione all'assembly cosidetto machine oriented (= orientato alla macchina).

Ogni linguaggio, per così dire umano, (che d'ora in avanti chiameremo col nome di linguaggio simbolico) è stato sviluppato per facilitare il compito di programmare il computer ma, tuttavia, ognuno di essi si è specializzato in una particolare materia. Troviamo perciò linguaggi nati per risolvere formule matematiche come il FORTRAN (contrazione di FORmula TRANslator) oppure il COBOL (COmmon Business Oriented Language = Linguaggio Comune Orientato agli Affari), oggi caduto in disuso.

Per i principianti potrebbe essere conveniente imparare ad usare il linguaggio nato proprio per questo scopo: il BASIC (Beginner's All-purpose Simbolic Instruction Code = Codice simbolico adatto a tutti gli scopi per principianti).

L'elenco è lungo, e non mi soffermerò oltre: in Internet troverete sicuramente altro materiale per approfondire i vantaggi e gli svantaggi di questo o quell'altro linguaggio di programmazione. Voglio tuttavia attirare l'attenzione del lettore sul fatto che i diversi linguaggi di programmazione simbolici sono tutti comprensibili all'uomo ma, di contro, assolutamente incomprensibili alla macchina. Come può il computer, allora, eseguire i nostri programmi se sono scritti in un linguaggio a lui incomprensibile?
La risposta è semplice: il programma scritto in linguaggio simbolico dovrà essere tradotto in linguaggio macchina. Avremo perciò due versioni dello stesso programma:

  • la prima versione che chiameremo sorgente sarà scritta in un linguaggio simbolico, comprensibile all'uomo.
  • la seconda versione che chiameremo oggetto sarà la traduzione in linguaggio macchina (detto anche linguaggio nativo) del programma sorgente

Dobbiamo distinguere tre modalità di traduzione:

  • nella prima modalità abbiamo la compilazione: il programma sorgente viene elaborato da un apposito programma detto compilatore che traduce l'intero programma sorgente in codice oggetto: nella RAM del computer viene caricato solo il codice oggetto per l'esecuzione in quanto il programma è stato tradotto una volta per tutte; il vantaggio di questo approccio è la velocità di esecuzione.
  • una seconda modalità di traduzione è la interpretazione: il programma sorgente viene caricato in RAM per l'esecuzione ed elaborato da un programma specifico detto interprete che traduce il sorgente una riga alla volta: per ogni riga viene prodotto il codice oggetto che viene poi eseguito dalla CPU e successivamente l'interprete passa alla riga successiva; ovviamente questo processo è di gran lunga più lento della compilazione pura: l'interprete perde una certa quantità di tempo per tradurre il codice sorgente inoltre la quantità di RAM occupata è maggiore perchè, oltre al sorgente, è necessario caricare in memoria anche l'interprete.
  • nel terzo caso, al giorno d'oggi sempre più diffuso, non si parla di compilazione vera e propria ma nemmeno di interpretazione bensì di semi-compilazione: il codice sorgente viene tradotto in un pseudo-codice detto bytecode che viene interpretato da una virtual machine (VM); poichè il bytecode non è direttamente comprensibile al processore, la VM deve essere caricata in memoria per l'esecuzione del programma tuttavia l'operazione di traduzione è di gran lunga più efficiente che non l'interpretazione del codice sorgente.

Appartengono al primo tipo, quello dei linguaggi compilati, il C/C++, il FORTRAN, il Pascal.
Al tipo interpretato appartiene invece il BASIC. Sempre a questo tipo appartengono anche i linguaggi di scripting come il Perl, il PHP, il Python.
Nei linguaggi semi-compilati troviamo invece il Java ed il C#.

Uno dei vantaggi dei linguaggi semi-compilati come il Java è che uniscono una decente velocità di esecuzione al fatto di essere multipiattaforma: lo stesso bytecode può funzionare benissimo su diverse piattaforme e/o sistemi operativi senza dover essere ricompilato purchè esista una virtual machine per quella piattaforma. Torneremo sulla velocità di esecuzione di Java in uno dei prossimi capitoli: Java è un linguaggio lento?

Di contro, i programmi in codice nativo, benchè più veloci in fase di esecuzione, devono comunque essere ricompilati per la specifica piattaforma hardware/software sulla quale devono girare ma i problemi non sono finiti qui: considerate le profonde differenze tra una piattaforma e l'altra ben difficilmente un programma scritto per un dato ambiente hardware e software riesce ad essere compilato su una altra piattaforma se non con sostanziali modifiche al codice sorgente.

NOTA: benchè è prassi comune indicare come compilazione la traduzione in linguaggio macchina di un sorgente scritto in un linguaggio "compilato" come il C, il termine compilazione non è appropriato. Nella sezione Il build di un eseguibile verrà introdotto il concetto di come si ottiene un programma eseguibile da un linguaggio compilato.

Argomento precedente - Argomento successivo - Indice Generale