introduzione

ambiente di sviluppo ed esecuzione

Le fasi di sviluppo di un programma C sono quattro (ognuna svolta da un programma diverso):

  1. il programmatore scrive un programma in un editor di testo e lo salva su disco
  2. il pre-processore processa il codice
  3. il compilatore compila il codice producendo un file oggetto, e lo salva su disco
  4. il linker collega il file oggetto alle librerie, e crea un file eseguibile

Il programma gcc (Gnu Compiler Collection) è in grado di svolgere tutte le fasi necessarie alla creazione di un file eseguibile.

Le fasi di esecuzione sono invece: 5) il loader preleva il programma dal disco e lo carica in memoria principale 6) la CPU prende ogni istruzione e la esegue, salvando in memoria,se necessario, nuovi dati durante l’esecuzione

il linguaggio C

  • è un linguaggio di alto livello: un programma è un insieme di istruzioni

struttura di un programma C

In C è obbligatoria la presenza di una main function.

  • main function e functions possono anche risiedere in diversi file .c

Ogni funzione è formata da un’intestazione (header), a sua volta composta da nome della funzione, tipo di valore ritornato e da una lista di parametri in input, e da un basic block (blocco di istruzioni) :

<return-type> function-name (parameter-list){
	instruction 1;
	instruction 2;
	...
	return ...
}
  • ogni statement è terminato da un ;
  • una funzione può avere un valore di ritorno, che viene impostato dalla keyword return, e viene ritornato al chiamante

programma basilare

#include <stdio.h>
 
int main(){
	printf("scemo chi legge \n");
	return 0;
}

direttive al preprocessore: file header

  • con # si indicano le direttive al preprocessore
  • stdio.h è un file header (.h) che contiene costanti, funzioni per input, output e gestione dei file
  • <> indicano che il file header è un file standard del C in /usr/include
  • "" indicano che il file header è dell’utente e si trova nella directory corrente o in un path specificato
  • -I permette di specificare le directory in cui cercare gli header file

compilare ed eseguire

precompilazione, compilazione, linking

Per eseguire solo la precompilazione:

cpp helloworld.c > precompilato.c
  • esegue tutte le direttive del compilatore ed elimina i commenti

Per eseguire solo la compilazione (di un precompilato):

gcc -c precompilato.c -o file.o
  • gcc controlla la sintassi
  • per ogni chiamata a funzione, controlla che venga rispettato l’header
  • crea del codice macchina solo per il contenuto delle funzioni

-o file.o serve a specificare il nome dell'output

Per eseguire solo precompilazione + compilazione:

gcc -c file.c -o file.o

Per eseguire solo linking:

gcc file.o
  • risolve tutte le chiamate a funzione (per ogni funzione ci deve essere anche un’implementazione data dal programmatore o da librerie di sistema)
  • l’inclusione delle librerie può essere automatica o specificata dall’utente

Linking di più file:

gcc file1.o file2.o file3.c
  • si possono mischiare file .c e .o

Per fare precompilazione, compilazione e linking:

gcc file1.c ... filen.c

flag

gcc -Wall prog-name.c
  • il flag -Wall fa sì che vengano stampati tutti i messaggi di warning (se presenti)
gcc -Wall prog-name.c -lm
  • il flag -lm va specificato se si includono le librerie matematiche <math.h> (per usare funzioni come sin, cos, log, ln, ecc.)

variabili

  • in C è necessario dichiarare nome e tipo di una variabile prima di poterla utilizzare

dichiarazione di variabili e costanti

Per dichiarare una variabile (o una serie di variabili) si usa la seguente sintassi:

optional_modifier data_type name_list;
  • optional_modifier indica dei modificatori applicati al tipo di dato (come signed, unsigned, const)
  • data_type indica il tipo di valore
  • name_list è una lista di nomi delle variabili che si vogliono dichiarare

Le variabili globali si dichiarano fuori dalle funzioni, i parametri nell’header e le variabili locali all’interno del blocco di codice di una funzione.

L’assegnazione di un valore ad una variabile può essere fatta:

  • in fase di dichiarazione: int x = 3;
  • in un momento successivo del codice
    • è possibile fare assegnazioni del tipo a = b = 0;
  • leggendo un valore in input, per esempio con scanf

numeri

tipi di dato per i numeri

center

intervalli di valori:

TypeStorage sizeValue range
char1 byte-128 a 127 o 0 a 255
unsigned char1 byte0 a 255
signed char1 byte-128 a 127
int2 o 4 bytes-32.768 to 32.767 o -2.147.483.648 a 2.147.483.647
unsigned int2 o 4 bytes0 a 65.535 o 0 a 4.294.967.295
short2 bytes-32.768 a 32.767
unsigned short2 bytes0 a 65.535
long8 bytes-9223372036854775808 a
9223372036854775807
unsigned long8 bytes0 a 18446744073709551615
TypeStorage sizeValue rangePrecision
float4 bytes1.2E-38 a
3.4E+38
6 posizioni decimali
double8 bytes2.3E-308 to
1.7E+308
15 posizioni decimali
long double10 bytes3.4E-4932 to
1.1E+4932
19 posizioni decimali
  • la dimensione di un tipo di dato può essere ottenuta anche con sizeof(datatype)
  • l tipo char può essere assegnato un carattere ASCII attraverso le single quote: al posto dichar c = 97;, si può scrivere char c = 'a';

booleani

Esistono due tipi di dati per rappresentare valori booleani:

  • _Bool può memorizzare solo 0 e 1
    • qualsiasi valore diverso da 0 verrà memorizzato come 1
  • bool memorizza true e false
    • richiede l’uso di <stdbool.h>

In ogni espressione logica, 0 significa false e 1 o diverso da zero significa true.

input e output

Quando un programma viene eseguito, l’ambiente run-time del C apre due file: stdin e stdout.

  • (tutte le funzioni principali per l’I/O sono nel file stdio.h)

output

Con printf, possiamo scrivere su stdout il valore di una variabile

printf("format_string", value-list);
  • format_string deve contenere dei placeholder; ogni placeholder inizia con % e serve a specificare il tipo di dato della variabile che si troverà al suo posto
  • value_list può contenere sequenze di caratteri, variabili, costanti ed espressioni logico-matematiche
  • printf riceve valori, ma C permette di manipolare anche indirizzi di memoria e passarli come input a funzioni (anche se, per stampare il contenuto di una locazione di memoria, si usa scanf)
  • printf restituisce il numero di caratteri stampati

placeholder comuni

  • %d o %i per integer, %l per long
  • %o per integers in ottale, %x per integers in esadecimale
  • %f, %e, %g per float (f - formato standard, e - notazione scientifica, g - sceglie automaticamente il formato migliore tra f ed e)
  • %lf per double

formato completo di un placeholder: %[parameter][flags][width][.precision][length]type (per saperne di più)

Possiamo controllare la spaziatura orizzontale e verticale della printf usando sequenze di escape \

Escape SequenceNameDescription
\aAlarm or BeepIt is used to generate a bell sound in the C program.
\bBackspaceIt is used to move the cursor one place backward.
\fForm FeedIt is used to move the cursor to the start of the next logical page.
\nNew LineIt moves the cursor to the start of the next line.
\rCarriage ReturnIt moves the cursor to the start of the current line.
\tHorizontal TabIt inserts some whitespace to the left of the cursor and moves the cursor accordingly.
\vVertical TabIt is used to insert vertical space.
\\BacklashUse to insert backslash character.
\’Single QuoteIt is used to display a single quotation mark.
\”Double QuoteIt is used to display double quotation marks.
\?Question MarkIt is used to display a question mark.
\oooOctal NumberIt is used to represent an octal number.
\xhhHexadecimal NumberIt represents the hexadecimal number.
\0NULLIt represents the NULL character.

input

Per prendere input da terminale, si usa la funzione scanf. La sua sintassi è:

scanf(format-string, address-list)
  • format-string contiene placeholder che comunicano a scanf il tipo di dato in cui la stringa in input viene convertita
  • address-list contiene gli indirizzi di memoria in cui devono essere memorizzati i valori in input

esempio

scanf("%d", &peso);
  • peso è una variabile intera - & estrae il suo indirizzo di memoria e lo passa a scanf

scanf restituisce il numero di valori letti in input.

operatori

  • aritmetici: +, -, *, /, %
  • relazionali: ==, !=, <, <=, >, >=
  • logici: !, &&, ||
  • bitwise: &, |, ~, ^
  • shift: <<, >>

La precedenza degli operatori segue il PEMDAS (Parentheses, Exponents, Multiplication and Division, Addition and Substraction).

  • C permette di abbreviare gli assegnamenti: d = d-4 è equivalente a d -= 4
  • esistono anche:
    • il post-incremento: x++ (usa prima il valore di x e poi lo incrementa)
    • il pre-incremento: ++x (incrementa il valore di x prima di usarlo)