This repository has been archived by the owner on Jun 16, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
413ff20
commit 04cd758
Showing
11 changed files
with
546 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# Compendio C/C++ | ||
|
||
Non è generalmente possibile utilizzare un linguaggio di programmazione "as is" | ||
|
||
Sono utili le librerie per evitare di reinventare la ruota e semplificare il processo di astrazione | ||
|
||
## Libreria standard | ||
Sia in C che in Cpp è presente una *libreria standard* | ||
|
||
La stdlib del C è un sottoset della stdlib del Cpp | ||
|
||
Entrambe sono suddivise in sezioni che svolgono compiti diversi | ||
|
||
Esistono molte altre librerie, come le librerie `boost` del Cpp | ||
|
||
## Moduli | ||
In C++ la libreria standard è suddivisa in moduli. | ||
|
||
In generale il nome di una libreria in Cpp si ottiene a partire dal nome della libreria C, rimuovendo il suffisso ".h" ed aggiungendo "c" come prima lettera | ||
|
||
## I/O formattato in C | ||
All'interno della `stdio.h` sono presenti `printf(const char format[], ...)` e `scanf(const char format[], <indirizzi_variabili>)` | ||
|
||
Queste due funzioni hanno un numero di parametri variabile (funzioni *variadiche*) | ||
|
||
Le funzioni variadiche si possono essere usate sia in C che in Cpp. Si utilizza un'estensione della sintassi vista fin'ora. Per recuperare gli args si utilizzano funzioni presenti nella libreria standard. | ||
|
||
### Confronto con cin e cout | ||
Rispetto al C, la stampa e l'input formattato in C++ permette di non specificare il tipo di dato da stampare o prendere in input, perchè il compilatore deduce automaticamente qual'è il tipo di dato utilizzato (delle variabili di destinazione/origine) | ||
|
||
### Corruzione della memoria | ||
=> molto frequente in C, in quanto se si sbaglia a passare l'indirizzo della variabile a scanf -> comporta la scrittura in una locazione non adeguatamente grande, oppure la scrittura sull'area di memoria di un puntatore | ||
|
||
### Performance | ||
scanf, printf, << e >> hanno la stessa velocità di lettura/scrittura perchè gli operatori << e >> vengono sostituiti a tempo di compilazione | ||
|
||
## Direttiva #define | ||
Negli ultimi standard del C è stato introdotto il qualificatore `cosnt`, prima si utilizzava la direttiva al precompilatore `#define` | ||
|
||
-> a tempo di compilazione viene sostituito simbolo per simbolo con il valore associato ad esso dal define | ||
|
||
## typedef | ||
In C è necessario dichiarare un oggetto di tipo struct nel seguente modo: | ||
``` | ||
struct <nome_struttura> <identificatore>; | ||
``` | ||
|
||
Quindi per evitare di scrivere tutto per esteso si utilizza: | ||
``` | ||
typedef struct persona persona_t; | ||
``` | ||
|
||
La stessa cosa vale per gli enum e per tipi di dato frequentemente utilizzati. | ||
``` | ||
typedef unsigned long int uint32; | ||
``` | ||
|
||
typedef permette di migliorare la leggibilità. | ||
|
||
## Allocazione memoria dinamica | ||
In C si usa `malloc()` (al posto di `new`) presente nella stdlib | ||
|
||
``` | ||
<nometipo>* <identificatore> = (<nometipo>*) malloc(<dimensione>); | ||
``` | ||
|
||
Differenze: | ||
- `new` casta automaticamente l'indirizzo allocato al tipo di dato dichiarato | ||
- `new` calcola la dimensione dell'area di memoria al posto di utilizzare sizeof e moltiplicare per la lunghezza dell'array | ||
|
||
E per liberare la memoria (al posto di `delete[]`): | ||
``` | ||
free(<indirizzo>); | ||
``` | ||
|
||
## Compilazione | ||
``` | ||
gcc program.c | ||
``` | ||
|
||
L'estensione è differente, ma questo non ha importanza (il compilatore potrebbe arrabbiarsi) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
# Input Output non formattato | ||
Fino ad ora abbiamo visto I/O formattato => gli operatori `<<` e `>>` stampano o prendono in input su uno stream sequenze di caratteri | ||
|
||
Esistono anche operazioni di i/o non formattate che operano su sequenze di byte -> trasferimento di byte da uno stream alla memoria o dalla memoria allo stream | ||
|
||
Ogni volta che faccio lettura o scrittura in modalità non formattata agisco in modo sequenziale sulla memoria | ||
|
||
Le funzioni di lettura e scrittura non formattate sono *funzioni membro* degli stream -> per invocarle si usa l'operatore punto | ||
|
||
## Buffer | ||
Un buffer è un array di byte utilizzato per operazioni di I/O | ||
|
||
Il tipo char ha esattamente la dimensioni di un byte -> tipo di dato utilizzato per operazioni di I/O non formattato | ||
|
||
## Funzioni membro degli istream | ||
- get() - ritorna il valore del prossimo byte nello stream di ingresso (intero). | ||
``` | ||
int i; | ||
while ( (i = cin.get()) != EOF ) { ... } | ||
``` | ||
- get(char &c) - preleva un byte dallo stdin e lo assegna alla variabile (tipo carattere) passata in ingresso. Nel caso in cui venisse incontrato EOF `c` rimane invariata e il metodo riorna false. | ||
``` | ||
char c; | ||
while (cin.get(c)) { ... } | ||
``` | ||
- get(char \*buf, int n, [char delimitatore]) - Legge una sequenza di byte dallo stdin e la copia in buf. Aggiunge automaticamente '\0' alla fine. La lettura va avanti finchè non si sono letti n byte OPPURE finchè non si incontra '\n' (delimitatore di default). L'istream viene messo in stato di errore se la riga è vuota oppure incontra il '\n' da solo (vedi dopo). | ||
- peek() - ritorna il valore del prossimo byte presente su un istream senza consumare il carattere -> può essere utile per evitare lo stato di errore dell'istream | ||
``` | ||
char *riga; | ||
while (cin.peek() == '\n') ; // pulisco righe vuote | ||
cin.get(riga, MAXLEN); | ||
cin.get(); // butto via '\n' | ||
``` | ||
- read(char \*buf, int n) - funzione di più basso livello rispetto a get(). Non aggiunge delimitatore e non va in stato di errore. Il delimitatore non esiste. read() cerca di leggere esattamente n bytes, e ritorna il valore effettivamente letto. | ||
|
||
// TODO: link file_conta_linee.cpp | ||
|
||
## Funzioni membro degli ostream | ||
- put(char c) - equivalente della get | ||
- write(char \*buf, int n) - equivalente di read() | ||
|
||
## File di testo e binari | ||
I file binari memorizzano i dati "grezzi" provenienti dalla memoria -> sono una sequenza di byte (numeri) | ||
|
||
Un file è una sequenza di byte a cui è associato un nome | ||
|
||
## Puntatori ed array | ||
Sappiamo che `<tipo> *` memorizza un puntatore. Si può passare un array al posto di `<tipo> *` il nome di un array, perchè esso corrisponde all'indirizzo del primo elemento. | ||
|
||
Il tipo char * memorizza indirizzi di qualunque tipo di variabile -> qualunque tipo di dato è convertibile in un array di caratteri | ||
|
||
``` | ||
reinterpreter_cast<char *>(<indirizzo_oggetto>); // reinterpreta l'oggetto come se fosse un array di caratteri | ||
``` | ||
|
||
Una sequenza di byte equivale ad un array di byte. Un array di byte è interpretabile come un array di caratteri. Quindi ogni tipo di dato può essere considerato un array di caratteri. | ||
|
||
Rimane da capire come determinare la dimensione di un oggetto: uso `sizeof(<tipo_dato>)` | ||
Oppure nel caso in cui sia un array: `sizeof(<tipo_dato>)*<numero_elementi>` | ||
|
||
``` | ||
int a[] = {1, 2, 3}; | ||
of.write(reinterpreter_cast<char*>(a), sizeof(a[0])*3); | ||
``` | ||
|
||
La dimensione finale del file sarà di esattamente 12 byte | ||
|
||
### Numeri reali e memoria | ||
Memorizzando un numero reale con questo metodo posso mantenere tutti i byte salvati in memoria evitando troncamenti | ||
|
||
// TODO: link scrivi_leggi_array.cpp | ||
// TODO: link studente_su_file.cpp | ||
|
||
## File di testo | ||
Per leggere e scrivere file di testo si usano tipicamente le scritture formattate perchè ogni byte equivale ad un char | ||
|
||
## Efficienza | ||
Le letture e scritture formattate possono essere effettuate mediante cicli for, perchè gli stream memorizzano un i dati in un buffer prima di eseguire la scrittura | ||
|
||
Le letture e scrutture non formattate sono inefficienti se effettuate mediante cicli, perchè non vene interposto un buffer tra la scruttura e la memoria. | ||
|
||
## Puntatori e alias | ||
W: puntatori e alias ad una variabile sono estremamente diversi. Quando utilizzo un alias il valore e l'indirizzo di alias e variabile rimangono uguali. | ||
|
||
## Dereferenziazione | ||
Per ottenere l'indirizzo di una variabilie che non sia un array si utilizza l'operatore `&`, che dereferenzia (prende l'indirizzo) della variabile alla quale è applicato | ||
|
||
### Sintassi | ||
``` | ||
& <identificatore_oggetto> | ||
``` | ||
|
||
### Semantica | ||
Ritorna l'indirizzo dell'oggetto | ||
|
||
// TODO: vedi es per casa (x2) | ||
|
||
## Accesso sequenziale e casuale | ||
Quando opero sui file posso accedere in modo casuale al file (non sequenziale). Questo non è possibile con gli stream. | ||
Quando accedo in modo "randomico" ad un file posso decidere arbitrariamente dove leggere o scrivere. | ||
Sugli stream è possibile solamente l'accesso sequenziale. | ||
|
||
Il byte a partire da quale avverrà la prossima operazione è dato da un contatore che parte da 0. Il suo valore può essere modificato con queste funzioni: | ||
|
||
``` | ||
seekg(nuovo_valore); // sposto la testina per un file di input (g sta per get) | ||
seekp(nuovo_valore); // sposto la testina per un file di output (p sta per put) | ||
``` | ||
|
||
Le due funzioni possono essere invocate con due argomenti: | ||
``` | ||
seekg(offset, origine); | ||
seekp(offset, origine); | ||
``` | ||
|
||
Dove `origine` può essere: | ||
- `ios::beg` (argomento default del secondo parametro) | ||
- `ios::end` (parto dalla fine andando all'indietro) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Algoritmi e programmi | ||
formulazione di un problema -(metodo risolutivo "astratto")-> individuzione algo -(codifica in un linguaggio di programmazione)-> programma | ||
|
||
Un programma mandato in esecuzione diventa un processo, in grado di prendere in input dei dati e di fornire i risultati in uscita | ||
|
||
## Proprietà di un algoritmo | ||
- eseguibile in un tempo finito | ||
- non ambiguo | ||
- | ||
|
||
## Costo computazionale | ||
Il costo computazione di un algoritmo dipende dal numero di passi elementari che sono necessari per arrivare ad una soluzione | ||
|
||
Questo valore dipende da: | ||
- dall'insieme di valori in ingresso | ||
- dall'insieme delle azioni elementari che posso eseguire sui dati. Eg: in base a come è implementato ogni passo elementare a basso livello dipende il costo computazionale dell'istruzione ad alto livello | ||
|
||
Necessaria una notazine che renda il significato di costo computazione indipendente da questi due fattori. | ||
|
||
Eg: N passi per lavorare su N elementi (solitamente sempre presente perchè i dati li devo almeno prendere in input) | ||
Eg: Due cicli innestati di N valori diventa N^2 -> non importa l'efficienza di ogni singolo passo, perchè comunque l'algoritmo con costo pari a N è sempre più ottimizzato | ||
|
||
Importa la *pendenza* del costo dell'algoritmo -> ovvero la derivata prima del costo di un algoritmo | ||
|
||
### Ordine di costo | ||
Un algo ha un ordine O(N) se esiste una costante _c_ tale che il numero di operazioni la variare dell'input cresce al più _c\*N_ | ||
|
||
### Ordini di costo notevoli | ||
- costo lineare O(N) | ||
- costo quadratico O(N^2) eg: algo di insertion sort | ||
- costo polinomiale O(N^i) | ||
- costo esponenziale O(a^N) l'algo non può essere completato in un tempo ragionevole | ||
- costo costante O(1) permette di eseguire le operazioni in un tempo che non dipende dalla dimensione dell'input (eg: hashmap) | ||
|
||
### Confronto costo algoritmi | ||
Non sempre un algoritmi con ordine di costo maggiore è il migliore per risolvere un problema che coinvolge un piccolo set di dati (perchè la notazione O-grande mi indica il costo per N che tende all'infinito) | ||
|
||
Verifica sempre i "costi di basso livello" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# Classi di memorizzazione | ||
- automatica | ||
- statica | ||
- dinamica | ||
|
||
## Oggetti statici | ||
- oggetti globali | ||
- oggetti dichiarati con l'attributo static | ||
|
||
Esistono per tutto il tempo di vita del programma | ||
|
||
Se non inizializzati hanno valore 0 | ||
|
||
## Oggetti dinamici | ||
Hanno un valore casuale (bullshits) | ||
|
||
Il tempo di vita va dal momento dell'allocazione a quello della deallocazione dell'area di memoria | ||
|
||
## RDA | ||
Record di attivazione: struttura dati che rappresenta l'ambiente della funzione e contiene: | ||
- parametri formali inizializzati con i parametri attuali | ||
- variabili e costanti locali | ||
- indirizzo del record di attivazione del chiamante (da riattivare una volta finita l'esecuzione della funzione) | ||
- indirizzo di ritorno (dal quale riprendere l'esecuzione dopo aver ritornato) | ||
|
||
## Base ed offset | ||
L'indirizzo a cui inizia il record di attivazione di una funzione viene chiamato indirizzo *base* | ||
Ogni oggetto è identificato da un *offset* calcolato a partire dall'indirizzo base | ||
|
||
Ogni oggetto locale è identificato da un indirizzo calcolato come base+offset | ||
|
||
## Prologo | ||
=> codice aggiuntivo inserito dal compliatore per creare il RDA, inizializzare il base pointer ed eseguire la prima istruzione della funzione | ||
|
||
## Dimensione | ||
=> dipende dal numero di variabili/costanti locali e dai parametri formali cha abbiamo dichiarato | ||
|
||
Gli RDA sono memorizzati nello _stack_ (pila) -> struttura dati LIFO | ||
|
||
L'RDA viene deallocato solo al termine dell'esecuzione | ||
|
||
Gli RDA sono innestati: vengono allocati secondo l'ordine delle chiamate e deallocati in ordine inverso | ||
|
||
Il C e C++ sono linguaggi imperativi e funzionali | ||
|
||
### Catena dinamica | ||
=> rappresenta la storia di come sono state chiamate le funzioni una dopo l'altra | ||
|
||
## Valore di ritorno | ||
È diponibile una sola area di memoria come valore di ritorno -> per questo ho un solo valore possibile, che può essere al massimo un puntatore | ||
|
||
### Epilogo | ||
Pezzo di codice inserito dal compilatore che si occupa di distruggere e deallocare l'RDA di una funzione | ||
|
||
## Spazio di indirizzamento | ||
Ad un processo, quando viene lanciato, viene associata un'area di memoria chiamata _spazio di indirizzamento_ (assegnata dal SO) così suddivisa: | ||
|
||
testo e istruzioni | ||
costanti | ||
oggetti statici inizializzati | ||
oggetti statici non inizializzati | ||
heap (memoria dinamica) | ||
area libera (che può ridursi dall'alto o dal basso) | ||
stack | ||
|
||
Eg: spectre e meltdown permettevano di leggere i valori rimasti sulla cache della CPU a causa di meccanismi della branch prediction mal gestiti | ||
|
||
Gli oggetti statici sono automaticamente inizializzati a zero | ||
|
||
Lo stack cresce verso l'alto, l'heap verso il basso. Quando lo spazio libero viene esaurito le due aree di memoria non sono ulteriormente incrementabili |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# Traduttori e compilatori | ||
Un algoritmo è utile se esiste un _esecutore_ in grado di eseguirilo. | ||
|
||
Un esecutore viene chiamato _automa esecutore_ -> deve essere in grado di eseguire una codifica dell'algoritmo | ||
|
||
Gli automi venivano realizzati con congegni meccanici (Pascal - pascalina, Babbage) o implementati attraverso un modello matematico (modello funzionale - Hilbert, modello ... - Turing) | ||
|
||
## Macchina virtuale | ||
=> automa esecutore in grado di eseguire un programma C/C++ (o un qualsiasi altro linguaggio di programmazione) senza che esista una macchina reale | ||
=> gira sulla macchina reale (processore) -> linguaggio macchina: sequenza di 0 e 1 -> operazioni elementari | ||
|
||
## Linguaggio assembly | ||
Linguaggio di basso livello in grado di essere tradotto 1:1 in linguaggio macchina | ||
|
||
Non esiste il concetto di memoria/variabili, ma sono utilizzabili solamente i registri (cella di memoria interna al processore) | ||
|
||
Le istruzioni sono parole mnemoniche che identificano una precisa sequenza di 0 e 1 intepretabile dalla CPU | ||
|
||
## Problema della portabilittà | ||
Il linguaggio macchina è il linguaggio di un processore, strettamente dipendente dall'instruction set della CPU -> il codice assembly non è portabile | ||
|
||
Eg: Istruction Set x64 risolve parzialmente questo problema, perchè permette di utilizzare lo stesso codice per una famiglia di CPU | ||
Ma se utilizzo una istruzione specifica di una CPU (oppure disponibile da una certa versione in poi) il programma non è più portabile | ||
|
||
## Scelta del linguaggio macchina | ||
È una buona scelta codificare un algoritmo in linguaggio macchina? | ||
- sì se devo utilizzare il programma su una sola architettura | ||
- no se devo rendere il codice portabile | ||
|
||
## Livello di astrazione | ||
C/C++ sono linguaggi di alto livello perchè si basano su un elaboratore ipotetico in grado di eseguire delle istruzioni che non appartengono ad un processore reale | ||
|
||
Un linguaggio di alto livello è più vicino al linguaggio naturale, quindi mette a disposizione istruzioni e strutture dati più vicine al dominio del problema | ||
|
||
L'esistenza di svariati linguaggi di programmazione | ||
|
||
## Processo di traduzione | ||
I traduttori trasformano il linguaggio sorgente in linguaggio macchina | ||
|
||
2 tipi di traduttori: | ||
- compilatori: genera un file eseguibile contenente tutto il programma. Non esegue il programma. Si dice che il compilatore *traduce* ogni istruzione. | ||
- interpreti: esegue direttamente il codice sorgente senza generare un file eseguibile. Esegue una istruzione alla volta. Si dice che l'interprete *valuta* ogni istruzione. | ||
|
||
## Programmi intepretati e compilati | ||
Tipicamente i programmi compilati girano più velocemente dei programmi interpretati | ||
|
||
I programma interpretati sono più portabili dei programmi scritti con un linguaggio compilato | ||
|
||
Questa differenza si sta appiattendo sempre di più | ||
|
||
## Programmi compilati | ||
Gli header file spesso contengono solo delle dichiarazione | ||
|
||
Linking del codice macchina delle funzioni messe a disposizione dalla libreria già compilata. Vengono messi a disposizione gli headerfile contenenti solamente le dichiarazioni e i prototipi delle funzioni | ||
|
||
### Link delle librerie | ||
A volte il complilatore non trova il percorso di una libreria: bisogna usare il flag `-lm` | ||
|
||
### Fasi compilazione | ||
- preprocessing -> esecuzione delle direttive al precompilatore (eg: gli include copiano e incollano pari pari gli headerfile importati, i define vengono risolti sostituendo il valore associato al simbolo) | ||
- traduzione -> genera un file oggetto (file contenente il linguaggio macchina) | ||
- linking -> si unisce il file oggetto (tipicamente _.o_) con il programma appena compilato. Si ottiene un file eseguibile. | ||
|
||
## Fasi di sviluppo | ||
1. progettazione | ||
2. scrittura (codifica) | ||
3. compilazione | ||
4. esecuzione | ||
5. collaudo (testing) | ||
6. ricerca ed eliminazione degli errorri (debugging) | ||
|
||
### Abiente di programmazione | ||
ambiente di programmazione o di sviluppo => Linux stesso è un ambiente di sviluppo. Elementi di un ambiente di sviluppo: | ||
- editor | ||
- traduttore | ||
- linker | ||
- interprete | ||
- debugger | ||
|
||
### Tipi di ambienti di sviluppo | ||
1. ambienti che mettono a disposizione singoli strumenti | ||
2. i singoli strumenti di sviluppo sono raggruppati sotto un'unica interfaccia grafica (IDE - Integrated Development Environment -) | ||
|
||
### Confronto tra gli ambienti di sviluppo | ||
Il vantaggio degli IDE è la semplicità e facilità d'uso | ||
|
||
Il vantaggio dei sistemi non IDE (tipo UNIX) è il poter distinguere le varie fasi dello sviluppo e l'indipendenza tra i vari strumenti |
Oops, something went wrong.