Questa guida in C++, la prima nella sezione Sviluppo del grectech Forum, è rivolta ai principianti con una certa familiarità sui tipi di dato gestiti dal linguaggio C++, a coloro che si accingono a compiere i primi passi nell'utilizzo di questo ostico ma potente linguaggio, fornendo al contempo importanti indicazioni preliminari per un corretto approccio alla programmazione "con metodo" e che vede impiegate poche ma semplici regole dogma, troppo spesso sottovalutate in fase di progettazione, che possono portare a comportamenti imprevedibili ed, in alcuni casi, a dir poco disastrosi se non seguite con attenzione.
Non è intenzione dello scrivente fornire spiegazioni riguardo alle classi, all'ereditarietà delle stesse, all' overload o al polimorfismo di cui il linguaggio C++ è ricco quale potenziamento ulteriore del linguaggio C e rivolto alla così detta OOP (Programmazione Orientata agli Oggetti), poiché ritiene che esse non debbano essere note al lettore in questa specifica guida e comunque materia d'integrazione riguardo alle conoscenze di C fin'ora acquisite, necessarie a compredere a pieno le innovazioni apportate dal suffisso '++' che determina il nome del linguaggio trattato.
Pertanto, la guida si prefigge tre scopi essenzialmente:
- Il primo è quello di fornire una porzione di codice riutilizzabile;
- Il secondo è quello di imporre la ricostruzione dell'intero script secondo logica, poichè le porzioni prese in esame volta per volta, saranno esaminate a casaccio;
- Il terzo è quello di puntualizzare che una qualsiasi procedura, indipendentmente dalla sua complessità, deve essere prima scomposta in moduli e poi ricomposta, tenendo presente che:
- Un programmatore non conosce un solo linguaggio;
- Un programmatore conosce più di un linguaggio valutandone pregi e difetti in base a cio che deve sviluppare, scegliendo quello che maggiormente si addice alla tipologia dell'applicativo che sviluppa;
- Un programmatore ama linguaggi che presentino interfacce di scambio tra tipi differenti con differenti potenzialità: Non è raro, infatti, che porzioni di codice che necessitino di velocità d'esecuzione maggiore vengano scritte in assembly o in C e successivamente integrati nel progetto principale.
Codice: Seleziona tutto
file1 => file2
Copia in corso 8589934590:4294967295 [50%]_
Sui sistemi a 32 bit, è possibile gestire valori ed indirizzi che non superino il valore pari a 2 elevato alla 32esima potenza meno 1 (4.294.967.295), ma quello che salta subito agli occhi è la dimensione del file sorgente, cioè esattamente il doppio.
Inoltre, per poter fornire 100% come progressione, è necessario che il calcolo della percentuale venga svolto anch'esso a 64 bit. Tale modus operandi non è così strano, basti pensare che la calcolatrice di Windows su un sistema a 32 bit è in grado di eseguire calcoli come se ci si trovasse ad operare su una piattaforma a 64 bit, ma quello che vedremo sarà come integrarne la possibilità nel codice di esempio proposto.
Prima di iniziare, però, è necessario progettare la procedura tenendo presente che la copia è un'operazione di lettura di un'informazione (8 bit) da un dispositivo di memorizzazione ad un altro e che essa avviene sequenzialmente per tutta la lunghezza del file sorgente, fino alla completa riproduzione dello stesso su quello di destinazione.
Le operazioni di lettura, soprattutto quelle di scrittura, tengono impegnato il bus di scambio dati sulla periferica di memorizzazione di massa (notoriamente molto più lente di una RAM ed ancora più lente di una memoria cache) più di ogni altra, pertanto procedere un byte per volta risulterebbe pesante sia per il controller, sia per la CPU, riducendo anche un hw potenziato ricco di processi in esecuzione alla velocità di una lumaca.
La soluzione, sarebbe quella di leggere l'intero contenuto del file sorgente in una prima fase e poi riversare lo stesso contenuto sulla destinazione in una seconda, ma se la dimensione del file sorgente fosse cospicua, attenderne la scrittura per intero forzerebbe la CPU ed il controller a tenere in sospeso i processi in coda più del previsto, con performances generali decisamente scadenti e disattese.
La soluzione maggiormente utilizzata, è quella di riempire un buffer di lettura di un quantitativo non troppo consistente di dati, dando allo scheduler (organizzatore coda dei processi) abbastanza tempo per riuscire a compiere i processi in sospeso, per poi riversare il buffer sulla destinazione fino al completamento dell'operazione di copia.
Forti delle nozioni di base appena esposte, diamo vita alla prima porzione di codice:
Codice: Seleziona tutto
#include <iostream>
#include <fstream>
//#include <sys/stat.h>
using namespace std;
int main(int argc, char *argv[]){
float perc;
unsigned int numbuff = 256960;
unsigned char buf1[numbuff];
unsigned int ErrLog = 0;
.
..
...
return ErrLog;
}
Poichè l'applicativo gira in ambiente console (shell di Windows), esso dovrà essere in grado di fornire un valore pari all'errore riscontrato in uscita dalla funzione main(), così da poterne gestire l'esito attraverso la variabile %ErrorLevel% in una eventuale procedura batch, es:
copia file1 file2
if errorlevel 1 …
if errorlevel 2 …,ecc
Segue la verifica del numero dei parametri immessi da riga di comando e l'eventuale uscita prematura dalla funzione main(), in caso di omissione di uno dei due o di entrambi, tenendo ben presente che la variabile argc considera anche la riga di comando stessa come valore per argv[], il cui indice posizionale è pari a 0 (zero):
Codice: Seleziona tutto
if(argc!=3) {
cout << "Utilizzo: Copy2 <file1> <file2>\n";
return 1;
}
Codice: Seleziona tutto
ifstream f1(argv[1], ios::in | ios::binary);
if(!f1) {
cout << "* Impossibile aprire il file sorgente "
<< argv[1] << "\n";
return 1;
}
Il risultato è procedere comunque, qualunque valore abbiano i caratteri di cui si compone il file in lettura.
Codice: Seleziona tutto
ofstream f2(argv[2], ios::out | ios::binary);
if(!f2) {
cout << "* Impossibile creare il file di destinazione "
<< argv[2] << " \n";
return 1;
}
A questo punto ci fermiamo un attimo e ricapitoliamo i punti fissi, costituenti dogma, che un programmatore deve considerare con grande attenzione quando sviluppa un applicativo stendendo il suo codice:
L'utilizzo delle variabili è subordinato alla scelta della loro tipologia, dichiarazione ed inzializzazione;
L'utilizzo del buffer, oltre a considerarlo come variabile soggetta alle regole precedenti, è subordinato anche al suo azzeramento preliminare;
L'esito di una condizione di verifica tra una o più variabili deve prevedere valori certi e sicuri delle stesse, sia alla base della verifica, sia nel prosieguo del flusso d'istruzioni.
Poichè, come si è visto poco sopra, la copia è un processo sequenziale di lettura/scrittura durevole per tutta la lunghezza del file sorgente, è giunto il momento di integrare la gestione dei valori a 64 bit in un ciclo while.
Codice: Seleziona tutto
#include <iostream>
#include <fstream>
#include <sys/stat.h>
using namespace std;
Trattasi di una struttura i cui membri possono essere richiamati in lettura/scrittura quando necessario.
I membri, nonché la tipologia di dato a cui essi appartengono, sono definiti sequenzialmente in modo da definire un'area di memoria compartimentata a priori, diversa da una struttura di tipo union, dove i valori dei vari membri appartenenti ad essa possono sovrascrivere sezioni compartimentali differenti dissimili tra loro, posizionate in maniera non necessariamente contigua all'interno della struttura stessa.
Codice: Seleziona tutto
.
..
...
#if defined (__MSVCRT__)
struct _stati64 {
_dev_t st_dev;
_ino_t st_ino;
unsigned short st_mode;
short st_nlink;
short st_uid;
short st_gid;
_dev_t st_rdev;
__int64 st_size;
time_t st_atime;
time_t st_mtime;
time_t st_ctime;
};
.
..
...
#if defined (__MSVCRT__)
_CRTIMP int __cdecl _fstati64(int, struct _stati64 *);
_CRTIMP int __cdecl _stati64(const char *, struct _stati64 *);
/* These require newer versions of msvcrt.dll (6.10 or higher). */
#if __MSVCRT_VERSION__ >= 0x0601
_CRTIMP int __cdecl _fstat64 (int, struct __stat64*);
_CRTIMP int __cdecl _stat64 (const char*, struct __stat64*);
#endif /* __MSVCRT_VERSION__ >= 0x0601 */
#if !defined ( _WSTAT_DEFINED) /* also declared in wchar.h */
_CRTIMP int __cdecl _wstat(const wchar_t*, struct _stat*);
_CRTIMP int __cdecl _wstati64 (const wchar_t*, struct _stati64*);
#if __MSVCRT_VERSION__ >= 0x0601
_CRTIMP int __cdecl _wstat64 (const wchar_t*, struct __stat64*);
#endif /* __MSVCRT_VERSION__ >= 0x0601 */
#define _WSTAT_DEFINED
#endif /* _WSTAT_DEFIND */
#endif /* __MSVCRT__ */
Codice: Seleziona tutto
float perc;
unsigned int numbuff = 256960;
unsigned char buf1[numbuff];
unsigned int ErrLog = 0;
struct _stati64 results, numread, i;
.
..
...
_stati64 (argv[1], &results);
_stati64 (argv[1], &numread);
_stati64 (argv[1], &i);
numread.st_size = 0;
Codice: Seleziona tutto
while(!f1.eof()){
f1.read((char *) buf1, numbuff);
if(!f1 && !f1.eof()){
cout << "\n* Impossibile leggere dal file sorgente "
<< argv[1] << "\n";
ErrLog++;
break;
}
i.st_size = (f1.eof()?results.st_size-numread.st_size:numbuff);
if(f2.write((char *) buf1, i.st_size)){
numread.st_size += i.st_size;
perc = (int)(((float)numread.st_size / (float)results.st_size) * 100);
cout << "Copia in corso "
<< numread.st_size
<< ":"
<< results.st_size
<< " ("
<< perc
<< "%) ...";
for(i.st_size=0; i.st_size<80; i.st_size++) cout << "\b";
memset(buf1, 0, numbuff);
}else{
cout << "\n* Impossibile scrivere sul file di destinazione "
<< argv[2] << "\n";
ErrLog++;
break;
};
};
Codice: Seleziona tutto
free(buf1);
f1.close();
f2.close();
return ErrLog;