Zoom Icon

Introduzione al linguaggio C

From UIC Archive

Programmazione in C: Introduzione al linguaggio

Contents


Introduzione al linguaggio C
Author: phobos
Email: phobos333-at-email-dot-it
Website:
Date: 15/07/2007 (dd/mm/yyyy)
Level: Working brain required
Language: Italian Flag Italian.gif
Comments:


Un po' di storia

Il linguaggio C fu implementato per la prima volta da Dennis Ritchie su un sistema DEC PDP-11 funzionante con sistema operativo UNIX. Erano gli anni 70... alcuni di noi non erano ancora nati... Perchè studiare un linguaggio nato prima di noi? Ci sono linguaggi che sono nati nello stesso periodo del C e oggi sono considerati inutili ed obsoleti. La risposta a questo quesito è molto semplice: il C è un linguaggio molto potente e usatissimo ancora oggi nella scrittura di programmi che devono funzionare in modo efficiente e veloce. Nel 1983 venne riunito un comitato ANSI con il compito di standardizzare il linguaggio. Il processo di standardizzazione richiese molto più tempo del previsto (sei anni) la versione "definitiva" del linguaggio fu resa disponibile alla fine del 1989. Non a caso lo standard ANSI del C viene anche detto C89. Nel 1999 si riunì nuovamente un comitato ansi per aggiungere al C89 l'Emendamento 1 e alcune nuove funzioni di libreria. Nacque così il C99. Per i più curiosi, possiamo dire che il C99 ha aggiunto numerose funzioni numeriche alle librerie del linguaggio, gli array a lunghezza variabile e il qualificatore restrict per i puntatori. Per il resto sono state aggiunte anche alcune caratteristiche presenti nel C++ come ad esempio i commenti mono riga. Attualmente il C viene considerato un sottoinsieme del C++. Il C++ è un linguaggio ad oggetti, il C no.

Linguaggio di medio livello

Il C è considerato un linguaggio di medio livello. Ciò non vuol dire che sia un linguaggio meno potente o più difficile da utilizzare di un linguaggio ad alto livello come ad esempio il BASIC o il Pascal. Con il livello intendiamo quanto il linguaggio si discosti dal linguaggio macchina, per avvicinarsi al linguaggio umano. Più un linguaggio è di basso livello, più è vicino all'hardware della macchina, maggiore sarà la difficoltà a realizzare operazioni più facilmente realizzabili con un linguaggio di alto livello, ma maggiori saranno le possibilità offerte dal linguaggio sul controllo dell'hardware e sulla velocità e potenza di elaborazione. Proprio per questo motivo, i sistemi operativi sono scritti in linguaggio C.

Il C non è un linguaggio tipizzato

Altra particolarità del linguaggio C è il non essere fortemente tipizzato. Iniziamo con il dire che un "tipo di dato" definisce una gamma di valori che una variabile è in grado di contenere. Esistono numerosi tipi di dati: interi, numeri a virgola mobile, booleani, caratteri, stringhe e così via. Molti linguaggi di programmazione di alto livello hanno notevoli restrizioni sulla tipizzazione dei dati. Il C ha tutti i tipi di dati standard e la possibilità di definirne di "tipo utente". Il "passaggio" o la conversione da un tipo ad un altro può avvenire seguendo delle semplici regole e con la massima elasticità.

Il C non implementa controlli a Runtime

Altra particolarità del linguaggio C è l'assenza di controllo errore a runtime. Alcuni linguaggi, specie quelli di ultima generazione, implementano dei controlli durante l'esecuzione del programma, per evitare che dati o costrutti mal congegnati possano generare errori. Ad esempio, nel linguaggio Java esiste un controllo per lo sforamento dei limiti di memorizzazione negli array. Nel C non esiste nessun controllo di questo tipo, tutto è demandato al programmatore che deve curarsi di "preventivare" tutte le casistiche a cui un dato o un costrutto possa andare incontro. Nel caso specifico, uno sforamento di indice di array provocherà nella migliore delle ipotesi una sovrascrittura di un dato memorizzato nella RAM immediatamente dopo l'area di memoria destinata all'array, nel peggiore dei casi si potrà verificare un blocco del programma, o peggio del sistema. Piccola nota per i curiosi: l'assenza "da linguaggio" di meccanismi di protezione che impediscano errori di questo tipo, viene sfruttata nell'hacking con le tecniche di "buffer overrun" per guadagnare il tanto agognato accesso root ad un sistema.

Il C è un linguaggio strutturato

La caratteristica fondamentale di un linguaggio strutturato è quella di eseguire il codice del programma in maniera sequenziale, senza "salti" da una parte all'altra. Un esempio di linguaggio non strutturato è il BASIC, che con l'istruzione GOTO permetteva di risolvere facilmente problemi risultanti da analisi poco attente, ma che produceva codice la cui manutenzione diveniva pressochè impossibile anche da parte di chi aveva scritto il programma pochi giorni prima. Il C è un linguaggio strutturato, ogni serie di operazioni, viene eseguita sequenzialmente; non esistono salti, se non chiamate alle soubroutine o alle funzioni che possono svolgere dei compiti specifici e ripetitivi. Questa, assieme all'isolamento dei dati, che definiremo tra qualche istante, sono le due caratteristiche che rendono il C un linguaggio molto amato dai programmatori professionisti. L'isolamento dei dati è la capacità di un programma di utilizzare dei dati all'interno di funzioni o subroutines senza che questi vadano ad interferire con eventuali stessi dati definiti al di fuori di queste ultime. Questa caratteristica, permette di operare sui dati con un buon livello di sicurezza degli stessi, senza correre il rischio di alterare erroneamente qualche dato "entrando" in una subroutine.

Il linguaggio C non è per tutti

Contrariamente a quanto si possa credere, non tutti i linguaggi di programmazione possono risultare comodi ad un programmatore. Ad esempio, linguaggi come il COBOL o il BASIC furono creati per permettere ai programmatori un'agevole lettura e manutenzione del codice (nel primo caso) o la soluzione veloce di semplici problematiche e l'aspetto didattico del linguaggio (nel secondo). Il C, al contrario, è stato creato da programmatori professionisti e quindi "soffre" della forma mentis dei programmatori esperti. Poche restrizioni, controllo pressochè completo dell'hardware e velocità di esecuzione, fanno del linguaggio C quanto di più simile ci sia al linguaggio macchina, senza le complicazioni dell'assembler vero e proprio. Non a caso, come già detto in precedenza, il linguaggio C veniva inizialmente utilizzato per la scrittura di "Software di sistema": Sistemi operativi, interpreti, editor, compilatori, drivers, eccetera. Come descritto poco sopra, l'assenza di controllo d'errore a runtime, l'impossibilità (almeno per la versione C89) di utilizzare strutture dati "dinamiche", quali ad esempio gli array a dimensione variabile, lo rendono un linguaggio che necessita di un'attenta analisi prima di iniziare a "buttare giù codice". Comunque, tutto ciò non scoraggia le migliaia di persone che quotidianamente programmano con questo linguaggio e che fanno sì che continui, dopo quasi vent'anni dalla sua nascita ad essere uno dei linguaggi di programmazione più utilizzato al mondo.

Anatomia generale di un programma C

Vediamo un esempio di come è strutturato generalmente il listato di un programma scritto in C

/* commenti */ /* sezione di include per le librerie */ #include <librerie.h> #include <altre_librerie.h>

/* definizione di costanti e-o macro */ #define COSTANTE1 valore #define MACRO1 istruzioni

/* dichiarazioni di variabili globali e-o dati strutturati */ tipo1 variabile1 tipo2 variabile2 ... tipoN variabileN

struct { tipo1 var1; tipo2 var2; ... tipoM varM; } nome_struct1; ...

/* definizione dei prototipi delle funzioni utilizzate */ tipo_ritornato_da_func1 func1(tipo_parametro1, tipo_parametro2, ..., tipo_parametroN) tipo_ritornato_da_func2 func2(tipo_parametro1, tipo_parametro2, ..., tipo_parametroM) ...

/* sezione "main" del programma */ int main(void) { /* variabili "locali" */ tipo1 variabileA; tipo2 variabileB; ... /* codice eseguibile */ istruzioni; istruzioni; { /* variabili definite fino alla chiusura del blocco */ tipoX variabileX ... istruzioni; istruzioni; ... } ... istruzioni; ... return 0; }

/* codice eseguibile delle funzioni */ tipo_ritornato_da_func1 func1(tipo_parametro1, tipo_parametro2, ..., tipo_parametroN) { /* dichiarazione variabili locali alla funzione */ tipoK variabileK; ... istruzioni; ... return var1; }

Questo listato è ovviamente scritto in italiano, i vari blocchi che lo costituiscono saranno oggetto di analisi nei capitoli successivi.

Vediamo un esempio concreto:

#include <stdio.h>

#define CLS system("clear")

int main(void) { char stringa[20];

CLS; printf("\n%s\n", "Inserisci la stringa (max. 20 caratteri)"); scanf("%s", stringa);

stampa(stringa);

return 0; }

void stampa(char str[20]) { printf("\n"); printf("\nLa stringa inserita è:"); printf("\n%s", str); printf("\n");

return; }

Molto velocemente, sempre che a questo punto qualcuno non si sia spaventato e sia scappato urlando, commentiamo le varie istruzioni di questo programma.

Le prime due righe includono nel sorgente le librerie per le funzioni "base" di I/O, e definiscono una macro che serve a cancellare lo schermo

NOTA: Con la funzione system() su sistemi unix si utilizza il comando clear il quale non è definito in sistemi Windows. Al suo posto, su sistemi di quest'ultimo tipo, si può utilizzare il comando cls.

La riga void stampa(char *) è la dichiarazione del prototipo della funzione utilizzata nel programma. Il tipo di dato void è una particolarità del linguaggio C, si utilizza per dichiarare un "non valore". Lo conosceremo meglio più avanti.

printf e scanf sono due funzioni (definite nella libreria stdio.h) che servono rispettivamente all'output ed all'input dei dati. Utilizzano dei "segnaposto" (in questo caso il %s) e dei codici per la formattazione delle stringhe (\n che indica il "newline") che analizzeremo in seguito.

stampa(stringa) è la chiamata alla funzione (la nostra subroutine) che si occuperà di stampare a video la stringa immessa dall'utente. Alla funzione viene "passato" un parametro, nel nostro caso, la stringa da stampare.

L'istruzione return 0 restituisce il controllo al sistema operativo, comunicando il codice d'uscita 0, che normalmente sta a significare che il programma è terminato correttamente.

Ciò che succede all'interno della funzione stampa è facilmente intuibile. Il controllo del programma, dopo la chiamata alla funzione, passa al codice in essa contenuto. Vengono stampate a video tutte le informazioni che sono contenute nelle istruzioni printf e l'istruzione return restituisce il controllo al programma chiamante, sull'istruzione immediatamente successiva a quella di chiamata alla funzione (nel nostro caso return 0;).

Avremo modo di capire meglio ogni singola istruzione e costrutto contenuto in questo semplice programma dopo aver acquisito i concetti che saranno sviluppati nelle lezioni successive. Lo scopo di questo programmino era solo quello di mostrare brevemente la struttura di un programma C.


Bibliografia

  • Herbert Schildt "C++ La Guida Completa 4/ed", McGraw Hill Informatica


Note finali

Ringrazio tutti coloro che vorranno contribuire a questa guida segnalandomi eventuali errori e/o possibili migliorie. Per questa prima lezione è tutto... alla prossima puntata.

phobos


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.