• Non ci sono risultati.

Introduzione al linguaggio C Qualche nota storica

N/A
N/A
Protected

Academic year: 2021

Condividi "Introduzione al linguaggio C Qualche nota storica"

Copied!
28
0
0

Testo completo

(1)

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

(2)

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

(3)

Caratteristiche di base

¨ 

Linguaggio imperativo, procedurale

¨ 

Linguaggio compilato, ovvero, il codice sorgente viene tradotto interamente da un compilatore in codice eseguibile

Variabili

(4)

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

(5)

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

(6)

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

(7)

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, long

contengono numeri interi,

float

e

double

numeri in virgola mobile

¨ 

Specificatori opzionali:

signed

,

unsigned

(8)

Tipi 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 long

permettono 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/5

dà come risultato

3

¨ 

Per avere il resto di questa divisione occorre utilizzare l’operatore % (modulo)

¨  16%5

dà come risultato 1

(9)

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'

(10)

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

(11)

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

(12)

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)

(13)

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

(14)

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

(15)

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

(16)

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;

}

(17)

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);

(18)

¨  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

(19)

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

(20)

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

(21)

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

(22)

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)

(23)

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;

(24)

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;

}

(25)

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

(26)

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);

}

(27)

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)

(28)

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

Riferimenti

Documenti correlati

La prima fase del concorso prevede una selezione delle migliori iniziative imprenditoriali sulla base di un business plan che descriva dettagliatamente l’idea d’impresa che si intende

ƒ restituisce un puntatore che contiene l’indirizzo del blocco di memoria allocato, cioè della variabile, la quale sarà quindi accessibile dereferenziando il puntatore. ƒ la

■ Quindi il vero argomento della prima funzione non é la seconda funzione, ma un normale valore, che può avere qualsiasi origine (variabile, espressione ecc...), e in particolare

Generalmente il confronto può essere fatto tra due generici puntatori che puntano allo stesso tipo di dato; questo confronto è comunque significativo solo nel caso in

[r]

[r]

Corso di STATISTICA MATEMATICA Prova scritta del

 Le operazioni matematiche possono essere eseguite solo tra due operandi dello stesso tipo (int con int, long con long, float con float, double con double, etc.)..  I