Category Archives: pR0Gr4MmaZion3

[PR0GRAMMAZi0NE UNiX: rIPASSO C][ PARTE IV : Struttura programma C ]


----------------------[ PR0GRAMMAZi0NE UNiX: rIPASSO C ]----------------------
---------------------[ PARTE IV : Struttura programma C ]---------------------
--------------------------------[ abnm3nt4l1 ]--------------------------------

Indice

00. Introduzione
01. Consumo
03. Costanti e variabili
04. Espressioni in C
05. Istruzioni e blocchi di istruzioni in C
06. Funzioni in C

00. iNtrODUZIONE
------------

Bene dopo aver superato piu' o meno indenni il periodo compreso tra il 24
Dicembre 2014 e il 2 Gennaio 2015 (piu' o meno perche' ~ 4 kg di peso
guadagnati per uno che oltre all'informatica ha la passione per la corsa
a piedi sono TAANTA ROBA!) vi do' un augurio di un anno ricco di
serenita', felicita', prosperita' e soddisfazioni. So che la serenita'
non e' una caratteristica delle giornate di un VUA ma augurarcela non
costa nulla (per il momento!).
Sull'altra mia passione prima o poi conto di pubblicare qualcosina
in futuro. Per ora torniamo all'informatica e in particolare al C.
In questo episodio daremo un'occhiata alla struttura di un programma
scritto in C.
Fino a ora abbiamo visto che un programma scritto in C dev'essere compilato
per essere eseguito e cosa accade dietro le quinte quando utilizziamo il
compilatore GCC per convertire il nostro sorgente in eseguibile binario.
Ora diamo un'occhiata alle peculiarita' del linguaggio e della struttura
di un programma. In particolare faro' un escursus su:

- Costanti e variabili;
- Espressioni;
- Istruzioni;
- Blocchi di istruzioni;
- Tipi di funzioni e nomi in C;
- Passaggio di argomenti alle funzioni;
- Il corpo delle funzioni;
- Chiamate di funzione;

02. cONSUMO
-------

1) un'arancia del Medio Campidano
2) 30 gr di frutta secca non salata
3) te' verde non zuccherato

03. c0St4nt1 e vARiaBIli
--------------------
Cosi' come un edificio e' costruito da mattoni, un programma in C e' si compone
di elementi base come espressioni, istruzioni, blocchi di istruzione e funzioni.
Prima pero' di discuttere questi elementi di base occorre prendere dimestichezza
con due costituenti dei programmi C, piccoli ma importanti, sui quali si basano
le espressioni: costanti e variabili.

Come si evince dallo stesso nome con il termine costante ci si riferisce a valori
fissi che non vengono alterati nel corso del programma. Questi valori sono chiamati
anche "literals" (leterali).
Le costanti possono essere di qualsiasi tipo fondamentale del C: numeri interi,
decimali, caratteri, stringhe e perfino enumerazioni.
Le costanti sono trattate alla stregua delle variabili eccetto per il fatto
che il loro valore non puo' essere alterato.

Vediamo come definire le costanti in C e quali errori vengono generati se si cerca
di modificarne il valore:

/* const.c */
#include

/* definizione di una costante con direttiva al preprocessore #define */

#define COSTANTE1 "CIAO\n"

int main (void) {
/* definizione di una costante tramite keyword const */
const char COSTANTE2[] = "CIAO CIAO\n";

printf("%s", COSTANTE1);
printf("%s", COSTANTE2);
return 0;
}

Questo codice compilato ed eseguito fornisce il seguente output.

> ./const
CIAO
CIAO CIAO

Vediamo le differenze tra le due modalita' di dichiarazione di valori costanti.

Utilizzando la direttiva al preprocessore in fase di inclusione questo cerchera'
le varie occorrenze della costante nel programma e le sostituira' con il valore.

In questo caso non si tratta comunque propriamente di una costante in quanto non
viene allocata memoria all'interno del programma e come detto in precedenza quando
il compilatore in fase di pre-processing incappa in una #define cerca le occorrenze
della costante nel programma e ne sostituisce il valore con quello dichiarato.

Output del preprocessing del nostro programma

-x-
int main (void) {
const char costante2[] = "CIAO CIAO\n";

printf("%s", "CIAO\n");
printf("%s", costante2);
}
-x-

Con la const viene invece creata una vera e propria variabile in sola lettura
in modo pulito e veloce. E' il metodo preferito per dichiarare variabili di tipo
costante.

Una variabile, contrariamente a cio' che abbiamo detto per le costanti, altro
non e' che un'area di storage per i dati del nostro programma.
I valori contenuti nell'area di storage referenziata da una variabile possono
variare nel corso dell'esecuzione del nostro programma.
Ogni variabile in C e' di un tipo specifico; questo determina:
la dimensione e il layout dell'area di memoria, il set e il range di valori
che possono essere immagazzinati e il tipo di operazioni che possono essere
effettuate sulla variabile.
Il nome puo' essere composto da lettere, numeri e caratteri underscore. Il
nome deve iniziare con una lettera o un underscore. Tenete inoltre a mente
che il C e' case-sensitive quindi distingue caratteri maiuscoli e minuscoli.
int pippo e int Pippo rappresentano due differenti nomi di variabili di tipo
intero.

In seguito vedremo i tipi di dato in C e le operazioni applicabili a questi.

04. Espressioni in C
----------------

Le espressioni sono la combinazione di costanti, variabili e operatori utilizzati
al fine di ottenere un particolare risultato.

Per esempio:

(3 * 5) + 10

e' un'espressione che moltiplica 3 a 5 e poi aggiunge 10 al risultato.
Esistono diversi tipi di espressione. Per esempio 6 e' una espressione costante;
z e' un'espressione variabile; 6 + z e' un'espressione di una costante a cui e'
sommata una variabile; exit(0) e' un'espressione di chiamata a funzione.

Oltre a numeri e operatori, le espressioni si compongono degli identificatori.
In questa famiglia riccadono sia i nomi di variabili e costanti che quelli di
funzioni e parole chiave del linguaggio.

05. iStRuz1oNi 3 bL0cCh1 di 1sTrUzion1 (sT4t3meNt) in C
---------------------------------------------------

Nel linguaggio C uno statement e' un'istruzione "completa" ovvero che si conclude
con un ";".
In molti casi e' possibile trasformare una espressione, vedi un'assegnazione di
variabile, in una istruzione aggiungendo un ";".

Es.:

z = 4;

Un gruppo di istruzioni puo' costiture un "blocco di istruzioni".
Un blocco di istruzioni (stament block) viene racchiuso tra parentesi graffe { ... }.

Per esempio quanto segue rappresenta il blocco di istruzioni della funzione main():

int main (void) {
const char costante2[] = "CIAO CIAO\n";

printf("%s", "CIAO\n");
printf("%s", costante2);
}

06. Funzioni in C
-------------

Le funzioni costituiscono i building block dei programmi in C. Oltre alle funzioni
standard e' possibile crearne e utilizzarne di tue all'interno di un programma C.
Nel primo programma che abbiamo scritto (e anche nei successivi esempi) abbiamo
fatto riferimento a funzioni standard del linguaggio o incluse nella libreria
standard: main(), return , printf().
Ora diamo un'occhiata da piu' vicino all'anatomia di una funzione in C.

La definizione di una funzione in C ha la seguente forma:

tipo_di_dato_restituito nome_funzione (lista dei parametri)
{
corpo della funzione
}

Una funzione in C consta di un header e di un corpo. Vediamo nel seguito le
parti che compongono questi due macrocostituenti:

- tipo di dato restituito: una funzione puo' restituire un valore. Il tipo di funzione
indica il tipo di dato che questa restituisce al termine dell'esecuzione. Alcune
funzioni, meglio note come procedure non restituiscono alcun tipo di dato al
termine della loro esecuzione. Per queste il return type sara' void.
- nome_funzione: e' il nome che identifica la funzione. E' bene che sia un nome
mnemonico e che riconduca a cio' che la funzione esegue.
- parametri: ai quali ci si riferisce come argomenti indicano i dati in input
alla funzione. Sono del tutto opzionali. Vi sono funzioni che non
necessitano di parametri
- corpo della funzione: consta di un insieme di funzioni che determinano cio'
che fa la funzione.

Es.:

/* funzione massimo: restituisce il piu' grande tra due numeri interi */

int massimo (int numero1, int numero 2) {
int risultato;

if ( numero1 > numero2 ) risultato = numero1;
else risultato = numero2;
return risultato;
}

Ovviamente e' possibile separare la dichiarazione dalla definizione della funzione.
Per esempio questo e' utile quando si definisce la funzione in un file sorgente
diverso rispetto a quello nel quale la funzione e' dichiarata.
Per esempio se noi dobbiamo utilizzare (chiamare) la funzione massimo nel file
main.c ma questa e' definita nel file funzioni.c in main.c dovro'
dichiarare in main.c la funzione, prima che questa venga chiamata, come segue:

int massimo(int, int);

Nella dichiarazione non e' importante il nome dei parametri ma solo il loro tipo,
cosi' che il compilatore possa valutare la correttezza della chiamata.

Un altro esempio di separazione tra dichiarazione e definizione si ha quando
si vogliono raggruppare tutte le definizioni di funzioni alla fine del nostro
programma, dopo il main. In questo caso perche' queste possano essere utilizzate
nel corpo del nostro programma occorre definire i prototipi o sia le dichiarazioni
prima del main.

Es.:

#include

/* dichiarazione massimo */

int massimo(int, int);

int main ( void ) {

printf("Il massimo tra 20 e 21 e': %d\n", massimo(20,21));
return 0;
}

/* definizione di massimo */
int massimo(int numero1, int numero2) {
int risultato;
if (numero1 > numero2 ) risultato = numero1;
else risultato = numero2;
return risultato;
}

And now, vediamo come usare queste funzioni in C!

Making function call

Durante la creazione di una funzione in C si stabilisce cio' che essa
deve fare.
Quando invece un programma effettua una chiamata alla funzione questo,
passa il controllo alla funzione chiamata. Una funzione chiamata effettua
i task per i quali e' stata programmata e quando incontra il termina della
funzione "}" o l'istruzione di return, restituisce il controllo al chiamante.
Se e' programmata per restiture un valore di un tipo prestabilito lo fornira'
in output al chiamante.

Per chiamare una funzione occorre semplicemente passare i parametri che essa
si attende accanto al nome e qualora questa restituisca un valore memorizzarlo
da qualche parte.

Torniamo a massimo.

#include

/* dichiarazione massimo */

int massimo(int, int);

int main ( void ) {
int max ;

/* chiamata della funzione massimo */
max = massimo (2,3);
printf("il massimo tra 2 e 3 e': %d\n", max);
return 0;
}

/* definizione di massimo */
int massimo(int numero1, int numero2) {
int risultato;
if (numero1 > numero2 ) risultato = numero1;
else risultato = numero2;
return risultato;
}

Argomenti delle funzioni

Se una funzione deve usare degli argomenti devono essere dichiarate delle
variabili che accettino il valore per questi argomenti. Queste prendono
formalmente il nome di parametri.

Questi parametri vengono creati quando una funzione viene chiamata e distrutti
al termine della chiamata della funzione.

Quando chiamiamo una funzione ci sono due modi di passare gli argomenti:

per valore: questo metodo copia l'attuale valore di un argomento in un parametro
della funzione. In questo caso le modifiche apportate al parametro
all'interno della funzione non hanno effetto sugli argomenti.
per riferimento: questo metodo copia l'indirizzo dell'argomento nel parametro.
All'interno della funzione l'indirizzo e' utilizzato per accedere
all'argomento usato nella chiamata. Questo significa che le modifiche
apportate al parametro hanno poi effetti sull'argomento.

Esempio di funzione con passaggio di parametri per valore:

#include

void scambio ( int x, int z);

int main ( void ) {
int arg1, arg2 = 0;

arg1 = 2;
arg2 = 1;

printf("arg1 vale: %d - arg2 vale: %d\n", arg1, arg2);

scambio(arg1,arg2);
printf("Dopo scambio: arg1 vale: %d - arg2 vale: %d\n", arg1, arg2);

return 0;
}

void scambio( int x, int z) {
int temp;

printf("Funzione pre-scambio: parametro x vale: %d - parametro z vale: %d\n", x, z);

temp = x;
x = z;
z = temp;
printf("Funzione Scambiati: parametro x vale: %d - parametro z vale: %d\n", x, z);
return;
}

OUTPUT:

arg1 vale: 2 - arg2 vale: 1
Funzione pre-scambio: parametro x vale: 2 - parametro z vale: 1
Funzione Scambiati: parametro x vale: 1 - parametro z vale: 2
Dopo scambio: arg1 vale: 2 - arg2 vale: 1

Esempio di funzione con passaggio di parametri per riferimento:

#include

void scambio ( int *x, int *z);

int main ( void ) {
int arg1, arg2 = 0;

arg1 = 2;
arg2 = 1;

printf("arg1 vale: %d - arg2 vale: %d\n", arg1, arg2);

scambio(&arg1,&arg2);
printf("Dopo scambio: arg1 vale: %d - arg2 vale: %d\n", arg1, arg2);

return 0;
}

void scambio( int *x, int *z) {
int temp;

printf("Funzione pre-scambio: parametro x vale: %d - parametro z vale: %d\n", *x, *z);

temp = *x;
*x = *z;
*z = temp;
printf("Funzione Scambiati: parametro x vale: %d - parametro z vale: %d\n", *x, *z);
return;
}

OUTPUT:

arg1 vale: 2 - arg2 vale: 1
Funzione pre-scambio: parametro x vale: 2 - parametro z vale: 1
Funzione Scambiati: parametro x vale: 1 - parametro z vale: 2
Dopo scambio: arg1 vale: 1 - arg2 vale: 2

Enjoy,

-- abnm3nt4l1st

[ PR0GRAMMAZi0NE UNiX: rIPASSO C ] – [ PARTE III : GCC Compilation step ]


----------------------[ PR0GRAMMAZi0NE UNiX: rIPASSO C ]----------------------
---------------------[ PARTE III : GCC Compilation step ]---------------------
--------------------------------[ abnm3nt4l1 ]--------------------------------

Indice

00. Introduzione
01. Consumo
03. Basi del processo di compilazione
04. Preprocessing, compilazione, assembly, Linking
05. Qualche esempio

00. iNtrODUZIONE
------------

Nel precedente post abbiamo trattato in maniera sommaria il "three-stage" process
di creazione di un programma binario eseguibile a partire dal suo codice sorgente
in C.
Di questi tre passi, il secondo, quello di compilazione del codice sorgente
che ha come risultato la produzione di un file binario verra' descritto nel
seguito del presente articolo in relazione a cio' che viene effettuato dal
compilatore GCC dietro le quinte.

02. cONSUMO
-------

1) ll contenuto di una caffettiera da 3 di caffe' 100% arabica tostatura forte
2) Una tazza di te' verde
3) ... acqua! (ragazzi sono appena trascorti 3 intensi giorni di mangiate
e bevute a base alcolica)

03. Basi del processo di compilazione
---------------------------------

In principio fu:

$ gcc -o salutomondo salutomondo.c

$ ./salutomondo
Hello, World!

Il sorgente e' il medesimo mostrato nel primo post della serie.

Ad ogni modo abbiamo un banalissimo programma scritto in C che stampa
sul vostro terminale la stringa "Hello, World!" seguita dalla sequenza
di escape \n.

Il codice sorgente viene compilato per mezzo di gcc e prodotto il file
eseguibile di output (-o) salutomondo.

Nel seguito diamo un'occhiata a cio' che esegue gcc (talvolta avvalendosi
di altri strumenti) per generare il file eseguibile salutomondo a partire
dal file sorgente salutomondo.c.

A tal riguardo aggiungiamo un'opzione alla nostra linea di commando per
istruire il nostro compilatore affinche' salvi i file temporanei che genera
nel corso della compilazione di un sorgente C, --save-temps.

$ gcc -save-temps -o salutomondo salutomondo.c

$ ls salutomondo*
salutomondo salutomondo.c salutomondo.i salutomondo.o salutomondo.s

A differenza dall'esecuzione del primo comando, ora nella directory nella quale abbiamo
eseguito la compilazione oltra al file sorgente salutomondo.c ed eseguibile salutomondo
troviamo altri 3 file con estensione .i, .o e .s.
Prima di procedere con la verifica del contenuto dei singoli file riporto il contenuto
della man page di gcc per comprendere cosa effettivamente gcc quando invocato Con
l'opzione -save-temps.

-save-temps
-save-temps=cwd
Store the usual "temporary" intermediate files permanently; place them in
the current directory and name them based on the source file.
Thus, compiling foo.c with -c -save-temps produces files foo.i and foo.s,
as well as foo.o. This creates a preprocessed foo.i output file even
though the compiler now normally uses an integrated preprocessor.

Quindi in poche parole non facciamo altro che indicare al compilatore di salvare
tutti i file temporanei che genera in ciascuno dei 4 passi che compie nel corso
del processo di compilazione e che elimina quando giunge al termine dello stesso.

04. Preprocessing, compilazione, assembly, Linking

Gli step che il compilatore compie sono:

1. Preprocessing;
2. Compilazione;
3. Assembly;
4. Linking.

1. Preprocessing

In questa fase tutti gli header file che si sono inclusi e utilizzati nel proprio
programma vengono espansi e inclusi nella posizione in cui compare la direttiva
#include relativa all'header stesso.
Oltre questo e' nel corso di questa fase che i valori di costanti e macro vengono
sostituiti e i commenti rimossi.

Il file intermedio generato dalla fase di preprocessing e' quello con estensione .i.

2. Fase di compilazione

In questa fase il compilatore compila il sorgente producendo il codice assembly.
Il codice assembly consta di un insieme di istruzioni che determinano cio' che
il programma fa. Ciascuna piattaforma ha un specifico insieme di istruzioni
assembly che vengono direttamente mappate in istruzioni relative processore.

Vediamo il contenuto del file salutomondo.s

.file "salutomondo.c"
.section .rodata
.LC0:
.string "Hello, World!"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: what fuck?"
.section .note.GNU-stack,"",@progbits

3. Assemblaggio (assembly step)

A questo punto, un compilatore particolare, l'assemblatore converte le istruzioni
assembly generate nella fase di compilazione e le converte nelle corrispondenti
istruzioni macchina o meglio un bit stream che consta di 0 e 1. Questo codice,
direttamente comprensibile al processore, viene salvato nel file oggetto salutomondo.o.
Questo file e' costituito da differenti sezioni che il processore utilizza durante
l'esecuzione del programma.

Il contenuto di questo file non risulta leggibile e interpretabile all'uomo per mezzo
di strumenti quali editor a eccezione di pochi simboli (come printf, main o stringhe).

4. Linking

Eccoci all'ultimo step del processo di compilazione.
Il file oggetto contiene il codice macchina prodotto dall'assemblatore alla fase
precedente; il linker si preoccupa di garantire che tutti i simboli non
definiti nel sorgente perche' inclusi da librerie esterne vengano risolti
unendo tra loro i diversi file oggetto e creando un unico file eseguibile.

I 4 step possono essere eseguito singolarmente utilizzando gcc con le opzioni
-E, -C e -S e il linker ld separatamente. Vedremo nel seguito degli esempi.

05. Qualche esempio
---------------

-- Creiamo l'eseguibile salutomondo con un solo comando preservando i file
temporanei generati dagli step intermedi

$ gcc -save-temps salutomondo.c -o salutomondo

-- Creiamo un eseguibile eseguendo manualmente le singole fasi che dal sorgente
conducono alla creazione dell'eseguibile

a. preprocessore

$ cpp salutomondo.c > salutomondo.i

oppure

$ gcc -E salutomondo.c -o salutomondo.i

b. compilazione

$ gcc -S salutomondo.i -o salutomondo.s

c. assemblaggio

$ as salutomondo.s -o salutomondo.o

d. linking

$ ld -o salutomondo -dynamic-linker /lib64/ld-linux-x86-64.so.2 \
/usr/lib64/crt1.o /usr/lib64/crti.o salutomondo.o -lc /usr/lib64/crtn.o

Provate e divertitevi!

— abnm3nt4l1st

[ PR0GRAMMAZi0NE UNiX: rIPASSO C ][ PARTE II : Problem officer! ]


----------------------[ PR0GRAMMAZi0NE UNiX: rIPASSO C ]----------------------
---------------------[ PARTE II : Problem officer! ]---------------------
--------------------------------[ abnm3nt4l1 ]--------------------------------

Indice

00. Introduzione
01. Consumo
03. Compilazione e Linking
04. Cenni sulla gestione degli errori e il debugging

00. iNtrODUZIONE
------------

Nulla da dichiarare!

02. cONSUMO
-------

1) ll contenuto di una caffettiera da 3 di caffe' 100% arabica tostatura forte
2) Una tazza di te' verde
3) biscotti di cereali integrali e frutta (con moderazione. Sono a dieta)

03. cOMP1L4zione 3 lINKING
----------------------

Per ottenere un eseguibile ossia un programma in linguaggio macchina direttamente
comprensibile al nostro computer occorre compiere 3 step.

Per prima cosa dobbiamo avere il file sorgente del nostro programma scritto in C
che per consuetudine terminano con estensione .c .
Il sorgente viene compilato dal compilatore C che genera un nuovo file, il file
oggetto che sui sistemi linux ha estensione .o .
Il file oggetto e' un file binario che non puo' pero' essere eseguito in
quanto potrebbe non essere completo di tutte le librerie che vengono incluse
durante l'ultimo step quello di linking. L'operazione di linking e' eseguita
da un programma speciale chiamato linker che viene generalmente incluso
nel package del compilatore.
Il linker, (ld su Linux) e' usato per unire tra loro i file oggetto, la
libreria standard C e altre librerie generate dall'utente per creare il
file eseguibile - il codice binario.
In questo passo, il codice binario delle librerie che sono invocate dal
sorgente vengono combinate con il file oggetto generato a partire dal sorgente
stesso.
Il risultato e' salvato in un nuovo file - il file eseguibile.
Occorre tenere a mente che i file oggetto e quelli eseguibili sono dipendenti
dall'architettura hardware e dal sistema; non possono essere semplicemente
copiati e trasferiti su piattaforme differenti nonostante lo sia (nel caso
in cui si sia fatto solo impiego della libreria ANSI C) il codice sorgente.

04. cENNI sULLa GeSTIOnE dEGLI eRRoRI e iL DeBUGGINg
------------------------------------------------

Una volta terminata la stesura del programma ci capitera' in fase di
compilazione di incorrere in diversi messaggi di errore o di warning.
Non preoccupatevi siamo umani! Pensate solo a gioire del fatto che il
compilatore si preoccupera' con voi di verificare la correttezza di quanto avete
prodotto prima di farvi fare ulteriori ....

Il compilatore infatti si preoccupera' di verificare la correttezza sintatica
del programma mentre il linker generera' degli errori qualora non sia possibile
recuperare le librerie contenenti la definizione delle funzioni che avete utilizzato.
Per esempio se utilizzate la funzione matematica sin() e avete sbagliato a digitare
scrivendo sinn() nel vostro codice la compilazione si interrompera' con un messaggio
simile a questo:

/tmp/ccLySlSb.o: In function `main':
error.c:(.text+0xf): undefined reference to `sinn'

Tutti gli errori individuati (dal compilatore o dal linker) devono essere corretti
prima che possa essere generato il file eseguibile.

Gli errori presenti nei programmi vengono chiamati BuG. In molti casi questi
verranno individuati da compilatore o linker ma molto piu' spesso di quanto
ci si possa aspettare la generazione dell'eseguibile giungera' a termine pero'
il programma non si comportera' come atteso e restituira' risultati non corretti.
Per individuare questi errori nascosti e' necessario utilizzare un debugger.

Generalmente la suite di sviluppo oltre al compilatore prevedere anche la
disponibilita' di un debugger.
Il debugger consente l'esecuzione del programma una riga per volta cosi'
da poter verificare cosa viene effettuato operazione per operazione.
Grazie al debugger e' poi possibile verificare il contenuto di variabili,
alterarne il contenuto, bloccare l'esecuzione di un programma in punti
o condizioni particolari.

In seguito tratteremo nel dettaglio l'utilizzo del debugger della
famiglia GNU: GDB.

Have Fun ;-)

-- abnm3nt4l1

[ PR0GRAMMAZi0NE UNiX: rIPASSO C ] – [ PARTE I : Hello, World! ]


----------------------[ PR0GRAMMAZi0NE UNiX: rIPASSO C ]----------------------
-------------------------[ PARTE I : Hello, World! ]-------------------------
--------------------------------[ abnm3nt4l1 ]--------------------------------

Indice

00. Introduzione
01. Consumo
02. Ready, set, go
03. Scriviamo un un po' di C
04. Ringraziamenti

00. iNtrODUZIONE
------------

If one learn from others but does not think, one will be bewildered;
If one thinks but does not learn from others, one will be in peril.

-- Confucius

rIPASSANDO iL C

Dopo aver letto un bellissimo e articolato ebook di BlackLight sulla
programmazione in C dal titolo Il linguaggio C, reperibile in rete e rilasciato
sotto CC, mi sono reso conto che abbandonare e riprendere un linguaggio di
programmazione non e' propriamente come "imparare ad andare in bicicletta".
C'e' poi un altro aspetto che mi ha portato a eseguire questo ripasso e
condividerlo con voi: quando si ripassa e ristudia un argomento si apprendono
sempre tematiche trascurate nel corso di una precedente lettura.

Giusto un'altra nota su questo insieme di "cose" che scrivero' nel corso del
ripasso. Tenuto conto del fatto che l'intento di questo blog e' la condivisione
delle mie ricerche, studi, esperienze e conoscenze ma soprattutto la mia
crescita professionale e il divertimento utilizzero' come template per i miei
articoli quella della ezine BFi che mi ha sempre affascinato quando ero un
giovane studentello delle superiori.

Per chi non sapesse cosa fosse BFi vi rimando a questa descrizione presente
in wikipedia

-> http://it.wikipedia.org/wiki/Butchered_From_Inside

A ogni modo, bando alle ciance e partiamo operativamente con questo ripasso.

02. cONSUMO
-------

1) ll contenuto di una caffettiera da 3 di caffe' 100% arabica tostatura forte
2) Una tazza di te' verde
3) dato il periodo :-) frutta secca a q.b.

03. Ready, set, go
--------------

Il C e' un linguaggio di programmazione di alto livello che vide la luce,
nei laboratori della AT&T Bell, nel 1972 a opera di Dennis Ritchie.
Questi, fece girare un primo prototipo del C su un DEC PDP-11 con sistema
operativo UNIX. Il C fu il risultato dello sviluppo di due linguaggi di
programmazione piu' vecchi: il B di Ken Thompson e il BCPL di Martin
Richards. Per anni il linugaggio C (il C per gli amici) fu strettamente legato
al sistema operativo UNIX (infatti dopo la progettazione del C tutte le
successive versioni del sistema operativo furono scritte in questo linguaggio
che ancora oggi costituisce la base per i sistemi UNIX-like inclusi Linux e
BSD).

Giusto un appunto: io e molti altri consideriamo il C non propriamente un
linguaggio di alto livello ma di medio livello in quanto utilizzato per
la programmazione di sistemi operativi (non solo applicazioni) e driver
per la gestione delle periferiche.

Essendo comunque un linguaggio di livello piu' elevato di quello macchina
questo offre tutti i vantaggi tipici dei linguaggi di alto livello, ovvero:

1) leggibilita';
2) manutenibilita';
3) portabilita'.

I benefici di leggibilita' e manutenibilita' sono direttamente legati dalla
sua vicinanza con i linguaggi umani, in particolare l'inglese.
Ogni linguaggio di alto livello ha poi bisogno di un compilatore o un interprete
che traduca le istruzioni in linauggio macchina che il computer possa comprendere
ed eseguire.
Cosi', differenti architetture o sistemi operativi potrebbero necessitare di
compilatori o interpreti differenti. Tuttavia se un programma viene scritto
senza l'utilizzo di funzioni strettamente dipendenti dal sistema operativo
o dall'architettura sulla quale viene programmato, puo' essere compilato
e successivamente eseguito senza modifiche su altre architetture e OS.

Tra gli altri vantaggi del C riporto la semplicita' (e' un linguaggio costituito
da poche parole chiave) e la facilita' di riuso del codice scritto tramite
la creazione di librerie.

*** Troppe varianti per un solo linguaggio: nascita dell'ANSI C ***

Per molti anni lo standard de facto per il C era quello descritto nel libro
di Brian Kernighan e Dennis Ritchie (il padre fondatore, R.I.P.), il Linguaggio
C o C Programming Language, presente nelle librerie della stragrande
maggioranza dei programmatori e noto come K&R (dalle inziali dei due autori).
Tuttavia il libro era scritto come tutorial al linguaggio e non voleva essere
una definizione dello standard cosi' differenti vendor proponevano una loro
variante (perdendo anche uno dei beneifici primari dei linguaggi di alto
livello: la portabilita').
Fu cosi' che nel 1983 un gruppo di vendor e sviluppatori si rivolsero
all'ANSI (American National Standards Institute) per creare una
versione standard del linguaggio. Alla fine del 1989 venne approvato
lo standard ANSI C.
Lo standard miglio' il C proposto da K&R e defini' un gruppo di funzioni
che andarono a costituire una libreria nota come ANSI C standard library.
La maggior parte dei compilatori moderni include la libreria standard.

sETUP DEL SISTEMA

Per programmare in C abbiamo bisogno di ben poche cose: un PC con sistema
operativo Unix-Like (una qualsiasi distribuzione Linux andra' piu' che bene),
un editor di testo (vi/vim, emacs, kate o gedit), un compilatore e un linker.
Ovviamente gli esempi faranno riferimento solo a sistemi Unix-Like, in
particolare Linux e presuppongono che il lettore abbia un minimo di
dimestichezza con questi ultimi e la shell che utilizzeremo al minimo
per compilare ed eseguire i nostri programmi.

03. Scriviamo un un po' di C
------------------------

Taglia il tuo albero e ti riscalderai due volte.

--Proverbio cinese

Il primo programma che andremo a realizzare sara' breve e semplice.
Stampera' sul nostro schermo un messaggio di saluto ed uscira'.

1) Aprite il vostro editor di testo preferito e scrivete:
(scrivete non copiate e incollate; scrivere vi aiutera' a memorizzare,
ricordare e prendere dimestichezza con il C)

/* salutomondo.c */

#include

int main(void) {
printf("Hello, World!\n");
return 0;
}

Una volta scritto il codice nel vostro editor preferito, salvatelo come
salutomondo.c

2) Utilizzando gcc (GNU C Compiler) compilate il programma:

$ gcc -o salutomondo salutomondo.c

3) Eseguiamo il nostro programma che stampera' sullo schermo la stringa
"Hello, World!" e andra' a capo:

$ ./salutomondo
Hello, World!
$

La prima riga del programma che riporta lo statement:

/* salutomondo.c */

e' un commento. In C i commenti si aprono la combinazione di caratteri /* e
terminano con la sequenza */.
Il compilatore ignora qualsiasi cosa compaia tra i marcatori di apertura e
chiusura di commenti.
Lo solo scopo dei commenti e' quello di documentare cosa fa il programma o
specifiche porzioni dello stesso al fine di facilitarne la leggibilita',
la comprensione e la manutenibilita' a se stessi o altri programmatori.
Oltre allo stile standard di commento in C che prevede l'utilizzo dei
marker /* e */ oggi giorno i compilatori supportano e comprendono anche
i commenti C++ che iniziano con // e finiscono con la fine della riga.

c: /* questo e' un commento */
c++: // questo e' un commento
c: /* questo e' un commento
distribuito su piu' righe
*/
c++: // questo e' un commento
// distribuito su piu' righe

I commenti in stile C++ se si usa il compilatore con l'opzione
-Wall -pedantic generano un messaggio di warning.

salutomondo.c:1:1: warning: C++ style comments are not allowed in ISO C90 [enabled by default]
// salutomondo.c

Se viene invece richiesta al compilatore la piena attinenza allo standard ANSI
C90 questo generera' un errore e non creera' l'eseguibile.

$ gcc -Wall -pedantic -std=c90 salutomondo.c
salutomondo.c:1:1: error: expected identifier or ‘(’ before ‘/’ token
// salutomondo.c
^

Il secondo statement #include e' una direttiva al preprocessore. La direttiva
#include fa si che il processore cerchi il file indicato e ne sostituisca il
contenuto nella posizione nella quale si trova l'include.

#include

Il preprocessore e' un programma che esegue alcune operazioni di preparazione
prima che il compilatore esegua il suo compito.
Vedremo nel corso del ripasso il ruolo del preprocessore nell'operazione
di building.
Il file stdio.h sta per standard input output header file.
Questo contiene numerosi prototipi e macro che permettono di eseguire l'I/O
in C.

I file che vengono inclusi dalla direttiva al preprocessore #include, come
stdio.h, sono header file. Generalmente la loro estensione e' .h ma non e'
vincolante. Oltre all'header stdio.h la libreria standard ANSI si compone di
diversi header file. Gli header che vengono utilizzati nei programmi dipendono
dalle funzioni delle quali necessitiamo e conseguentemente delle librerie
che intendiamo utilizzare.
Gli header file possono essere specificati nelle direttive include rinchiusi
tra angle bracket o doppi apici.
Quando vengono usati i dopi apici si indica al preprocessore di cercare l'header
nella directory corrente; utilizzando la coppia gli si chiede di cercarli
all'esterno della directory corrente di default /usr/include.

La funzione main() e' una funziona molto speciale per il C; e' il vero e proprio
corpo del programma. Ogni programma in C deve avere una funzione main().
E' possibile mettere la funzione main() in qualsiasi punto del programma,
questa e' sempre la prima funzione eseguita. Il programma termina
quando vengono eseguiti tutti gli statement della funzione main().
L'int che precede la funzione main() specifica il tipo di dato che questa
restituisce al chiamante (lo stesso sistema operativo). Nel nostro caso
restituira' 0 (return 0).
A questo punto viene chiamata la funzione printf() definita' in stdio.h.
Questa funzione stampa un messaggio nello standard output. Il messaggio
e' racchiuso fra le parentesi tonde e i "". La sequenza \n e' una
sequenza di escape e sta per new-line (va a capo o muovi il cursore
all'inizio della riga successiva).
Tutte le funzioni in C possono restituire dei valori. Lo statement
return viene utilizzato per restiture al chiamante valori del tipo specificato
per la funzione nella sua definizione.

Vedremo il dettaglio della struttura dei programmi C nei prossimi ripassi.

int main(void) {
printf("Hello, World!\n");
return 0;
}

Piccola nota: tutte le istruzioni in C terminano con un punto e virgola ;

04. rInGRAZiAMENTI

Vorrei ringraziare anzittutto voi lettori e tutte le persone che mi
stanno vicine, sopportano, supportano e vogliono bene.

Salut, Mundi

Have Fun ;-)

-- abnm3nt4l1