Guida Linux_ Il Linguaggio C

12
Il linguaggio C 21.1 Linux il linguaggio C e la programmazione Nel sistema operativo Linux sono presenti vari linguaggi di programmazione, come il C, il Perl, il PHP etc, ma qui ci occuperemo del linguaggio C, in quanto Linux e' stato scritto usando il linguaggio C. Una differenza peculiare tra i sistemi Linux e Windows e' la disponibilita' dei sorgenti dei vari progammi del sistema operativo stesso. Un sorgente e' la traduzione del linguaggio comprensibile dalla macchina in linguaggio comprensibile dagli umani. Per capire cosa e' un sorgente, occorre partire dall'inizio, cioe' dal microprocessore. Ogni PC contiene al suo interno una CPU (Central Processing Unit) o microprocessore. Ogni processore puo' eseguire un numero di istruzioni prestabilito. Si dice cioe' che ogni microprocessore possiede un determinato set di istruzioni. Ad esempio, per spostare un valore da una parte all'altra della memoria RAM, esiste una istruzione MOV (da MOVe, cioe' sposta), per saltare da una istruzione all'altra viene usata una istruzione JMP (da JuMP, cioe' salto) e via dicendo. Il microprocessore Intel Pentium riconosce un insieme di istruzioni che sono diverse da quelle riconosciute da un microprocessore AMD Athlon le quali a loro volta sono diverse da un microprocessore Motorola 68000 e cosi' via. Ogni tipo marca e modello di microprocessore cioe', riconosce esclusivamente le istruzioni per le quali e' stato costruito. Ad ogni modo queste istruzioni alla fine sono sempre codificate in numeri binari. Ad esempio un ipotetica istruzione 'Mov reg2,reg1' potrebbe corrispondere alla serie 0100010001000100. Un programma e' un insieme di istruzioni (o comandi) impartiti al microprocessore e puo' essere considerato in definitiva come un file contenente una lunghissima serie di 0 e di 1. Questa serie interminabile di zeri e di 1 viene letta e capita perfettamente dalla macchina ma non si puo' dire altrettando degli umani... ;o) Il microprocessore legge le istruzioni e le esegue. I primissimi programmi erano di dimensioni modeste, percio' con non poca difficolta' si potevano scrivere direttamente in binario. Successivamente con l'aumentare delle dimensioni dei programmi questo tipo di gestione divenne impraticabile e si passo' dal sistema binario al sistema esadecimale perche' piu' compatto. Ma in fondo anche il sistema esadecimale non e' un linguaggio da 'umani'...leggere una serie di lettere e cifre come AF01D2C43AC012FC43D non e' proprio il massimo. Ecco che allora venne creato un linguaggio mnemonico piu' vicino agli umani: ogni istruzione che originariamente era una riga di 0 e 1 venne tradotta in istruzioni piu' leggibili come 'mov reg2,reg1'. Il rapporto era 1 a 1, cioe' ad ogni istruzione composta da una serie di 0 e 1 corrispondeva ora una istruzione in linguaggio mnemonico. Questo linguaggio mnemonico si chiama assembler. Mediante tale linguaggio la creazione dei programmi divenne un'attivita' alla portata degli umani, ma si trattava pur sempre di una attivita' abbastanza complessa. Le cose rimasero cosi' fino a quando divenne chiaro che alcuni insiemi di istruzioni potevano essere raggruppati per formare delle macroistruzioni. Per eseguire una determinata azione infatti, si scriveva sempre lo stesso insieme di istruzioni. Ad esempio per leggere un carattere da un file, il microprocessore deve eseguire sempre determinate operazioni (scritte in assembler dai programmatori). Ma allora perche' non creare delle macroistruzioni composte da piu' istruzioni elementari assembler? Ad esempio una macroistruzione 'leggi-file', una 'scrivi-file', una 'verifica-condizione' e via dicendo. Vennero cosi' alla luce i linguaggi di programmazione di seconda generazione, cioe' quei linguaggi composti da macroistruzioni decisamente piu' leggibili dagli umani. Ora questi linguaggi evoluti, erano dotati di macroistruzioni come read, write, move, if, tutte istruzioni intelligibili per gli umani. Read significava Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm 1 di 12 03/03/2015 23:23

description

Il Linguaggio C

Transcript of Guida Linux_ Il Linguaggio C

Page 1: Guida Linux_ Il Linguaggio C

Il linguaggio C

21.1 Linux il linguaggio C e la programmazione

Nel sistema operativo Linux sono presenti vari linguaggi di programmazione, come il

C, il Perl, il PHP etc, ma qui ci occuperemo del linguaggio C, in quanto Linux e' stato

scritto usando il linguaggio C. Una differenza peculiare tra i sistemi Linux e Windows

e' la disponibilita' dei sorgenti dei vari progammi del sistema operativo stesso. Unsorgente e' la traduzione del linguaggio comprensibile dalla macchina in linguaggiocomprensibile dagli umani. Per capire cosa e' un sorgente, occorre partire dall'inizio,

cioe' dal microprocessore. Ogni PC contiene al suo interno una CPU (Central

Processing Unit) o microprocessore. Ogni processore puo' eseguire un numero di

istruzioni prestabilito. Si dice cioe' che ogni microprocessore possiede un

determinato set di istruzioni. Ad esempio, per spostare un valore da una parte

all'altra della memoria RAM, esiste una istruzione MOV (da MOVe, cioe' sposta), persaltare da una istruzione all'altra viene usata una istruzione JMP (da JuMP, cioe' salto)

e via dicendo. Il microprocessore Intel Pentium riconosce un insieme di istruzioni che

sono diverse da quelle riconosciute da un microprocessore AMD Athlon le quali a lorovolta sono diverse da un microprocessore Motorola 68000 e cosi' via. Ogni tipomarca e modello di microprocessore cioe', riconosce esclusivamente le istruzioni perle quali e' stato costruito. Ad ogni modo queste istruzioni alla fine sono semprecodificate in numeri binari. Ad esempio un ipotetica istruzione 'Mov reg2,reg1'potrebbe corrispondere alla serie 0100010001000100. Un programma e' un insiemedi istruzioni (o comandi) impartiti al microprocessore e puo' essere considerato indefinitiva come un file contenente una lunghissima serie di 0 e di 1. Questa serieinterminabile di zeri e di 1 viene letta e capita perfettamente dalla macchina ma nonsi puo' dire altrettando degli umani... ;o) Il microprocessore legge le istruzioni e leesegue. I primissimi programmi erano di dimensioni modeste, percio' con non pocadifficolta' si potevano scrivere direttamente in binario. Successivamente conl'aumentare delle dimensioni dei programmi questo tipo di gestione divenneimpraticabile e si passo' dal sistema binario al sistema esadecimale perche' piu'compatto. Ma in fondo anche il sistema esadecimale non e' un linguaggio da

'umani'...leggere una serie di lettere e cifre come AF01D2C43AC012FC43D non e'proprio il massimo. Ecco che allora venne creato un linguaggio mnemonico piu' vicinoagli umani: ogni istruzione che originariamente era una riga di 0 e 1 venne tradotta in

istruzioni piu' leggibili come 'mov reg2,reg1'. Il rapporto era 1 a 1, cioe' ad ogni

istruzione composta da una serie di 0 e 1 corrispondeva ora una istruzione inlinguaggio mnemonico. Questo linguaggio mnemonico si chiama assembler.

Mediante tale linguaggio la creazione dei programmi divenne un'attivita' alla portatadegli umani, ma si trattava pur sempre di una attivita' abbastanza complessa. Le

cose rimasero cosi' fino a quando divenne chiaro che alcuni insiemi di istruzioni

potevano essere raggruppati per formare delle macroistruzioni. Per eseguire unadeterminata azione infatti, si scriveva sempre lo stesso insieme di istruzioni. Ad

esempio per leggere un carattere da un file, il microprocessore deve eseguire sempredeterminate operazioni (scritte in assembler dai programmatori). Ma allora perche'non creare delle macroistruzioni composte da piu' istruzioni elementari assembler?Ad esempio una macroistruzione 'leggi-file', una 'scrivi-file', una 'verifica-condizione'

e via dicendo. Vennero cosi' alla luce i linguaggi di programmazione di seconda

generazione, cioe' quei linguaggi composti da macroistruzioni decisamente piu'leggibili dagli umani. Ora questi linguaggi evoluti, erano dotati di macroistruzioni

come read, write, move, if, tutte istruzioni intelligibili per gli umani. Read significava

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

1 di 12 03/03/2015 23:23

Page 2: Guida Linux_ Il Linguaggio C

leggi un file, write scrivilo etc. In questi tipi di linguaggi il rapporto non e' piu' di 1 a 1

come con l'assembler ma di 1 a N. Cioe', ad ogni macroistruzione corrispondono piu'istruzioni elementari assembler. Tali linguaggi vengono definiti linguaggi di alto livello.

Successivamente vennero creati dei linguaggi di terza generazione: in sostanza piu'

passa il tempo e piu' i nuovi linguaggi si discostano dalla lingua della macchina e si

avvicinano alla lingua degli uomini. Il C e' un linguaggio di alto livello, cioe' ad ogni

istruzione scritta in C corrispondono N istruzioni elementari. Un programma scritto

mediante un linguaggio di programmazione viene definito sorgente. Un sorgentequindi e' un insieme di istruzioni impartite al microprocessore e scritte in un file di

testo. Un file contenente istruzioni in cifre binarie interpretabili dalla macchina vienedetto eseguibile o file binario. Ma come fare per tradurre questo file sorgente

leggibile dagli umani in istruzioni in linguaggio binario? Utilizzando i compilatori. Un

compilatore non e' altro che un programma che legge un sorgente e lo traduce nella

corrispondente serie interminabile di cifre binarie leggibili dal microprocessore equindi eseguibili. In sostanza quindi, un compilatore traduce un file sorgente in un file

eseguibile. Questa affermazione evidenzia lo scopo di un compilatore ma nella realta',

come al solito, le cose sono un po' piu' complesse. Infatti il compilatore non crea un

eseguibile direttamente, ma crea un file oggetto. Il file oggetto e' un file

parzialmente eseguibile. Per poter arrivare all'eseguibile vero e proprio occorre

ancora l'intervento di un altro programma: il linker (o linkage editor). Iprogrammatori nel tempo si resero conto che alcune funzioni generali venivanoripetute spesso all'interno dei loro programmi. Programmi che fanno cose diversecontengono in realta' al loro interno alcune istruzioni uguali. Ad esempio unprogramma che visualizza a video il risultato di una somma di due numeri ed unprogramma che visualizza a video il risultato di una radice quadrata sono dueprogrammi diversi. Producono due risultati diversi, hanno istruzioni diverse al lorointerno ma nonostante cio' hanno un punto in comune: visualizzano a video unrisultato. Perche' scrivere ogni volta le istruzioni necessarie per visualizzare a videoun risultato? Perche' non creare un piccolo programmino generico che effettuaquesto lavoro? Un programma che prende dei dati in input e li visualizza a video. Sipotrebbe creare un programma che legge un carattere da un file, un programma chescrive un carattere in un file e via dicendo. Si potrebbero cioe' creare dei piccoliprogrammini che effettuano una operazione generica ed utilizzarli tutte le volte cheoccorre effettuare quell'operazione all'interno di un qualsiasi programma. Infatti e'stato fatto cosi': sono stati creati parecchi programmini generici utilizzabili da

qualsiasi programmatore. Questi programmini chiamati moduli (o anche membri)

sono stati inseriti all'interno di contenitori chiamati librerie. Sono anche loro deiprogrammini parzialmente eseguibili, cioe' dei file oggetto. I programmi scritti daiprogrammatori hanno bisogno di questi programmini per poter eseguire delle

operazioni generiche (come la stampa a video) e questi programmini a loro volta

hanno bisogno di un programma che gli fornisca dei dati per svolgere il loro lavoro.Come collegare i programmi ai moduli di libreria? Questa operazione di collegamento

(link) viene effettuata dal linker. Il linker unisce, cuce, collega dei moduli oggetto tra

loro. Ogni file oggetto (il programma appena scritto ed i vari moduli di libreria)rappresenta un tassello del puzzle. Il linker unisce i vari tasselli e produce il puzzle,

cioe' il file eseguibile finale. Studiando il linguaggio C si scoprira' che esistono unamoltitudine di programmini gia' scritti e perfettamente funzionanti posti all'interno di

librerie ed utilizzabili da chiunque. Esistono svariate librerie ed ogni libreria si occupa

di un argomento particolare. Esistono librerie generiche, librerie dedicate allestringhe, librerie dedicate alle operazioni matematiche, librerie dedicate all'accesso aifile, librerie dedicate alla grafica e via dicendo. Quando utilizziamo uno di questiprogrammini gia' scritti, diciamo che utilizziamo una funzione di libreria. Ad

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

2 di 12 03/03/2015 23:23

Page 3: Guida Linux_ Il Linguaggio C

esempio la funzione 'printf' e' un programmino che permette di visualizzare a video i

dati che gli forniamo in input. E' una funzione usata all'interno di parecchi programmi.Ma torniamo ai sorgenti: dovrebbe ormai essere chiaro che per ogni file esguibile

deve esistere da qualche parte il relativo file sorgente. Qui entra in ballo Linux. Infatti

Linux insieme ai programmi eseguibili fornisce anche i relativi sorgenti. Qualsiasi

programmatore che conosca il linguaggio C puo' esaminare Linux in profondita' per

capire quali sono le operazioni che eseguono determinati programmi. Ma un

programmatore che conosce il linguaggio C, puo' anche modificare i programmi oaddirittura riscriverli! Certo, occorre essere un programmatore esperto, ma e'

possibile. Di quali strumenti occorre disporre per scrivere un programma? Un editordi testi per scrivere il sorgente nel linguaggio di programmazione che si conosce ed il

relativo compilatore per tradurlo in formato eseguibile. Per ogni linguaggio di

programmazione esiste il relativo compilatore. In ogni distribuzione Linux viene

fornito anche un compilatore per il linguaggio C. Il compilatore C fornito nelle variedistribuzioni Linux e' il compilatore GCC (GNU Compiler Collection). Quando parliamo

genericamente di compilatore normalmente intendiamo sia il compilatore che il linker.

21.2 Un approccio insolito al C

Sin dai tempi della prima versione del libro 'Linguaggio C' di Brian W. Kerninghan eDennis M. Ritchie(creatore del C), la tradizione vuole che per trattare un linguaggio diprogrammazione si debba partire dal famoso programmino Hello World, cioe' il piu'semplice programma che visualizza a video la frase 'Hello World'. Qui pero' vorreitentare un approccio diverso: comincero' ad affrontare i tipi di dati previsti nel Cevidenziando tutti quegli aspetti che spesso sono fonte di confusione se non chiaritiadeguatamente. Il testo guida adottato qui e' il famoso libro: 'linguaggio C' di BrianW. Kerninghan e Dennis M. Ritchie (la cosidetta bibbia del C, versione in linguaoriginale: "The C Programing Language 2nd Edition (ANSI C)", B.W. Kernighan, D.M.Ritchie (1988); Prentice Hall; ISBN 0-13-110362-8. Versione tradotta in italiano:Linguaggio C, Gruppo Editoriale Jackson, ISBN 88-7056-443-6). Cerchero' dirimanere fedele alle raccomandazioni del C ANSI/ISO. ANSI e ISO sono dueorganizzazioni che si occupano di standard, ANSI sta per American National StandardInstitute mentre ISO sta per International Standard Organization. Lo standard ANSIC venne definito nel 1989 dall' American National Standard Institute e vennesuccessivamente adottato dall' International Standard Organization come standardinternazionale. Lo standard ANSI originale e' definito nel documento ANSI

X3.159-1989 (chiamato anche C89). Tale standard venne adottato dall'ISO nel

documento: ISO/IEC 9899:1990 (chiamato anche C90). Per avere maggioriinformazioni sullo stato dello standard si possono consultare i siti www.iso.ch ehttp://std.dkuug.dk/JTC1/SC22/WG14/ oppure cercare su google la stringa 'ISO/IEC

9899: 1999'. Ma perche' e' importante uno standard? Quando sviluppiamo deiprogrammi in C possiamo scrivere del codice:

conforme allo standard1.

con comportamento definito dall'implementazione (implementation defined)2. con comportamento non definito (undefined)3.

Un codice conforme allo standard verra' compilato da qualsiasi compilatore aderenteallo standard ANSI e, cosa piu' importante, sara' portabile, nel senso che, una volta

ricompilato, girera' ('girare' e' un termine gergale che significa 'essere eseguibile'.

Quindi quando si dice che un programma 'gira su Linux' significa che tale programmae' eseguibile sui sistemi Linux) su qualsiasi sistema operativo senza apporre

modifiche alcune o con poche modifiche. Viceversa, un programma non aderente allo

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

3 di 12 03/03/2015 23:23

Page 4: Guida Linux_ Il Linguaggio C

standard ANSI non sara' portabile, nel senso che potra' girare solo sul sistema

operativo per il quale e' stato pensato. In particolare, del codice non aderente allostandard, potra' avere un comportamento definito dall'implementazione oppure

indefinito. Scrivere del codice con comportamento definito dall'implementazione

significa scrivere programmi che possono comportarsi in un modo piuttosto che in un

altro a seconda del compilatore usato. Poiche' i compilatori sono ottimizzati per il

sistema operativo per i quali sono stati scritti, ogni compilatore e' diverso da un altro.

Un compilatore ottimizzato per Linux e' scritto (implementato) in modo da produrrecodice piu' efficente per i sistemi Linux, mentre un compilatore Windows e'

implementato in modo da sfruttare al massimo le caratteristiche del sistemaoperativo Windows. Ne consegue che le stesse istruzioni scritte in linguaggio C

vengono tradotte in modo diverso a seconda del compilatore usato. Se si scrive del

codice che produce determinati risultati con un compilatore Linux e se tale codice

non e' conforme allo standard ma e' implementation defined, i risultati che si avrannoricompilando lo stesso codice con un compilatore Windows potrebbero essere

diversi. Infine, scrivere del codice dal comportamento indefinito, significa scrivere del

codice sintatticamente corretto ma semanticamente errato. Del codice con queste

caratteristiche sara' scritto seguendo le regole della grammatica del linguaggio (ed il

compilatore non produrra' errori) ma conterra' delle istruzioni logicamente insensate.

Di fronte a queste istruzioni il compilatore potra' generare un eseguibile funzionante,un eseguibile che va in crash (si pianta), un eseguibile che spegne il PC e chissa' chealtro! Da tutto questo ne segue che sarebbe meglio scrivere del codice conforme allostandard ANSI. I compilatori aderenti allo standard ANSI sono tenuti a documentarepunto per punto tutte le parti implementation defined del linguaggio. Piu' avantiverrano evidenziate quelle parti di codice che potrebbero avere un comportamentoimplementation defined o undefined (indefinito).

21.3 Variabili e costanti

Un programma e' un file di testo contenente una parte di istruzioni (la parte definitacodice) ed una parte dati. Ad esempio per effettuare una divisione ci occorre unfoglio di carta ed una penna per annotare tutti i passi ed i valori intermedi prima diottenere il risultato finale (oppure usare una calcolatrice! ;o). La stessa cosa e' validaper i programmi. Un programma, eseguendo determinate operazioni, puo' avere lanecessita' di memorizzare dei valori temporanei da qualche parte. Questi valori

vengono memorizzati nella memoria RAM all'interno delle variabili. Una variabile

puo' essere paragonata ad una scatola che puo' contenere dei valori che variano neltempo. Tale scatola puo' contenere delle penne, dei colori, delle puntine da disegno,

dei fiori etc. Quindi una variabile e' una zona della memoria RAM riservata alprogramma che puo' contenere dei valori. Supponendo di voler memorizzare il valore

2 da qualche parte nella memoria, potremmo usare una variabile 'x' ed assegnargli ilvalore 2. A questa variabile potremmo dare un nome a piacere del tipo

'mia-variabile'. Successivamente potremmo assegnare a tale variabile un altro valore

se vogliamo. Ma come viene creata una variabile nella memoria RAM? La RAM e' unamatrice di celle di memoria, dove ogni cella puo' contenere un valore. Puo essereparagonata ad un quaderno a quadretti dove all'interno di ogni quadretto si puo'inserire un valore. Il microprocessore individua ogni cella di memoria (il quadretto) in

base al suo indirizzo all'interno della RAM (il foglio) usando dei sistemi di

indirizzamento particolari. Ad esempio potrebbe individuare un quadretto tramite lecoordinate riga/colonna: la riga 37 e colonna 44 identificano insieme un preciso

quadretto all'interno del foglio. Un qualsiasi sistema di individuazione del quadrettoviene definito indirizzamento. In realta' all'interno della RAM tutto e' molto piu'

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

4 di 12 03/03/2015 23:23

Page 5: Guida Linux_ Il Linguaggio C

complesso ed ogni cella di memoria ha un indirizzo ben preciso. Poiche' gli indirizzi di

memoria sono difficili da ricordare per gli umani, nei linguaggi di programmazionesono state inventate le variabili. Una variabile nasce quando viene eseguito un

programma e muore quando tale programma termina l'esecuzione. Una variabile

allora non e' altro che un nome simbolico al quale e' associato un indirizzo di

memoria ben preciso. Piu' precisamente il nome di una variabile viene detto

identificatore. E' il compilatore che, nella fase di compilazione (cioe quando traduce

un sorgente in file oggetto) associa al nostro nome a piacere un preciso indirizzo dimemoria. Tutte le variabili all'interno del nostro programma avranno cosi' nomi di

fantasia: pippo, pluto, topolino, mia-variabile, variabileX etc, ma ad ogni nome ilcompilatore associera' un indirizzo di memoria ben preciso (il compilatore compila

delle tabelle di simboli appunto, dove ad ogni nome o simbolo viene associato un

indirizzo di una zona di memoria RAM). Tutte le volte che faremo riferimento alla

variabile 'mia-variabile', il compilatore la cerchera' nelle sue tabelle dei simboli dallaquale leggera' l'indirizzo di memoria associato. Come al solito, nella realta' le cose

non sono cosi' semplici, perche' con un nome di variabile possiamo identificare una o

piu' celle in base al tipo di variabile. Il contrario di una variabile e' una costante,

cioe' un valore che non varia nel tempo ma anzi rimane fisso (costante) per tutta la

durata dell'esecuzione del programma. Percio' una variabile puo' variare mentre una

costante non puo' variare. Occorre aver ben chiaro questo concetto in modo daevitare confusioni quando parleremo dei puntatori e delle loro relazioni con gli array.

21.4 I tipi di dati

Abbiamo detto che un valore o meglio un oggetto occupa una o piu' celle di memoriaa seconda del tipo di oggetto. Una variabile puo' contenere solo oggetti di undeterminato tipo. E' un po' come dire che la scatola delle scarpe puo' contenere soloscarpe, la scatola dei fiammiferi puo' contenere solo fiammiferi, la scatola dellecaramelle solo caramelle e cosi' via. Insomma, ogni scatola puo' contenere solooggetti di un determinato tipo. Non esistono quindi delle variabili generiche chepossono contenere qualsiasi tipo di oggetto (in realta' nel C e' stato introdotto il tipovoid che fa riferimento ad un oggetto inesistente e quindi non associato ad alcun tipoin particolare). Ma vediamo quali erano i tipi di dati ammessi in C inizialmente:

tipi fondamentali:

unsigned char

signed char

unsigned short int

signed short int

unsigned int

signed int

unsigned long int

signed long int

float

doublelong double

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

5 di 12 03/03/2015 23:23

Page 6: Guida Linux_ Il Linguaggio C

void

enum

tipi derivati:

array

puntatori

struttureunion

Successivamente a seguito di varie revisioni degli standard, vennero aggiunti nuovi

tipi. Consultando lo standard ISO/IEC 9899/1999 si rilevano infatti questi nuovi tipi:

bool

long long int (intero a 64 bit)

float _Complex

double _Complex

long double _Complex

Le dimensioni dei tipi fissate dallo standard ANSI non sono rigide in quanto possonovariare a seconda dell'implementazione, cioe' ogni implementazione deve attenersi ailimiti minimi ma puo' aumentarli se lo ritiene necessario. Ad esempio il tipo int puo'avere una dimensione che varia da 2 byte (16 bit) a 4 byte (32 bit) in base allamacchina. Per conoscere la dimensione di un tipo nella macchina che si sta usando,e' possibile usare l'operatore sizeof messo a disposizione dal C. In seguito vedremol'uso di tale operatore. Ad ogni modo esistono alcune regole fissate dallo standardANSI che devono essere rispettate da tutti i compilatori conformi a tale standard. Inparticolare gli short e gli int devono essere di almeno 2 byte ed i long di 4. Inoltreuno short non puo' essere piu' grande di un int ed un int non puo' essere piu' grandedi un long. I valori minimi e massimi consentiti e legati al compilatore sono definitiall'interno di 2 file: limits.h e float.h. In generale si ha:

char ≤ short ≤ int ≤ long

In ogni caso i tipi numerici char, short, int, long possono essere signed o unsigned,cioe' con o senza segno. I tipi senza segno, in base alla loro ampiezza possono

assumere i seguenti valori:

1 byte (es. un char) = da 0 a 255

2 byte (es. uno short) = da 0 a 32.7674 byte (es. un long) = da 0 a 4.294.967.296

Se tali tipi fossero al contrario segnati si avrebbe:

1 byte (es. un char) = da -128 a 127

2 byte (es. uno short) = da -32768 a 32.7674 byte (es. un long) = da -2.147.483.648 a 2.147.483.647

In generale l'intervallo di valori esprimibile da un tipo dipende dalla sua lunghezza e,limitatamente alla rappresentazione dei dati in complemento a due, e' dato da:

-2n-1...2n-1-1

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

6 di 12 03/03/2015 23:23

Page 7: Guida Linux_ Il Linguaggio C

Dove n rappresenta il numero di bit utilizzati. Ad esempio un tipo di dato con

ampiezza di 2 byte utilizzera' 16 bit (2*8) pertanto il valore di n sara' 16. In realta' lostandard ISO/IEC 9899: 1999 pone dei limiti inferiori, infatti un char ad esempio

varia da -127 a 127 ed uno short da -32767 a 32767. E' bene far riferimento allo

standard se l'obiettivo e' la portabilita' ed ai file limits.h e float.h per conoscere il

range di valori esatto consentito dal compilatore se non si e' interessati alla

portabilita'. I tipi char, a dispetto del nome sono in realta' dei piccoli interi e possono

contenere un intervallo di numeri da -128 a 127 se segnati o da 0 a 255 se senzasegno. Cio' puo' essere dimostrato dal codice seguente perfettamente legale:

char c;

c = 'A';

c = c * 3;

Inizializzare una variabile di tipo char con la costante carattere 'A' infatti, equivale

inizializzarla con il numero 65 se stiamo lavorando su un sistema che utilizza lacodifica ASCII (ma puo' equivalere ad un altro numero, come ad esempio 193 se

stiamo lavorando su un sistema che utilizza la codifica EBCDIC). Occorre far

attenzione agli apici: una costante carattere e' delimitata dagli apici singoli mentre gli

apici doppi delimitano una stringa costante. Una curiosita': se stiamo lavorando su

un sistema Unix/Linux abbiamo 3 tipi di apici a disposizione: gli apici singoli, gli apicidoppi e gli apici inversi. Ognuno ha un significato specifico. I tipi float double e longdouble, sono tipi di dato in virgola mobile (floating point, cioe' punto fluttuante, datala notazione inglese inversa alla nostra secondo la quale la virgola decimale e'rappresentata dal punto, mentre per rappresentare le decine, le centinaia, le migliaiaetc si usa la virgola) sono un po' diversi e meritano una trattazione separata. Quandotrattiamo numeri con decimali (cioe' non interi), numeri molto grandi o numeri moltopiccoli, possiamo utilizzare la rappresentazione in virgola mobile. Ad esempio setrattiamo dati come i dati finanziari di una azienda, la distanza tra la luna e la terra, lamassa di un elettrone, gli abitanti di una nazione e cosi' via. I calcoli in virgola mobilevengono eseguiti dalla FPU (Floating Point Unit) una sorta di calcolatore specializzatoper questi calcoli ed integrato nella CPU. Inizialmente i calcolatori possedevano laCPU ed un coprocessore matematico utilizzato per questo genere di calcoli.Attualmente il coprocessore e' integrato nella CPU (FPU) (le applicazioni 3D sono unesempio di applicazioni che fanno intenso uso di calcoli in virgola mobile). Secondotale rappresentazione un numero puo' essere rappresentato in questi modi:

0,001 oppure 1*10-3 oppure 1E-3

Queste 3 notazioni si equivalgono. Percio' 1E-38 equivale a 1*10-38 (notazione

scientifica), cioe': 0,00000000000000000000000000000000000001 (spero di avercontato correttamente 38 zeri ;o). I numeri in virgola mobile possono essere aprecisione singola oppure a precisione doppia. I float sono numeri in virgola

mobile a singola precisione, mentre i double sono a doppia precisione. Nellaprecisione singola vengono usati 32 bit (4 byte) mentre nella precisione doppia 64 (8

byte). Esiste poi la precisione doppia estesa che usa 80 bit (10 byte) e laprecisione quadrupla che usa 128 bit. Nella notazione a virgola mobile il numeroviene scomposto in 2 parti: la mantissa (o significante) e l'esponente. La mantissa

contiene le cifre piu' significative del numero, mentre l'esponente indica la posizionedella virgola all'interno del numero stesso (da qui la denominazione di virgola

'mobile'). Nella precisione singola si hanno a disposizione 32 bit di cui 23 dedicati allamantissa 1 al segno ed i rimanenti 8 bit sono dedicati all'esponente. Nella precisione

doppia si hanno a disposizione 64 bit di cui 52 per la mantissa, 1 per il segno ed i

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

7 di 12 03/03/2015 23:23

Page 8: Guida Linux_ Il Linguaggio C

rimanenti 11 per l'esponente. Questo come stabilito dallo standard IEEE 754

(Institute of Electrical and Electronics Engineers, una associazione senza scopo dilucro formata da piu' di 380.000 membri appartenenti a 150 paesi che si occupano di

elettronica, telecomunicazioni, computer etc). E' importante saper gestire i tipi di

dato a virgola mobile per evitare di commettere errori. Nel passato alcuni errori sui

tipi a virgola mobile sono stati fatali. 4 giugno 1996: viene lanciato Arianne 5 a

Kourou. Dopo 36 secondi il razzo cambio' rotta e si autodistrusse. Cosa era

accaduto? Un sistema di riferimento inerziale cerco' di convertire un numero a 64 bitin virgola mobile in un numero a 16 bit in virgola fissa. Poiche' il numero di partenza

era troppo grande per essere contenuto nel numero di arrivo, venne generato unerrore di overflow che provoco' l'invio di un messaggio diagnostico al computer di

bordo. Questo messaggio di errore venne interpretato erroneamente come un dato di

volo corretto. Tornando alla notazione scientifica, abbiamo visto che un numero puo'

essere espresso nella forma: 1*10n. Se cio' e' vero allora le forme:

1260

1260*100

126,0*101

12,60*102

1,260*103

0,126*104

12600*10-1

126000*10-2

1260000*10-3

...

...

sono tutte equivalenti (ricordiamo che N*B-E equivale a N/B E, ad esempio: 2*10-2

equivale a 2/102, cioe' 2 diviso 100, il quale da' come risultato finale 0,02). Questopuo' creare dei problemi in quanto non esiste un modo unico che identifica unostesso numero. Occorre percio' definire uno standard che non crei dubbi sulla formautilizzata per rappresentare un numero. A tale scopo viene utilizzata la notazionescientifica normalizzata adottata dallo standard IEEE 754. Secondo tale standard

per convenzione la prima cifra intera prima della virgola e' diversa da zero pertantose ragioniamo in base 2 (i calcolatori lo fanno) la prima cifra intera prima della virgolanon puo' che essere 1 (nello standard IEEE 754 la cifra 1 viene omessa in quanto

sottintesa). Sempre secondo tale standard l'esponente e' polarizzato, cioe' se si

hanno 8 bit a disposizione per l'esponente, un bit e' riservato al segno e quindi i

valori possibili con un esponente di 8 bit saranno compresi tra 2-127 e 2127 (poiche'un bit e' riservato al segno si ha 8-1=7, quindi con 7 bit a disposizione si possono

avere 27 combinazioni diverse, cioe' 127). Data una mantissa 'm' ed un esponente 'e'si potranno rappresentare i seguenti intervalli di valori:

-∞oooo-m eoooooooooooooooooooo-m-eoooooZEROooooo

m-eoooooooooooooooooooo m eoooo ∞

L' intervallo dei valori rappresentabili nella modalita' in virgola mobile e' evidenziato in

rosso i rimanenti intervalli non sono rappresentabili. In particolare gli intervallievidenziati in giallo rappresentano l'overflow, mentre gli intervalli evidenziati in

verde rappresentano l'underflow. La differenza tra underflow ed overflow e'

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

8 di 12 03/03/2015 23:23

Page 9: Guida Linux_ Il Linguaggio C

importante in quanto in caso di errori di underflow il calcolatore puo' porre rimedio

esprimendo con lo zero il valore infinitesimale che e' uscito dal range disponibile (e siottiene un errore di approssimazione) mentre in caso di errori di overflow il

calcolatore non e' in grado di porre rimedio (non e' possibile approssimare

introducendo un valore infinito) percio' si blocca il calcolo ed il programma termina in

errore. E' da notare che la distanza tra due numeri in virgola mobile vicini tra loro

tende ad aumentare con l'aumentare del valore del numero e tende a diminuire verso

lo zero. Cioe' la distribuzione dei numeri non e' costante. In altre parole si ha unararefazione all'aumentare del valore del numero e viceversa un addensamento con il

diminuire del valore. Diminuendo il valore la distanze si accorciano e i numeri siavvicinano tra loro mentre aumentando il valore le distanze si allungano ed i numeri

si allontanano tra loro:

-∞---|-|-|-|-|-

ZERO-|-|-|-|--|---|-----|------|---------|----------|---------------|-------------------->∞

Ad ogni modo la percentuale di errore e' costante nei vari intervalli. La mantissa

rappresenta la precisione del valore rappresentato, cioe' il numero di cifre

significative del valore rappresentato: maggiore e' il numero delle cifre dellamantissa maggiore sara' la precisione ottenuta. Poiche' un numero binario equivale in

decimale al numero moltiplicato per 0,301, avremo che 2127 equivale circa a 1038 inquanto 127 * 0,301 = 38,227. Leggendo i valori contenuti nel file float.h l'intervallo divalori ammissibile dai tipi in virgola mobile della mia implementazione e':

float = da -2 128 a -2-125 e da 2-125 a 2 128

double = da -2 1024 a -2-1021 e da da 2-1021 a 2 1024

long double = da -2 16384 a -2-16381 e da 2-16381 a 2 16384

Oppure in decimale:

float = da -10 38 a -10-37 e da 10-37 a 10 38

double = da -10 308 a -10-307 e da 10-307 a 10 308

long double = da -10 4932 a -10-4931 e da 10-4931 a 10 4932

Il massimo valore della parte intera esprimibile, e' in realta la precisione massimaottenibile, mentre il numero massimo della parte frazionaria (esponente) indica

l'intervallo massimo di numeri rappresentabili. Togliendo un bit alla mantissa lo si

puo' aggiungere all'esponente e viceversa. Per conoscere l'intervallo di valori interiammissibili o, piu' esattamente il numero di cifre della parte intera esprimibile,

occorre far riferimento ancora una volta allo standard IEEE 754. Secondo talestandard i tipi float (precisione singola) dispongono di 32 bit di cui 24 sono dedicati

alla parte intera (in realta' 23 perche' uno e' dedicato al segno). Cio' significa che il

valore massimo esprimibile (limitatamente alla parte intera) e' di circa 223, ossia

8388608, quindi 6 cifre 'piene' e circa 7 cifre 'quasi' piene (esattamente fino a8388608) a disposizione. Cio' significa che per elaborare un numero di 8 cifre una

variabile di tipo float non e' sufficiente. Lo stesso ragionamento e' applicabile ai

double (precisione doppia) ed ai long double (precisione estesa). Per i double,sempre secondo lo standard IEEE 754 i bit disponibili per la parte intera sono 52 (53

- 1 bit dedicato al segno) mentre per i long double sono 63 (64 - 1). Eseguendo lostesso calcolo visto per i float sara' evidente che con i double saranno disponibili 15

cifre per la parte intera (fino a 252 = 4503599627370496) e con i long double 18

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

9 di 12 03/03/2015 23:23

Page 10: Guida Linux_ Il Linguaggio C

cifre (fino a 252 = 9223372036854775808). Comunque questi valori sono indicativi,

in quanto l'esatto intervallo e la precisione dipende dall'implementazione ed e'

ricavabile esclusivamente dai file limits.h, float.h e values.h. Un compilatore diverso

su una macchina diversa, potrebbe fornire valori diversi. Ecco la tabella dei tipi a

virgola mobile secondo lo standard IEEE 754:

formato bit usati bit della mantissa bit dell'esponente bit del segno

float 32 23 8 1

double 64 52 11 1

long double 80 64 15 1

Per verificare i limiti delle variabili in virgola mobile supportate dal compilatore e'

possibile utilizzare il seguente programma:

#include<float.h>

#include<stdio.h>

int main(void)

{

float f;

double x;

long double ld;

printf("\nValori minimo e massimo rappresentabili: \n");

printf(" float minimo : %e\n", FLT_MIN);

printf(" double minimo : %.15le\n", DBL_MIN);

printf(" long double minimo : %.30Le\n", LDBL_MIN);

printf(" float massimo : %e\n", FLT_MAX);

printf(" double massimo : %.15le\n", DBL_MAX);

printf(" long double massimo: %.30Le\n", LDBL_MAX);

printf("\n");

return 0;

}

Lo standard ISO/IEC 9899:1990 in realta' pone dei limiti minimi (in valore assoluto)che ogni implementazione deve rispettare, ma ogni implementazione e' libera di

aumentare tali valori (sempre in valore assoluto):

float = da 1E-37 a 1E 37double = da 1E-37 a 1E 37

long double = da 1E-37 a 1E 37

Attenzione a non confondere il long double con il nuovo long long. Il long double e'un tipo a virgola mobile a doppia precisione estesa di 80 bit (10 byte di cui 64 per lamantissa, 15 per l'esponente ed 1 per il segno) mentre il long long e' un intero a 64

bit (8 byte). Il long long ammette un intervallo di valori da -263 a 263-1. Anche per

questi tipi vale il discorso dell'implementazione: lo standard definisce i limiti minimi

che devono essere rispettati, ma ogni implementazione e' libera di aumentarli. Infatti

su alcuni sistemi esistono dei char da 64 bit (cioe' lunghi 8 byte) oppure dei longdouble lunghi 128 bit. La verita' in un ipotetico sistema X, con un sistema operativo Y

ed un compilatore Z e' data solo dai file limits.h, float.h e values.h. Lo standard adogni modo definisce i limiti minimi ai quali qualsiasi compilatore deve attenersi:

occorre seguire le direttive dello standard se l'obiettivo e' la portabilita'. Secondo lo

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

10 di 12 03/03/2015 23:23

Page 11: Guida Linux_ Il Linguaggio C

standard IEEE 754 esistono 4 tipi di dato in virgola mobile: a precisione singola (4

byte), a doppia precisione (8 byte), a doppia precisione estesa (10 byte) e aquadrupla precisione (16 byte). Lo standard definisce la precisione quadrupla un tipo

non specificato dallo IEEE 754 ma lo riconosce come uno standard de facto. Gli altri

due tipi di oggetti fondamentali sono il tipo void ed il tipo enum. Il tipo void (che

significa vuoto) significa nessun tipo. Attenzione a non confondere NULL con il tipo

void: il primo e' un valore nullo il secondo e' un tipo di oggetto. Il tipo void e' utile

per vari scopi che vedremo in seguito. Il tipo enum e' un tipo di costanteenumerativa. Un'enumerazione e' un elenco di costanti intere come ad esempio:

enum giorni { LUN = 1, MAR = 2, MER = 3, GIO = 4, VEN = 5, SAB = 6, DOM = 7 )

oppure:

enum boolean { TRUE, FALSE )

oppure:

enum giorni { LUN = 1, MAR, MER, GIO, VEN, SAB, DOM )

Nel primo caso ogni costante ha un valore specificato esplicitamente, nel secondocaso implicitamente la prima costanta ha valore 0 e la seconda 1 (una terza costanteavrebbe valore 2, una quarta 3, una quinta 4 e cosi' via) e nell'ultimo caso i valorinon specificati continuano la progressione a partire dall'ultimo specificato(nell'esempio sopra MAR vale 2, MER vale 3 e cosi' via). Una alternativa al tipo enume la direttiva #define che vedremo in seguito. I tipi derivati (vettori, strutture,puntatori e union) verranno illustrati in seguito. Alcuni testi annoverano tra i tipianche le funzioni in quanto sono pur sempre oggetti in memoria e possono ancheessere indirizzati attraverso i puntatori ma personalmente preferisco considerarle unostrumento per scrivere dei piccoli blocchi di istruzioni richiamabili in vari punti delprogramma e che possono ritornare dei valori; un po' come le procedure del Pascal,le perform del Cobol o le subroutines del Fortran o del Basic (le gosub).

21.5 Ancora sulle costanti

Abbiamo visto che il contrario di una variabile e' una costante, cioe' un valore che

non varia nel tempo ma anzi rimane fisso (costante) per tutta la durata

dell'esecuzione del programma. Le costanti possono essere intere, carattere ostringa. Una costante intera come 123 e' una costante di tipo int. Una costante interadi tipo long e' seguita dal carattere 'l' o 'L' come ad esempio 123456789L. Le costanti

senza segno sono seguita da una 'u' o 'U' come ad esempio 123456789UL (che

corrisonde ad un tipo unsigned long). Una costante a virgola mobile termina con ilcarattere 'f' o 'F' come ad esempio 123.4 (ricordiamo che il punto corrisponde alla

nostra virgola decimale) e corrisponde al tipo float. Una costante a virgola mobile di

tipo long double termina con una 'l' o 'L'. Una costante intera puo essere espressa indecimale, in ottale oppure in esadecimale. Un prefisso '0x' o '0X' indica la notazione

esadecimale come ad esempio 0X1F (che corrisponde al numero decimale 31). Unozero prefisso ad un intero indica la notazione ottale, ad esempio il numero di prima

puo' essere scritto come 037. Percio' 0X1F, 037 e 31 si equivalgono e corrispondono

al numero decimale 31. Una costante carattere puo' rappresentare dei caratteri dicontrollo particolari come carattere backspace, il carattere di tabulazione od ilcarattere new line attraverso la cosidetta sequenza di escape. Una sequenza diescape e' un sistema per indicare dei caratteri speciali utilizzando come prefisso il

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

11 di 12 03/03/2015 23:23

Page 12: Guida Linux_ Il Linguaggio C

carattere '\' (backslash o barra inversa). Ecco l'elenco delle sequenze di escape:

\a (campanello)\b (backspace)

\f (form feed o salto pagina)

\n (new line o nuova riga)

\r (return o ritorno carrello)

\t (tab o carattere di tabulazione)

\v (tab verticale)\\ (backslash o barra inversa)\? (punto interrogativo)

\' (apice singolo)

\" (apice doppio)

\ooo (numero ottale)

\xhh (numero esadecimale)

La costante carattere '\0' rappresenta il carattere nullo cioe' con valore zero, da non

confondere con NULL. La stringa costante e' una serie do zero o piu' caratteri

racchiusi tra doppi apici. Internamente ogni stringa e' terminata dal carattere nullo

'\0'. Attenzione ancora una volta che '\0' e "\0" non sono la stessa cosa, in quanto laprima espressione identifica un carattere mentre la seconda una stringa. Un esempiodi stringa puo' essere "pippo" oppure "paperino pippo e pluto". Una stringacostante in memoria viene rappresentata come un vettore di caratteri.Poiche' ogni stringa costante termina con il carattere '\0', lo spazio occupato inmemoria e' maggiore di un carattere. Ad esempio la stringa "ciao" occupa 5 caratteriin quanto in memoria viene rappresentata cosi': "ciao\0". '\0' e' un carattere e nondue in quanto il carattere backslash (barra inversa) in realta' e' un metacarattere,cioe' rappresenta un informazione relativamente al carattere vero e proprio. Adesempio 'b' e '\b' sono due cose diverse in quanto il primo rappresenta il carattere 'b'mentre il secondo rappresenta il carattere di controllo backspace (cioe' il tasto chepermette di tornare indietro nella digitazione del testo cancellando). Infine esistono leespressioni costanti. Un esempio di espressione costante e' la seguente: #defineMASSIMO 1000. Questa e' una direttiva per il precompilatore che all'atto dellacompilazione sostituira' la parola MASSIMO con il valore 1000 in qualsiasi punto delprogramma venga trovata. L'utilita' di queste espressioni constanti e' chiara:

impostato un valore costante poniamo a 1000, se successivamente si rendesse

necessario portare tale valore a 2000, sarebbe sufficiente modificare una sola riga (lariga #define MASSIMO 1000 diventerebbe #define MASSIMO 2000). Viceversa, senzal'uso delle espressioni costanti, occorrerebbe modificare tutte le righe che presentino

il valore costante 1000 per portarlo a 2000.

Inizio della guida Precedente Indice Il primo programma in C

Copyright (c) 2002-2003 Maurizio Silvestri

Guida Linux: il linguaggio C http://www.wowarea.com/italiano/linux/linguaggioc01.htm

12 di 12 03/03/2015 23:23