Introduzione al linguaggio C
Qualche nota storica
¨ C sviluppato da Dennis M. Ritchie agli AT&T Bell Labs fra il 1969 e il 1973
¨ Nato come linguaggio di programmazione per lo sviluppo di Unix e per sopperire alle limitazioni del linguaggio “B”
¨ Ritchie e Brian Kernighan nel 1978 pubblicano il libro "The C Programming Language". Il dialetto descritto nel libro è noto come K&R-Style
¨ Grazie alla portabilità, il linguaggio iniziò subito ad essere apprezzato. Ma è mancata una versione standard del linguaggio fino ANSI 1989 (ANSI-C)
¨ Ultime revisioni dello Standard risalgono al 1999 e al 2011 e sono dette Standard C99 e Standard C11
Linguaggio C
http://spectrum.ieee.org/static/interactive-the-top-programming-languages
Linguaggio C
¨
Software che forse avete usato e che sono scritti in C:
¤ Java virtual machine
¤ Linux (ma una piccola parte è in assembly)
¤ Kernel di Mac OS X
¤ Windows (in C e in C++)
¤ Oracle DBMS (in C e in C++)
¤ Firmware dei router Cisco
Caratteristiche di base
¨
Linguaggio imperativo, procedurale
¨
Linguaggio compilato, ovvero, il codice sorgente viene tradotto interamente da un compilatore in codice eseguibile
Variabili
variabili
¨
Per ogni variabile esistono due operazioni fondamentali:
¨ Dichiarazione: il compilatore deve sapere quanto
spazio riservare per la variabile e come interpretare i bit contenuti in questa area di memoria
¨ Assegnamento (modifica) di un valore: calcolo di un
nuovo valore e modifica del valore corrente di una variabile. Il valore contenuto in una variabile è l’ultimo assegnatole e resta inalterato finché un successivo assegnamento non modifica il valore stesso
¨
Una variabile può essere usata (in un’espressione, per modificarne il valore) solo dopo essere stata dichiarata
variabili
¨ Dichiarazione:
int fattoriale;
¨ Assegnamento:
fattoriale = 1;
¨ Dichiarazione e inizializzazione:
int fattoriale = 1;
¨ Il valore assegnato deve essere coerente con il tipo della variabile
¨ Il C, come altri linguaggi di programmazione, è fortemente tipato, cioè per ogni espressione utilizzata nel programma il compilatore deve sempre essere in grado di determinarne il tipo
Dichiarazione di costanti
¨
Sintassi
const <tipo> <identificatore> = <espr>;
Es.:
const int N = 100;
const float PIGRECO = 3.1415F;
const char SIM = 'A';
¨
Le costanti sono variabili alle quali non è permesso cambiare valore: non possono cioè trovarsi a sinistra
dell’operatore di assegnamento¨
Convenzione: il nome di costante si scrive interamente con caratteri maiuscoli
Dichiarazione di costanti simboliche
¨ Per definire una costante simbolica si scrive:
#define <nome> <valore>
Es.:
#define PIGR 3.1415 #define MSG "ciao mondo!"
¨ Convenzione: come le costanti, devono essere scritte interamente in maiuscolo
¨ Differenza tra #define e const:
¤ Una costante definita con const occupa memoria come una variabile, ma non può essere modificata
¤ Una costante definita con #define non occupa memoria: il preprocessore del C, prima di compilare il codice, sostituisce ogni simbolo con il suo corrispondente valore (come se fosse un
Variabili
¨
Una variabile non può essere “vuota”: a una variabile è sempre associata una locazione di memoria e quindi un valore, che corrisponde al valore dei bit contenuti nella zona di memoria
Se, ad esempio, il compilatore associa a una variabile la locazione di memoria con indirizzo 2341, allora la variabile assumerà come valore il valore “casuale” dei bit all’indirizzo 2341 (ad esempio 42)
¨
Se non provvedete voi, inizialmente la variabile fattoriale conterrà quindi “spazzatura”
¨
Regola d’oro in C:
Inizializzare sempre le variabili!
Variabili
¨
Ogni variabile è caratterizzata da:
¤ Nome: nome simbolico con cui si fa riferimento alla variabile all’interno di un programma
¤ Tipo: definisce l'insieme dei valori che la variabile può assumere
¤ Indirizzo: identifica un'area di memoria che mantiene il valore corrente della variabile
¤ Valore: valore corrente della variabile
¨
Ad esempio:
¤ nome: fattoriale
¤ tipo: int
¤ indirizzo: 2341
¤ valore: 42
Tipi primitivi
¨
Un tipo di dato è caratterizzato da:
¤ la rappresentazione in memoria dei dati di quel particolare tipo (es. quanti bit si usano)
¤ l’insieme di operazioni ammissibili su dati di quel tipo
¨
A cosa servono i tipi di dati?
¤ Per permettere di effettuare controlli statici (cioè al momento della compilazione e non in fase di
esecuzione) sulla correttezza sintattica del programma e quindi prevenire errori di programmazione
Tipi primitivi
¨
Tipi primitivi del C:
char,
int, short,
long,
float,
double¨
I tipi
char, int, short, longcontengono numeri interi,
floate
double
numeri in virgola mobile
¨
Specificatori opzionali:
signed,
unsignedTipi primitivi
¨ Lo standard C, che è pensato per una grande varietà di architetture diverse, non specifica esattamente quanto siano grandi i tipi primitivi
¨ Per sapere quanti byte occupa un determinato tipo su una macchina specifica, si usa a tempo di esecuzione l’operatore sizeof. Ad es.
printf("%d", sizeof(int));
Tipo Dimensione char di solito 1 byte short di solito 2 byte int di solito 4 byte long di solito 8 byte
float Singola precisione (di solito 4 byte) double Precisione doppia (di solito 8 byte)
Tipi interi
¨
I tipi
int,
short e longpermettono di rappresentare numeri interi positivi e negativi su un numero di bit che dipende dalla macchina
¨
Le operazioni possibili su variabili di tipo intero sono le operazioni aritmetiche +, -, *, / e l’operatore % (modulo)
¨
Attenzione: la divisione tra due interi è un intero
¨
Es.:
16/5dà come risultato
3¨
Per avere il resto di questa divisione occorre utilizzare l’operatore % (modulo)
¨ 16%5
dà come risultato 1
Tipi interi
¨ Si può specificare se si rappresentano interi con segno o senza segno: signed int e unsigned int
¨ Per default i tipi int hanno segno (cioè scrivere
semplicemente int è equivalente a scrivere signed int)
¨ una variabile unsigned è normalmente rappresentata con lo stesso numero di byte della variabile signed, tuttavia i valori memorizzati sono privi di segno (e quindi rappresenta esclusivamente numeri positivi)
¨ il numero di bit usato per rappresentare un int varia tra le diverse architetture. Lo standard specifica soltanto che vengano usati almeno 16 bit
¨ Nel file header limits.h sono indicati con costanti
simboliche il valore minimo e massimo che possono assumere le variabili di un determinato tipo (ad es. INT_MIN,
INT_MAX, UINT_MAX)
Tipo char
¨
Rappresenta un numero; di solito occupa 8 bit
¨
Prevede quindi come operatori quelli aritmetici
¨
Usato di solito per contenere il codice ASCII di un carattere
¨
Nelle operazioni, bisogna inoltre considerare che le tabelle ASCII dei caratteri occidentali rispettano il seguente ordinamento:
'0'<'1'<'2'<...<'9'<...
<'A'<'B'<'C'<...<'Z'<...
<'a'<'b'<'c'<...<'z'
Tipi interi
Tipo Dimensione
in byte su x86 a 64 bit
Intervallo rappresentabile
signed char 1 -128 ... +127 unsigned char 1 0 ... +255
signed short 2 -32768 ... +32 767 unsigned short 2 0 ... +65 535
signed int 4 -2 147 483 648 ... +2 147 483 647 unsigned int 4 0 ... +4 294 967 295
signed long 8 -9 223 372 036 854 775 808 ...
+9 223 372 036 854 775 807 (9 miliardi di miliardi) unsigned long 8 0 ... +18 446 744 073 709 551 615 (18 miliardi di
miliardi)
Tipi interi
Si consideri il seguente programma su una macchina x86 a 64 bit
#include <stdio.h>
#define BIG 2000000000 //2 miliardi
int main(void) {
int a, b = BIG, c = BIG;
a = b + c;
printf("%d\n", a);
}
¨ Nonostante i due addendi siano rappresentabili con int, la loro somma è superiore al valore massimo rappresentabile da un int. Si ha
overflow
¨ Il compilatore e il runtime del C non segnalano errori e l’esecuzione prosegue con risultati scorretti (in questo caso -294 967 296)
¨ È compito del programmatore assicurarsi che i valori non escano dagli intervalli appropriati
Tipi float e double
¨
Rappresentano (a precisione singola i float e a precisione doppia i double) i numeri reali
¨
In realtà sono solo una approssimazione (per precisione e intervallo dei valori rappresentabili) dei numeri reali
¨
Le operazioni aritmetiche sui numeri in virgola mobile non sono necessariamente esatte ma sono approssimate
¨
Le operazioni possibili sui tipi float e double sono le operazioni aritmetiche
Espressioni
Espressioni numeriche
¨ Come abbiamo già visto:
¤ 4 è un’espressione il cui valore corrisponde alla costante stessa
¤ x è una variabile, ma è anche un’espressione il cui valore corrisponde al valore assunto dalla variabile
¤ (x+y)*3-z è un’espressione aritmetica in cui valgono le normali precedenze tra operatori
Espressioni con operatori di confronto
¨ Uguaglianza
espr1 == espr2 è un’espressione che assume il valore:
0 quando la valutazione di espr1 restituisce un valore diverso dalla valutazione di espr2
1 altrimenti.
Es: int x=5, y=7;
l’espressione x == y vale 0 (falso) mentre x+2 == y vale 1 (vero)
¨ Disuguaglianza
espr1 != espr2 è un’espressione che vale 1 quando espr1 è diverso da espr2, 0 altrimenti
Es.: int x=5, y=7;
x != y vale 1 (vero) x+2 != y vale 0 (falso)
Espressioni con operatori di confronto
¨ Maggiore (minore)
espr1 > espr2 vale 1 (vero) quando espr1 è maggiore di espr2, 0 (falso) altrimenti [analogo ma opposto per il minore]
Es.: int x=5, y=7;
x > y è falsa (0)
x+2 > y è ancora falsa (0) x+5 > y è vera (1)
¨ Maggiore o uguale (minore o uguale)
espr1 >= espr2 vale 1 quando espr1 è maggiore o uguale di espr2, 0 viceversa.
Es.: int x=5, y=7;
x >= y è falsa (0) x+2 >= y è vera (1) x+5 >= y è vera (1)
Espressioni con operatori di confronto
¨ Negazione
!(x==y) è vera quando x è diverso da y
!(x>=3) è vera quando x è minore di 3, ovvero quando l’espressione x>=3 è falsa
!(!x) doppia negazione afferma: l’espressione è vera quando è vera x
Valori logici
¨ In C non esiste un tipo booleano. Quando si valuta
un’espressione logica (ad esempio in una istruzione if), si assume che sia falso un valore uguale a 0 e vero qualsiasi valore diverso da 0
¨ Quindi in un’espressione logica scrivere “diverso da zero” è opzionale
if (trovato) … equivale a
if (trovato != 0) …
¨ È comunque meglio scrivere !=0 se facilita la comprensione del codice
L’espressione di assegnamento
¨ Anche l’assegnamento è un’espressione x = espr
è un’espressione il cui valore corrisponde al valore dell’espressione espr.
In quanto espressione potrebbe essere usata per discriminare una condizione vera da una condizione falsa, infatti:
x = 0 è un’espressione falsa perché vale 0
x = 8 è un’espressione vera perché vale 8, diverso da 0
¨ ATTENZIONE! Questa caratteristica è una potenziale fonte d’errore, spesso difficile da scoprire:
if (eta=18) {…}
Una disattenzione di questo tipo non causa alcun tipo di errore di compilazione, ma il comportamento del codice non è quello atteso
Espressioni complesse
¨ Due espressioni possono essere congiunte per mezzo dell’operatore && (AND logico)
¨ Se x rappresenta l’età di un individuo, diremo che è minorenne se:
x >= 0 && x < 18
L’espressione è vera quando valgono entrambe le condizioni su x
¨ Due espressioni possono essere disgiunte per mezzo dell’operatore || (OR logico)
¨ Se x rappresenta il peso di un individuo adulto, diremo che non è normale se:
x < 45 || x > 180
L’espressione è vera quando vale almeno una delle condizioni su x
Cambiamenti di tipo impliciti ed espliciti
Cast
Esempio
¨
Scriviamo un programma che converte temperature Fahrenheit in temperature Celsius
¨
La formula è
cels = (fahr - 32) * 5 * 1/9
¨
Sfruttando la proprietà associativa del prodotto possiamo scrivere la formula in due modi
equivalenti:
cels = ((fahr - 32) * 5) / 9 cels = (fahr - 32) * (5 / 9)
Esempio
#include <stdio.h>
int main(void) {
/* stampa la tabella Fahrenheit-Celsius per fahr=0, 40, ..., 320 */
int fahr, cels1, cels2, inf, sup, incr;
inf = 0; /* estremo inferiore della tabella */
sup = 320; /* estremo superiore della tabella */
incr = 40; /* incremento */
fahr = inf;
printf("fahr\tcels1\tcels2\n");
while (fahr <= sup) {
cels1 = ((fahr - 32) * 5) / 9;
cels2 = (fahr – 32) * (5/9);
printf("%d\t%d\t%d\n", fahr, cels1, cels2);
fahr = fahr + incr;
}
return 0;
}
Esempio
¨ L’output del programma è:
fahr cels1 cels2
0 -17 0
40 4 0
80 26 0
120 48 0
160 71 0
200 93 0
240 115 0 280 137 0 320 160 0
Osservazione
Nel nostro programma che differenza c’è tra:
cels1 = ((fahr - 32) * 5) / 9;
e
cels2 = (fahr - 32) * (5 / 9);
¨ Supponiamo fahr = 120 e valutiamo le due espressioni cels1 = ((fahr - 32) * 5) / 9;
cels1 = ((120 - 32) * 5) / 9;
cels1 = (88 * 5) / 9;
cels1 = 440 / 9;
cels1 = 48;
cels2 = (fahr - 32) * (5 / 9);
cels2 = (120 - 32) * (5 / 9);
cels2 = 88 * (5 / 9);
cels2 = 88 * 0;
cels2 = 0;
Osservazione
Overloading
/* divisione tra interi, il risultato è ancora un valore di tipo intero*/
/* divisione tra double, il risultato è ancora di tipo double*/
/* divisione tra float, il risultato è di tipo float*/
¨ In C (come in molti altri linguaggi) operazioni primitive (+,-,*,/,%) sono definite per tipi omogenei (operandi tutti int, o tutti float, o tutti double,
…)
¨ Overloading di significati: lo stesso simbolo (/) è utilizzato in contesti diversi e può dare risultati diversi.
int x, y;
Se x=10 e y=4;
x/y vale 2
double x, y;
Se x=10 e y=4.0;
x/y vale 2.5
float x, y;
Se x=10.0f e y=4.0f;
x/y vale 2.5
Espressioni con tipi non omogenei
¨
Se gli operatori di base sono definiti tra tipi primitivi omogenei, cosa accade quando si scrive un’espressione non omogenea?
int x = 5;
double y = 2.0;
Qual è il tipo dell'espressione x/y? Viene eseguita la divisione tra reali o quella tra interi?
Conversioni di tipo implicite
¨ In C sono possibili operazioni solo tra operandi omogenei, quindi il tipo degli operandi è temporaneamente forzato in modo che l'espressione
x/y diventi omogenea
¨ Il linguaggio promuove automaticamente, quando necessario, i tipi “più piccoli” in tipi “più grandi” seguendo la gerarchia
char < short < int < long < float < double
¨ Cioè un valore di tipo char può essere “visto” come un valore di tipo short oppure int oppure long ecc.
¨ Analogamente un valore di tipo int può essere “visto” come di tipo long, float o double
¨ Nel nostro esempio, il valore assunto dalla variabile x viene promosso al tipo double. In questo modo l'espressione x/y diventa omogenea di tipo double e la divisione diventa in virgola mobile
Esempio
int x = 5;
char y = 7;
double r = 5.0;
double k =(x+y)/ r;
¨ Passo 1: (x+y)
¤ y viene convertito da char all’intero corrispondente
¤ viene effettuata la somma tra interi
¤ risultato intero tmp = 12
¨ Passo 2 (tmp / r)
¤ tmp viene convertito nel double corrispondente (12.0)
¤ viene applicata la divisione tra double
¤ risultato double k=2.4
Compatibilità nell’assegnamento
¨ In generale, sono implicite le conversioni di tipo che non provocano perdita di informazione
¨ Tuttavia, le espressioni che possono provocare perdita di informazioni non sono illegali: il compilatore genera un warning. Per “tacitare” il warning si può fare un cast esplicito
¨ Esempio
int i=5; float f=2.71F; double d=3.1415;
f = f+i; /* int convertito in float */
i = d/f; /* double convertito in int! */
f = d; /* arrotondamento o troncamento */
¨ Al momento della compilazione verrà visualizzato il messaggio:
Possible warning: conversion may lose significant digits
che avvisa che l’assegnamento di un valore float a una variabile intera può causare la perdita di informazioni e che, di per sé, non è un errore
Casting
¨ In qualunque espressione è possibile forzare una particolare conversione utilizzando l’operatore di cast
¨ Sintassi
( <tipo> ) <espressione>
¨ Esempi
int i=5; double x=7.77; double y=7.1;
i = (int) sqrt(384); //sqrt(384)==19.595917 x = y*y;
i = (int) x % (int) y;
Esempio
¨ Ricordate. Come il valore 9 rappresenta una costante intera, il valore 9.0 rappresenta una costante in virgola mobile, quindi la divisione i/9 è diversa dalla divisione i/9.0. Infatti la prima è intera e la seconda è in virgola mobile
¨ Supponiamo di volere effettuare la divisione in virgola mobile nell’espressione double cels3 = ((fahr - 32) * 5) / 9;
Non basta dichiarare cels3 di tipo double
Infatti la divisione sarebbe pur sempre quella intera e solo successivamente il risultato verrebbe promosso a double:
Con fahr = 120, l’espressione
cels3 = ((fahr - 32) * 5) / 9;
assume il valore 48, che verrebbe promosso al tipo double e cioè al valore 48.0
¨ Perché la divisione sia in virgola mobile occorre forzare uno dei due operandi a essere di tipo float o double
Esempio
¨ Un primo modo è quello di usare costanti in virgola mobile invece che intere cels3 = ((fahr - 32) * 5) / 9.0;
¨ Un secondo modo è quello di forzare il tipo di un’espressione con un cast cels3 = (double)((fahr - 32) * 5) / 9;
Il cast forza al tipo double il valore dell’espressione ((fahr - 32) * 5)
Sempre con fahr = 120 l’espressione ((fahr - 32) * 5) assume il valore 440, il quale viene forzato al tipo double, quindi 440.0, e
successivamente viene valutata l'espressione 440.0/9
Per quanto detto prima, i tipi dei due operandi vengono resi omogenei: 9 viene promosso a double e si ha 440.0/9.0 → 48.888… divisione in virgola mobile!
Cast: osservazione sulle precedenze
¨ Poiché l’operatore di cast ha precedenza rispetto agli operatori aritmetici, nell’espressione
(double)((fahr - 32) * 5) / 9 solo ((fahr - 32) * 5) è soggetta al cast
¨ Attenzione! Nella seguente espressione la divisione non è in virgola mobile: perché?
(double)(((fahr - 32) * 5) / 9)
Overflow
Si riconsideri il programma su una macchina x86 a 64 bit
#include <stdio.h>
#define BIG 2000000000 //2 miliardi
int main(void) {
int b = BIG, c = BIG;
long a;
a = b + c;
printf("%ld\n", a);
return 0;
}
¨ Rispetto alla versione vista in precedenza, abbiamo cambiato il tipo della variabile a da int a long. In questo caso si ha ancora overflow? Perché?
Overflow
¨ Si ha overflow anche in questo caso: la somma è tra tipi int e dà un risultato int errato perché in overflow. Solo dopo avviene la promozione a long
¨ Una possibile soluzione consiste nel forzare la somma tra long:
#include <stdio.h>
#define BIG 2000000000 //2 miliardi int main(void) {
int b = BIG, c = BIG;
long a;
a = b + (long) c;
printf("%ld\n", a);
return 0;
Conversioni nei cast
¨
Quando si effettua un cast da un tipo a un altro (o quando si assegna un valore di un tipo a una variabile di un altro tipo), viene effettuata una conversione del valore
¨
Se la conversione avviene tra tipi per i quali ci può essere perdita di informazione, il compilatore può emettere un warning (per abilitare questo warning su gcc si può usare il flag
–Wconversion)
¨
Se la conversione è voluta, si può “tacitare” il warning rendendo il cast esplicito
Conversioni nei cast
Es. (su architettura x86 a 64 bit) int main(void) { int i1, i2 = 12;
long l=3000000000; //3 miliardi double d1, d2 = 7.0, d3 = 2.275;
i1 = d2; //warning: implicit conversion turns floating- point number into integer e assegna 7
i1 = (int) d2; //assegna 7 i1 = (int) d3; //assegna 2 d1 = i2; //assegna 12.0
i1 = l; //warning: implicit conversion loses integer precision e assegna -1294967296
i1 = (int) l; //assegna -1294967296 return 0;
}
Note sulla scrittura di codice
Leggibilità del codice sorgente
¨
Un aspetto importante nella scrittura dei programmi consiste non solo nella correttezza e nell’efficienza del codice, ma anche nella sua leggibilità
¨
Un codice sorgente facilmente leggibile è più facile da capire, permette di coglierne la struttura
velocemente ed è più semplice da modificare in un secondo tempo, quando si fa la manutenzione del codice
¨
Quindi è importante usare nomi di variabile che
rispecchino il loro uso e indentare il codice
L’indentazione del codice sorgente
¨
Indentare il codice sorgente significa fare iniziare una linea a una certa distanza dal margine
¨
Ha lo scopo di mettere in evidenza blocchi di codice
¨
Si fa iniziare la linea con un certo numero di spazi (di solito multiplo di 3 o di 4) o di TAB
¨
L’indentazione viene ignorata dal compilatore C: è utile esclusivamente agli esseri umani che metteranno mano al sorgente del programma
¨
Va aumentato il livello di indentazione per il corpo delle funzioni, dei cicli, delle istruzioni if else
¨
Il codice va indentato mentre si scrive il codice e non in un secondo momento
L’indentazione del codice sorgente
#include <stdio.h>
#define SIZE 1000
#define DASTAMPARE 1
#define DANONSTAMPARE 0 int main(void) {
unsigned int array[SIZE];
unsigned int i, j;
for (i = 0; i < SIZE; i++) array[i] = DASTAMPARE;
for (i = 2; i < SIZE; i++) if (array[i] == DASTAMPARE) for (j = i + 1; j < SIZE; j++) if (j % i == 0)
array[j] = DANONSTAMPARE;
for (i = 2; i < SIZE; i++) if (array[i] == DASTAMPARE) printf( "%u\n", i);
}
Si confronti la leggibilità di queste due versioni (il compilatore C genera il medesimo eseguibile):
#include <stdio.h>
#define SIZE 1000
#define DASTAMPARE 1
#define DANONSTAMPARE 0 int main(void) {
unsigned int array[SIZE];
unsigned int i, j;
for (i = 0; i < SIZE; i++) array[i] = DASTAMPARE;
for (i = 2; i < SIZE; i++) if (array[i] == DASTAMPARE) for (j = i + 1; j < SIZE; j++) if (j % i == 0)
array[j] = DANONSTAMPARE;
for (i = 2; i < SIZE; i++) if (array[i] == DASTAMPARE) printf( "%u\n", i);
}
Utilizzo di costanti
¨ Nel codice sorgente bisogna cercare di non utilizzare valori numerici ma utilizzare delle costanti simboliche.
¨ In questo modo si hanno due vantaggi:
¤ Se fosse necessario cambiarne il valore, sarebbe più facile farlo perché si evita di modificare il codice sorgente in più punti
¤ È più facile comprendere il loro scopo
Utilizzo di costanti
Si confrontino queste due versioni
#include <stdio.h>
#define SIZE 1000
#define DASTAMPARE 1
#define DANONSTAMPARE 0 int main(void) {
unsigned int array[SIZE];
unsigned int i, j;
for (i = 0; i < SIZE; i++) array[i] = DASTAMPARE;
for (i = 2; i < SIZE; i++) if (array[i] == DASTAMPARE) for (j = i + 1; j < SIZE; j++) if (j % i == 0)
array[j] = DANONSTAMPARE;
#include <stdio.h>
int main(void) {
unsigned int array[1000];
unsigned int i, j;
for (i = 0; i < 1000; i++) array[i] = 1;
for (i = 2; i < 1000; i++) if (array[i] == 1)
for (j = i + 1; j < 1000; j++) if (j % i == 0)
array[j] = 0;
for (i = 2; i < 1000; i++) if (array[i] == 1)
Compilazione
¨ gcc produce due tipi principali di messaggio: warning ed errori
¨ I warning sono avvertimenti che indicano possibili problemi nel codice ma non pregiudicano la compilazione
¨ Non tutti i warning vengono visualizzati, quindi è preferibile utilizzare il flag -Wall: gcc -Wall sorgente.c -o eseguibile
¨ Per un warning, gcc riporta il nome del file sorgente, il numero di linea e colonna in cui è stato rilevato il warning e un messaggio Ad es., se nel file sorgente.c alla linea 5 ho un’istruzione if (a=0), gcc stampa il messaggio:
sorgente:5:8: warning: using the result of an assignment as a condition without parentheses
¨ È buona norma correggere il sorgente in modo da eliminare tutti i problemi segnalati dai warning
Compilazione
¨ Gli errori, invece, interrompono la compilazione e l’eseguibile non verrà generato
¨ Anche per gli errori, gcc riporta il nome del file sorgente, il numero di linea e colonna in cui è stato rilevato il problema e un messaggio
¨ Può succedere che l’errore non sia nella linea segnalata ma nelle linee precedenti