Algoritmi e Linguaggi di Programmazione – dal problema al codice macchina
Cosa significa davvero scrivere un programma? Partiamo dalla logica pura dell'algoritmo fino al bit eseguito dalla CPU, per capire perché l'Assembly 8086 è il ponte perfetto tra software e hardware.
1. Algoritmo e programma — due concetti distinti
Prima di scrivere qualsiasi codice è fondamentale distinguere due concetti che spesso vengono confusi dagli studenti al primo approccio.
Algoritmo
Una sequenza finita, non ambigua e ordinata di istruzioni per risolvere un problema — indipendente dal linguaggio e dall'hardware. Descrive il cosa fare.
Programma
L'implementazione dell'algoritmo in un linguaggio eseguibile — dipende dalla piattaforma, dal compilatore e dall'architettura. Descrive il come farlo in un contesto specifico.
Algoritmo = idea risolutiva astratta. Programma = traduzione concreta di quell'idea in un linguaggio che una macchina (reale o virtuale) può eseguire.
Esempio — somma di due numeri
Lo stesso problema espresso prima come algoritmo in pseudocodice, poi come programma Assembly 8086. Nota come la logica non cambi, ma le conoscenze richieste sì.
All'interrogazione non dire “l'algoritmo è il programma”. L'algoritmo sta nella testa, il programma sta nel file. Se cambi CPU, il programma Assembly va riscritto, l'algoritmo no. Questa frase ti salva il 30.
Pensa alla ricetta della carbonara: la sequenza “cuoci pasta, rosola guanciale, mescola uovo...” è l'algoritmo. Scriverla in italiano, in inglese o in simboli per un robot da cucina sono tre programmi diversi della stessa ricetta.
2. Classificazione dei linguaggi di programmazione
I linguaggi si classificano in base al livello di astrazione rispetto all'hardware. Più il linguaggio è ad alto livello, più si avvicina al linguaggio naturale e si allontana dalla macchina — e viceversa.
Python, C, Java, JS
Indipendente dall'hardware, vicino al linguaggio naturale. Concentrato sulla logica.
Assembly x86, ARM ASM
Dipendente dall'architettura, accesso diretto a registri e memoria.
Binario puro · 10110000
Unico eseguito direttamente dalla CPU. Bit puri, nessun simbolo.
3. Linguaggi di alto livello
I linguaggi di alto livello sono indipendenti dall'hardware e più vicini al modo in cui gli esseri umani ragionano. Il programmatore si concentra sulla logica del problema senza preoccuparsi di registri, indirizzi o dimensioni dei dati. Un compilatore o interprete penserà alla traduzione.
Perché si usano
✓ Codice leggibile e manutenibile
✓ Portabile su architetture diverse
✓ Sviluppo più rapido, meno bug di basso livello
Cosa non puoi fare
✗ Nessun controllo diretto sull'hardware
✗ Overhead introdotto dalla traduzione
✗ Meno efficiente in scenari real-time o embedded
Quando configuri una periferica o gestisci un interrupt dell'8086, Python non ti permette di dire “metti questo bit nel registro DX”. Devi scendere di livello.
4. Linguaggi di basso livello — Assembly
I linguaggi di basso livello sono strettamente dipendenti dall'architettura hardware. Il programmatore interagisce direttamente con registri, indirizzi di memoria e dimensioni dei dati — nulla è gestito automaticamente. È qui che si vede davvero come funziona una CPU.
Per scrivere questo codice il programmatore deve conoscere esplicitamente:
Quale registro per cosa
Quale registro usare — AX accumulatore, BX base, AL parte bassa di AX.
Byte vs Word
Byte (8 bit) o word (16 bit) — AL vs AX non è intercambiabile, cambia encoding.
Come accedere alla memoria
Diretto, indiretto, tramite registro base, segmento:offset — a basso livello tutto è esplicito.
Scrivere MOV AX, 5 e poi MOV BL, AX è SBAGLIATO: BL è 8 bit, AX è 16 bit. In Assembly le dimensioni devono combaciare: usa MOV AL, 5 se poi lavori su 8 bit, oppure MOV BX, AX per 16 bit. L'assembler 8086 segnala errore di operandi.
Un programma Assembly scritto per l'8086 NON è eseguibile su ARM o RISC-V senza riscrittura completa. Ogni famiglia ha propria ISA, propri registri e convenzioni → codice non portabile.
5. Confronto diretto — alto livello vs basso livello
La stessa operazione somma 5 + 3 a confronto sui tre livelli, per fissare bene la differenza:
| Livello | Codice | Cosa gestisce il programmatore | Portabilità |
|---|---|---|---|
| Alto livello | x = 5 + 3 | Solo la logica | Qualsiasi architettura |
| Assembly | MOV AL, 05h | Registri, dimensioni, indirizzamento | Solo architettura specifica |
| Macchina | 10110000 00000101 | Tutto — bit per bit | Solo CPU specifica |
Perché Python è portabile e Assembly 8086 no? Perché Python viene tradotto da interprete/compilatore per ogni CPU, mentre Assembly è già legato all'ISA: i mnemonici sono simboli per opcode reali dell'8086.
6. Linguaggio macchina e Assembly — il rapporto
Il linguaggio macchina è l'unico formato eseguibile direttamente dalla CPU — pura sequenza di bit. L'Assembly è la sua rappresentazione simbolica leggibile dall'uomo: ogni mnemonico (MOV, ADD, JMP) corrisponde a una specifica sequenza binaria definita dall'ISA dell'8086.
L'8086 è il modello didattico ideale: architettura semplice, ISA ben documentata, corrispondenza diretta tra istruzione simbolica e bit. Ogni istruzione che scriveremo nelle prossime lezioni avrà effetto preciso e verificabile su registri, memoria e flag — nulla è nascosto da livelli di astrazione. Capito questo, capisci qualsiasi CPU.
7. Perché queste distinzioni contano in Sistemi e Reti
Comprendere la differenza tra livelli di astrazione è fondamentale per tre ragioni pratiche nel contesto di questo Modulo 2:
Hardware non astratto
L'Assembly richiede conoscenza dell'hardware — per questo l'8086 ha registri con funzioni specifiche (AX, CX, SP) che in C non vedi.
Niente GC
A basso livello non esiste gestione automatica — ogni accesso richiede indirizzo, segmento e offset espliciti. Devi calcolarlo tu.
Confine SW/HW
Il codice Assembly è legato all'architettura — capire l'ISA dell'8086 significa capire il confine tra software e hardware, core di Sistemi e Reti.
Nella prossima lezione entreremo dentro l'8086 vero: registri, segmentazione e il motivo per cui AX, BX, CX, DX non sono uguali. Tutto ciò che hai visto oggi diventa concreto.
8. Verifica / Esercizio
🔬 Attività in classe — 15 min
Risolvi a mente, poi codifica.
- Scrivi l'algoritmo in pseudocodice per calcolare il quadrato di un numero letto in input. Quante istruzioni servono?
- Traduci
x = 10 + 20in Assembly 8086 usando soloAL. Quale istruzione scegli per caricare i valori? Perché AL e non AX se i numeri < 256? - Spiega perché la sequenza binaria
10110000 00000101è portabile solo su 8086 e non su ARM. Cosa rappresenta il primo byte? - Errore:
MOV BL, 300— perché l'assembler segnala errore? Come lo correggi?
1) Leggi N / Q = N * N / Stampa Q — 3 istruzioni logiche, indipendente dal linguaggio.
2) MOV AL, 0Ah (10) + ADD AL, 14h (20) — uso AL perché 8 bit bastano (0-255). Risparmio spazio e opcode più corto. Se usassi AX, dovrei usare MOV AX, 000Ah → 3 byte invece di 2.
3) Il primo byte è l'opcode B0h = MOV AL, imm8 specifico dell'ISA Intel x86. ARM ha codifica diversa, registri diversi, non riconosce quell'opcode.
4) BL è 8 bit (max 255). 300 > 255 → overflow. Soluzioni: usa registro 16 bit MOV BX, 300 oppure dividi in due operazioni se devi restare su 8 bit (non possibile per 300, serve word).
9. Riepilogo e concetti chiave
📚 Prossimo passo — Introduzione al microprocessore 8086
Ora che sai perché serve scendere di livello, apriremo la scatola: come è fatto dentro l'8086, perché ha segmenti, cosa significano CS:IP e perché AX non è uguale a BX. Porta con te questa distinzione algoritmo/programma.
