Nel sistema operativo in uso sulle macchine del laboratorio (linux, che
è una implementazione di Unix), la comunicazione con le periferiche avviene
attraverso la creazione di canali di comunicazione, identificati da
file descriptors, che operano su un albero di file
(filesystem).
In questi canali di comunicazione, fluiscono sequenze di byte
(o, se preferiamo, di char),
senza ulteriore interpretazione o organizzazione. Le operazioni fondamentali
che il sistema operativo è in grado di gestire a basso
livello sono le seguenti:
open |
Apertura di un canale di comunicazione fra un processo ed
un file identificato nella struttura gerarchica del
filesystem (Es: /home/prelz/.login,
/dev/urandom, etc.) |
lseek |
Posizionamento relativo o assoluto all'interno del file. (Può essere interpretato in vari modi a seconda del tipo di file). |
read |
Lettura di un certo numero di byte dal canale di comunicazione. |
write |
Scrittura di un certo numero di byte nel canale di comunicazione. |
fstat |
Richiesta al filesystem di informazioni su un determinato file. |
ioctl |
Esecuzione di funzioni di controllo, o diagnostiche. |
close |
Chiusura del canale di comunicazione. |
Queste funzioni vengono eseguite ad un livello molto più basso
rispetto a quello delle funzioni di "Text I/O" che si utilizzano
di consueto in C++. Fra questo livello e quello della programmazione I/O C++
è inoltre interposto uno strato di buffering che permette di
disaccoppiare le caratteristiche fisiche dei dispositivi utilizzati
per la comunicazione.
Per avvicinarsi (anche se non molto) alle funzioni di basso livello,
si possono usare i metodi di "Binary I/O" get(), read(), put(),
write(), come ad esempio in questo
.
Se vogliamo vedere cosa succede a basso livello, possiamo utilizzare
la funzione di process tracing, accessibile ad esempio attraverso
il comando strace:
> strace ./ex1
execve("./ex1", ["./ex1"], [/* 31 vars */]) = 0
(...)
open("/dev/urandom", O_RDONLY) = 3
fstat(3, {st_mode=S_IFCHR|0444, st_rdev=makedev(1, 9), ...}) = 0
ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fffffc1c2e0) = -1 EINVAL (Invalid argument)
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2aaaaaac0000
read(3, "O\312kh\232\252Y\nW\217\367\251\341GI\32W\240\224\32\334"..., 8192) = 8192
(...)
iostreamfstream. Senza pretese di comprendere subito i
dettagli, osserviamo come fstream faccia parte di
una articolata famiglia, descritta da questa figura (tratta dall'utile
riferimento di http://www.cplusplus.com - la figura si trova anche nel Capitolo 9 di
"C++ in nutshell"):
Osserviamo dove si situano all'interno di questa gerarchia oggetti che
conosciamo: std::cout (è un std::ostream),
std::fstream (è un std::iostream).
Cosa possono avere in comune questi due oggetti ? Come (e dove) descrivere
queste caratteristiche comuni ?
Di questa famiglia, forse gli oggetti meno familiari sono gli
std::stringstream, che possiamo vedere in azione in questo
.
Gli std::stringstream consentono di applicare
una stringa di char in memoria
le stesse operazioni che si potrebbero applicare ad un file
(dopo tutto, come abbiamo visto, ogni file permette comunque di
gestire una sequenza di byte/char).
Lo stato di un qualsiasi stream della famiglia di iostream
può essere variato attraverso manipolatori. Alcuni manipolatori si
trovano predefiniti negli include file <ios>,
<istream>, <ostream>,
<iomanip>:
<ios> |
std::(no)boolalpha |
Attiva la conversione in stringa dei tipi bool
(true/false anziché 1/0). |
<ios> |
std::dec, std::hex, std::oct |
Cambia la base per la conversione dei numeri rispettivamente in decimale, ottale, esadecimale |
<iomanip> |
std::setbase(int base) |
Modifica la base per la conversione dei numeri. Le basi ammesse sono 8, 10 o 16. |
<ios> |
std::(no)showbase |
Aggiunge '0' o '0x' rispettivamente prima dei numeri ottali ed esadecimali. |
<ios> |
std::fixed, std::scientific |
Modifica la conversione dei tipi di dati non interi in virgola fissa, oppure in notazione esponenziale. |
<ios> |
std::(no)showpos |
Mostra un segno '+' anche quando non sarebbe necessario. |
<ios> |
std::(no)showpoint |
Mostra un punto decimale anche quando non sarebbe necessario. |
<ios> |
std::(no)uppercase |
Usa lettere maiuscole nella stampa dei numeri (X, E, A-F etc.). |
<iomanip> |
std::setprecision(int n) |
Modifica la precisione utilizzata per la conversione dei valori numerici. |
<iomanip> |
std::setfill(char c) |
Modifica il carattere di riempimento usato per la stampa. |
<iomanip> |
std::setw(int n) |
Modifica la larghezza del campo utilizzato per la conversione del
successivo valore (gli spazi sono riempiti con il carattere
definito in setfill. |
<ios> |
std::internal, std::left, std::right |
Determina la posizione dell'argomento in seguito alla
conversione in stringa. Giustificato (con eventuali caratteri
definiti in setfill
aggiunti dopo il segno o prefisso), allineato a sinistra
o a destra. |
<ios> |
std::(no)skipws |
Ignora gli spazi bianchi (o caratteri corrispondenti a spazio bianco,
come \t, \n, \r)
all'inizio di una fase di input |
<istream> |
std::ws |
Consuma immediatamente gli spazi bianchi (o caratteri corrispondenti a spazio bianco). |
<ios> |
std::(no)unitbuf |
Causa uno svuotamento automatico (flush) del buffer al termine di ogni operazione di output. |
<ostream> |
std::flush |
Causa lo svuotamento immediato (flush) del buffer. |
<ostream> |
std::endl |
Causa l'emissione in output di un carattere di fine riga e lo svuotamento (flush) del buffer. |
<ostream> |
std::ends |
Causa l'emissione in output di un carattere di fine stringa (NUL). |
Esercizio: Scrivere un programma che legge un file qualsiasi e stampa i singoli byte del contenuto in formato esadecimale.