Skip to content

Programmazione in C

Questo tutorial copre gli elementi fondamentali del linguaggio C, dalla scrittura del primo programma fino alla gestione dinamica della memoria con liste concatenate.

  • Un editor di testo (VS Code, Notepad++, ecc.)
  • Un compilatore C (gcc su Linux, MinGW/gcc su Windows)
  • Terminale / Prompt dei comandi

Su Linux il compilatore gcc è spesso preinstallato (altrimenti sudo apt install gcc). Su Windows invece va installato manualmente. La scelta più comune è MinGW-w64.

  1. Vai su MinGW-w64 oppure scarica direttamente MSYS2 (il metodo più semplice):

    • Scarica MSYS2 da msys2.org
    • Esegui l’installer e segui le istruzioni
  2. Apri MSYS2 UCRT64 dal menu Start e aggiorna i pacchetti:

    Terminal window
    pacman -Syu
  3. Installa il compilatore GCC e gli strumenti di build:

    Terminal window
    pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-gdb make
  4. Aggiungi MinGW al PATH di Windows:

    • Cerca “Variabili d’ambiente” nel menu Start
    • Clicca su Variabili d’ambiente…
    • In Variabili di sistema, seleziona Path e clicca Modifica…
    • Aggiungi: C:\msys64\ucrt64\bin
    • Clicca OK e riavvia il terminale
  5. Verifica l’installazione:

    Terminal window
    gcc --version
    gdb --version

In breve: MinGW-w64 standalone (alternativa)

Section titled “In breve: MinGW-w64 standalone (alternativa)”

Se non vuoi usare MSYS2, puoi scaricare il pacchetto standalone MinGW-w64 da mingw-w64.org e aggiungere bin/ al PATH.


Apri VS Code e installa queste estensioni:

EstensioneAutoreRuolo
C/C++MicrosoftIntelliSense, debug, syntax highlighting
Code RunnerJun HanEsegue velocemente il file corrente

Dopo aver installato Code Runner, puoi eseguire qualsiasi file .c con:

  • Tasto destroRun Code (oppure Ctrl+Alt+N)
  • L’output appare nel pannello OUTPUT

Code Runner usa automaticamente gcc se presente nel PATH.

Per eseguire il debug passo-passo:

  1. Apri la cartella del tuo progetto in VS Code
  2. Crea un file programma.c con del codice C
  3. Vai al pannello Run and Debug (icona ▶️ a sinistra)
  4. Clicca create a launch.json file e seleziona C++ (GDB/LLDB)
  5. VS Code creerà automaticamente un .vscode/launch.json simile a:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug C",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/programma.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "gdb.exe",
"preLaunchTask": "gcc build"
}
]
}
  1. Crea anche .vscode/tasks.json per compilare automaticamente prima del debug:
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "gcc build",
"command": "gcc",
"args": [
"-g",
"-o",
"${workspaceFolder}/programma.exe",
"${workspaceFolder}/programma.c"
],
"group": "build",
"problemMatcher": ["$gcc"]
}
]
}

Ora premi F5 per avviare il debug: VS Code compilerà automaticamente il file e lo eseguirà con GDB.

4. Compilare manualmente dal terminale integrato

Section titled “4. Compilare manualmente dal terminale integrato”

VS Code ha un terminale integrato (Ctrl+ò o View → Terminal). Puoi usarlo per compilare manualmente:

Terminal window
gcc -g -o programma.exe programma.c
.\programma.exe

#include <stdio.h> // Input/output library
int main() {
printf("Hello, World!\n");
return 0;
}

Ogni programma C ha:

  • #include <stdio.h> — direttiva per includere la libreria standard di I/O
  • int main() — funzione principale, punto di ingresso del programma
  • return 0; — indica che il programma è terminato correttamente

1. Scrivi il codice in un file programma.c

2. Compila con gcc:

Terminal window
gcc programma.c -o programma

3. Esegui:

Terminal window
./programma

Opzioni utili:

Terminal window
gcc -Wall -Wextra -o programma programma.c # Con tutti i warning
gcc -g -o programma programma.c # Con info di debug (per gdb)

int main() {
// Tipi di base
int a = 10; // Intero (32 bit)
float b = 3.14f; // Virgola mobile (32 bit)
double c = 3.1415926535; // Virgola mobile (64 bit)
char d = 'A'; // Carattere (8 bit)
// Modificatori
unsigned int e = 42; // Solo valori positivi
long int f = 100000L; // Intero lungo
short int g = 5; // Intero corto
// Costante
const int MAX = 100;
printf("int: %d, float: %.2f, double: %.2f, char: %c\n", a, b, c, d);
return 0;
}
SpecificatoreTipo
%d / %iint
%ffloat / double
%cchar
%sstringa (char*)
%ppuntatore
%xesadecimale
%luunsigned long

#include <stdio.h>
int main() {
// Dichiarazione e inizializzazione
int numeri[5] = {10, 20, 30, 40, 50};
// Oppure senza specificare la dimensione
int voti[] = {8, 7, 9, 6, 10};
// Accesso agli elementi
for (int i = 0; i < 5; i++) {
printf("numeri[%d] = %d\n", i, numeri[i]);
}
// Calcolare la dimensione di un array
int size = sizeof(voti) / sizeof(voti[0]);
printf("L'array ha %d elementi\n", size);
return 0;
}
#include <stdio.h>
int main() {
// Matrice 3x3
int matrice[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Stampa a matrice
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrice[i][j]);
}
printf("\n");
}
return 0;
}

#include <stdio.h>
// Prototipi (dichiarazione)
int somma(int a, int b);
void stampaMessaggio(char messaggio[]);
int main() {
int risultato = somma(5, 3);
printf("Risultato: %d\n", risultato);
stampaMessaggio("Ciao mondo!");
return 0;
}
// Definizione
int somma(int a, int b) {
return a + b;
}
void stampaMessaggio(char messaggio[]) {
printf("%s\n", messaggio);
}
#include <stdio.h>
// Passaggio per valore (copia)
void raddoppiaValore(int x) {
x = x * 2; // Modifica solo la copia locale
}
// Passaggio per riferimento (puntatore)
void raddoppiaRiferimento(int *x) {
*x = *x * 2; // Modifica il valore originale
}
int main() {
int n = 5;
raddoppiaValore(n);
printf("Dopo valore: %d\n", n); // 5 (invariato)
raddoppiaRiferimento(&n);
printf("Dopo riferimento: %d\n", n); // 10 (modificato)
return 0;
}
#include <stdio.h>
// Un array decade sempre a puntatore
void stampaArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
void modificaArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // Modifica l'array originale
}
}
int main() {
int numeri[] = {1, 2, 3, 4, 5};
int size = sizeof(numeri) / sizeof(numeri[0]);
stampaArray(numeri, size);
modificaArray(numeri, size);
stampaArray(numeri, size); // {2, 4, 6, 8, 10}
return 0;
}

#include <stdio.h>
int main() {
int x = 42;
int *ptr; // Dichiarazione puntatore a int
ptr = &x; // ptr contiene l'indirizzo di x
printf("Valore di x: %d\n", x);
printf("Indirizzo di x: %p\n", (void*)&x);
printf("Valore di ptr: %p\n", (void*)ptr);
printf("Dereferenza di ptr: %d\n", *ptr);
// Modifica tramite puntatore
*ptr = 100;
printf("Nuovo valore di x: %d\n", x); // 100
return 0;
}
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // Equivalenti: p = &arr[0]
// Aritmetica dei puntatori
printf("Primo elemento: %d\n", *p); // 10
printf("Secondo elemento: %d\n", *(p+1)); // 20
printf("Terzo elemento: %d\n", *(p+2)); // 30
// Scorrere un array con puntatori
for (int *q = arr; q < arr + 5; q++) {
printf("%d ", *q);
}
printf("\n");
return 0;
}
#include <stdio.h>
int somma(int a, int b) { return a + b; }
int differenza(int a, int b) { return a - b; }
int prodotto(int a, int b) { return a * b; }
int main() {
// Dichiarazione puntatore a funzione
int (*operazione)(int, int);
operazione = somma;
printf("Somma: %d\n", operazione(10, 5));
operazione = differenza;
printf("Differenza: %d\n", operazione(10, 5));
operazione = prodotto;
printf("Prodotto: %d\n", operazione(10, 5));
return 0;
}

In C le stringhe sono array di char terminati da \0 (null terminator).

#include <stdio.h>
#include <string.h> // Per funzioni sulle stringhe
int main() {
// Dichiarazione
char s1[] = "Ciao"; // Array automatico
char s2[20] = "Mondo"; // Con dimensione esplicita
char *s3 = "Hello"; // Stringa constante (non modificabile!)
// Copia e concatenazione
char buffer[50];
strcpy(buffer, s1); // Copia "Ciao" in buffer
strcat(buffer, " "); // Concatena spazio
strcat(buffer, s2); // Concatena "Mondo"
printf("%s\n", buffer); // "Ciao Mondo"
// Lunghezza
printf("Lunghezza: %zu\n", strlen(buffer));
// Confronto
if (strcmp(s1, "Ciao") == 0) {
printf("Le stringhe sono uguali\n");
}
// Input da tastiera
char nome[50];
printf("Inserisci il nome: ");
fgets(nome, sizeof(nome), stdin);
nome[strcspn(nome, "\n")] = '\0'; // Rimuove newline
printf("Ciao, %s!\n", nome);
return 0;
}
FunzioneDescrizione
strlen(s)Restituisce la lunghezza (escluso il carattere NUL)
strcpy(dest, src)Copia src in dest
strcat(dest, src)Concatena src in fondo a dest
strcmp(s1, s2)Confronta: 0 = uguali, <0 se s1 < s2, >0 se s1 > s2
strchr(s, c)Trova la prima occorrenza di c in s
strstr(s1, s2)Trova la prima occorrenza di s2 in s1

#include <stdio.h>
#include <string.h>
// Definizione di una struttura
struct Studente {
char nome[50];
char cognome[50];
int matricola;
float media;
};
int main() {
// Inizializzazione
struct Studente s1 = {"Mario", "Rossi", 12345, 27.5};
// Accesso ai campi
printf("Nome: %s\n", s1.nome);
printf("Cognome: %s\n", s1.cognome);
printf("Matricola: %d\n", s1.matricola);
printf("Media: %.2f\n", s1.media);
// Modifica
s1.media = 28.0;
// Array di strutture
struct Studente classe[3] = {
{"Mario", "Rossi", 12345, 27.5},
{"Anna", "Bianchi", 12346, 30.0},
{"Luca", "Verdi", 12347, 24.0}
};
for (int i = 0; i < 3; i++) {
printf("%s %s: %.2f\n", classe[i].nome, classe[i].cognome, classe[i].media);
}
return 0;
}
#include <stdio.h>
#include <string.h>
struct Punto {
int x;
int y;
};
void sposta(struct Punto *p, int dx, int dy) {
p->x += dx; // Equivalente a (*p).x += dx
p->y += dy;
}
int main() {
struct Punto p1 = {10, 20};
struct Punto *ptr = &p1;
printf("(%d, %d)\n", ptr->x, ptr->y);
sposta(ptr, 5, -3);
printf("Dopo spostamento: (%d, %d)\n", p1.x, p1.y);
return 0;
}
#include <stdio.h>
// typedef crea un alias per il tipo
typedef struct {
char nome[50];
int eta;
} Persona;
int main() {
Persona p1 = {"Alice", 25}; // Non serve più "struct"
printf("%s ha %d anni\n", p1.nome, p1.eta);
return 0;
}

Le funzioni di allocazione dinamica sono definite in <stdlib.h>.

FunzioneDescrizione
malloc(size)Alloca size byte, restituisce un puntatore
calloc(n, size)Alloca n elementi di size byte, inizializzati a 0
realloc(ptr, new_size)Ridimensiona un blocco allocato
free(ptr)Libera la memoria allocata
#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
printf("Quanti numeri vuoi inserire? ");
scanf("%d", &n);
// Allocazione dinamica di un array
int *numeri = (int*)malloc(n * sizeof(int));
// Controllo se l'allocazione è riuscita
if (numeri == NULL) {
printf("Errore di allocazione!\n");
return 1;
}
// Inserimento dati
for (int i = 0; i < n; i++) {
printf("Inserisci numero %d: ", i + 1);
scanf("%d", &numeri[i]);
}
// Stampa
printf("Numeri inseriti: ");
for (int i = 0; i < n; i++) {
printf("%d ", numeri[i]);
}
printf("\n");
// Liberare la memoria
free(numeri);
return 0;
}
// malloc: memoria non inizializzata (può contenere "spazzatura")
int *arr1 = (int*)malloc(5 * sizeof(int));
// calloc: memoria inizializzata a zero
int *arr2 = (int*)calloc(5, sizeof(int));
if (arr2 != NULL) {
for (int i = 0; i < 5; i++) {
printf("%d ", arr2[i]); // 0 0 0 0 0
}
free(arr2);
}
#include <stdio.h>
#include <stdlib.h>
int main() {
int *numeri = (int*)malloc(3 * sizeof(int));
numeri[0] = 10;
numeri[1] = 20;
numeri[2] = 30;
// Riallocazione per array più grande
int *temp = (int*)realloc(numeri, 5 * sizeof(int));
if (temp == NULL) {
printf("Errore realloc!\n");
free(numeri);
return 1;
}
numeri = temp;
numeri[3] = 40;
numeri[4] = 50;
for (int i = 0; i < 5; i++) {
printf("%d ", numeri[i]);
}
printf("\n");
free(numeri);
return 0;
}

Una lista concatenata è una struttura dati dinamica composta da nodi collegati tramite puntatori.

typedef struct Nodo {
int dato;
struct Nodo *next; // Puntatore al nodo successivo
} Nodo;
#include <stdio.h>
#include <stdlib.h>
typedef struct Nodo {
int dato;
struct Nodo *next;
} Nodo;
// Inserimento in testa
Nodo* inserisciTesta(Nodo *testa, int valore) {
Nodo *nuovo = (Nodo*)malloc(sizeof(Nodo));
if (nuovo == NULL) {
printf("Errore allocazione!\n");
return testa;
}
nuovo->dato = valore;
nuovo->next = testa;
return nuovo;
}
// Inserimento in coda
Nodo* inserisciCoda(Nodo *testa, int valore) {
Nodo *nuovo = (Nodo*)malloc(sizeof(Nodo));
if (nuovo == NULL) {
printf("Errore allocazione!\n");
return testa;
}
nuovo->dato = valore;
nuovo->next = NULL;
if (testa == NULL) {
return nuovo; // Lista vuota
}
Nodo *corrente = testa;
while (corrente->next != NULL) {
corrente = corrente->next;
}
corrente->next = nuovo;
return testa;
}
// Rimuovi un valore
Nodo* rimuovi(Nodo *testa, int valore) {
Nodo *corrente = testa;
Nodo *precedente = NULL;
while (corrente != NULL && corrente->dato != valore) {
precedente = corrente;
corrente = corrente->next;
}
if (corrente == NULL) return testa; // Non trovato
if (precedente == NULL) {
testa = corrente->next; // Rimozione in testa
} else {
precedente->next = corrente->next;
}
free(corrente);
return testa;
}
// Stampa la lista
void stampaLista(Nodo *testa) {
Nodo *corrente = testa;
while (corrente != NULL) {
printf("%d -> ", corrente->dato);
corrente = corrente->next;
}
printf("NULL\n");
}
// Libera tutta la memoria
void liberaLista(Nodo *testa) {
Nodo *corrente = testa;
while (corrente != NULL) {
Nodo *prossimo = corrente->next;
free(corrente);
corrente = prossimo;
}
}
// Cerca un elemento
int cerca(Nodo *testa, int valore) {
Nodo *corrente = testa;
int pos = 0;
while (corrente != NULL) {
if (corrente->dato == valore) return pos;
corrente = corrente->next;
pos++;
}
return -1; // Non trovato
}
int main() {
Nodo *lista = NULL;
lista = inserisciTesta(lista, 30);
lista = inserisciTesta(lista, 20);
lista = inserisciTesta(lista, 10);
stampaLista(lista); // 10 -> 20 -> 30 -> NULL
lista = inserisciCoda(lista, 40);
lista = inserisciCoda(lista, 50);
stampaLista(lista); // 10 -> 20 -> 30 -> 40 -> 50 -> NULL
lista = rimuovi(lista, 30);
stampaLista(lista); // 10 -> 20 -> 40 -> 50 -> NULL
int pos = cerca(lista, 40);
printf("Valore 40 trovato alla posizione: %d\n", pos);
liberaLista(lista);
return 0;
}

Terminal window
gcc -g -o programma programma.c
Terminal window
gdb ./programma

Comandi GDB essenziali:

ComandoDescrizione
break mainImposta breakpoint su main
break 10Breakpoint alla riga 10
runAvvia il programma
next (o n)Esegue riga successiva (senza entrare nelle funzioni)
step (o s)Entra nella funzione chiamata
print xStampa il valore di x
continue (o c)Continua fino al prossimo breakpoint
quitEsci da gdb
Terminal window
gcc -g -o programma programma.c
valgrind --leak-check=full ./programma
  1. Apri il file .c in Visual Studio
  2. Premi F9 per impostare un breakpoint
  3. Premi F5 per avviare il debug
  4. Usa F10 per eseguire riga dopo riga
  5. Usa F11 per entrare nelle funzioni

┌─────────────────────────────────────────┐
│ PROGRAMMA C │
├─────────────────────────────────────────┤
│ #include <stdio.h> │
│ │
│ int main() { │
│ ┌─ Variabili e tipi │
│ ├─ Array e matrici │
│ ├─ Puntatori │
│ ├─ Stringhe (char[]) │
│ ├─ Strutture (struct) │
│ ├─ Funzioni (parametri, return) │
│ └─ Memoria dinamica (malloc/free) │
│ } │
├─────────────────────────────────────────┤
│ Compilazione: gcc -o prog prog.c │
│ Esecuzione: ./prog (Linux) │
│ prog.exe (Windows) │
│ Debug: gdb ./prog │
│ Memory check: valgrind ./prog │
└─────────────────────────────────────────┘

  1. Array e funzioni: Scrivi una funzione che accetta un array e la sua dimensione e restituisce il valore massimo.

  2. Stringhe: Scrivi una funzione che conta quante vocali ci sono in una stringa.

  3. Strutture: Definisci una struttura Rettangolo con base e altezza, poi scrivi una funzione che calcola l’area.

  4. Memoria dinamica: Scrivi un programma che chiede all’utente quanti numeri vuole inserire, li legge, li memorizza in un array allocato dinamicamente, e poi li stampa in ordine inverso.

  5. Lista concatenata: Aggiungi alla lista dell’esempio una funzione che conta il numero di elementi.