Comment s'utilise fwrite ?
Je rappelle que le prototype de cette fonction est :
Code : C | size_t fwrite (const void *ptr, size_t size, size_t nmemb, FILE *stream);
|
- ptr pointeur sur le premier octet de la liste d'objets à inscrire.
- size L'espace mémoire pris par un membre de la liste d'objets à inscrire.
- nmemb Le nombre de membres ayant la taille size dans la liste d'objets à inscrire.
- stream Pointeur sur le flux (pointeur sur FILE dans notre cas).
- Valeur retournée La fonction fwrite retourne le nombre d'éléments qu'elle a réussi à inscrire correctement dans le flux pointé par stream.
Ecriture d'une variable dans un fichier
Admettons que je veuille sauvegarder ma variable 'var' dans mon fichier, j'utiliserai donc fwrite ainsi :
Code : C | int var = 15;
fwrite( &var , sizeof(int) , 1 , fichier);
|
D'où vient le 'fichier'

?
Et bah le 'fichier' c'est le fichier dans lequel je souhaite sauvegarder ma variable, que je dois avoir ouverte préalablement à l'aide de fopen comme ceci :
Code : C | FILE * fichier;
fichier = fopen("monfichier.bin" , "wb");
|
Il faut noter les choses suivantes avec la légende "très important"

:
- L'extension du fichier n'a pas d'importance, mais ici j'ai choisi le .bin, pour éviter le .txt. Car, contrairement au mode formaté qu'on a vu précédemment, un fichier écrit en mode binaire ne doit pas être ouvert ou édité avec un éditeur de texte classique (Bloc-notes par exemple), mais par un éditeur de fichiers binaires.
De toute façon, le contenu de notre fichier ne sera pas exploitable généralement
- "wb"
: le 'b' ici indique à fopen qu'on souhaite ouvrir le fichier en mode binaire (donc non formaté), ce même 'b' peut être combiné avec tous les autres modes de la fonction fopen (a,r,w,...).
Dans le cas de r+,a+ ou w+, le 'b' doit être entre la lettre et le signe '+' comme ceci : "rb+".
- Et le plus important, toujours tester le retour de fopen
Maintenant que nous savons ouvrir un fichier en mode binaire, analysons la ligne :
fwrite( &var , sizeof(int) , 1 , fichier);
1- J'appelle ma fonction fwrite.
2- Je lui donne un pointeur sur l'espace mémoire que je cherche à sauvegarder.
3- Je lui dis que cet espace fait (
sizeof(int)
) 4 octets dans notre cas.
4- Je lui dis qu'il n'y a qu'un seul élément.
5- Et je lui dis que c'est dans 'fichier' que je voudrais sauvegarder tout ça

.
Ecriture d'un tableau alloué statiquement dans un fichier
Si j'ai maintenant un tableau de int comme ceci :
Code : C
Et que je veuille le sauvegarder dans un fichier alors c'est simple

, il suffit de faire ceci :
Code : C | fwrite( tab , sizeof(int) , 10 , fichier);
|
Et oui pas besoin de faire une boucle pour inscrire mon tableau case par case

.
Car on a demandé à la fonction fwrite d'inscrire l'espace mémoire pointé par 'tab', et dont la portée est 10 x 4 octets. Ce qui correspond aux 10 éléments de mon tableau.
Je vais vous conseiller une autre façon de le faire, que si vous décidez de changer la taille ou le type de votre tableau, cela ne vous obligera pas à changer l'appel à la fonction fwrite. L'écriture est la suivante :
Code : C | fwrite( tab , sizeof(tab[0]) , sizeof(tab)/sizeof(tab[0]) , fichier);
|
- sizeof (tab[0])
nous indiquera la taille de chaque élément de notre tableau (4 octets).
- sizeof (tab)
nous indiquera la taille totale allouée à notre tableau (40 octets), que si on divise par la taille de chaque élément, ceci nous donne le nombre d'éléments que contient notre tableau (10 éléments).
Ceci indépendamment du type de notre tableau et de sa taille
Cette méthode est valable aussi pour des tableaux à plusieurs dimensions et dont la déclaration est faite statiquement (et non par allocation dynamique)
Ainsi une utilisation comme ceci est correcte :
Code : C | int tab[10][10];
fwrite( tab , sizeof(tab) , 1 , fichier);
|
Ce cas est seulement pour une déclaration dite "statique" de notre tableau, pour un cas d'allocation dynamique, ceci n'est plus valable.
Ecriture d'un tableau alloué dynamiquement dans un fichier
Si je dispose d'un tableau que j'ai alloué dynamiquement par malloc comme ceci :
Code : C | int * ptab;
ptab = malloc(10 * sizeof(int));
//Ne pas oublier de tester le retour de malloc !
|
la sauvegarde dans notre fichier s'effectuera de la même façon qu'un tableau statique :
Code : C | fwrite( ptab , sizeof(int) , 10 , fichier);
|
Ou indépendemment du type ainsi :
Code : C | fwrite( ptab , sizeof (* ptab) , 10 , fichier);
|
Ceci n'est pas vrai dans le cas de tableaux à plusieurs dimensions (alloués dynamiquement), il faudrait écrire chaque dimensions comme présenté ci-dessus.
Ecriture d'un pointeur dans un fichier
Ce cas est très identique à celui d'un tableau alloué dynamiquement.
Si on a un pointeur 'ptr' ayant une taille allouée de 'size' octets, alors fwrite s'utilise ainsi :
Code : C | fwrite ( ptr , size , 1 , fichier);
|
ainsi, tout le bloc mémoire alloué pour notre pointeur sera considéré comme un seul élément uni (d'où le 1 au 3ième argument).
On peu également considérer que la mémoire comporte 'size' éléments de taille 1 octet, auquel cas l'utilisation de fwrite devient :
Code : C | fwrite ( ptr , 1 , size , fichier);
|
Il s'agit de deux notions différentes, et qui aboutiront à deux façons parfois différentes de construire notre fichier.
Alors dans la lecture avec fread, on fera attention à respecter ce détail.
Ecriture d'une structure dans un fichier
L'écriture d'une structure est très similaire aux cas présentés ci-dessus, car pour fwrite, encore une fois, la structure ne sera qu'une suite d'octets. Cependant, il y a quelques petites notions à comprendre

. D'ailleurs c'est pourquoi je fais ce tutoriel.
Si nous avons une structure comme ceci :
Code : C | typedef struct {
int age;
char nom[30];
char prenom[30];
}Personne;
|
Alors il n'y aucun problème à l'utilisation de l'opérateur
sizeof
pour savoir la taille de notre structure.
Code : C | Personne personne1 = {15,{"NOM"},{"Prenom"}};
fwrite( &personne1 , sizeof(personne1) , 1 , fichier );
|
Maintenant si on a une structure comme ceci :
Code : C | typedef struct {
int age;
char * nom;
char * prenom;
}Personne;
Personne personne1 = {15,"TOTO","TATA"};
|
Alors si on essaie de récupérer la taille de cette dernière par un sizeof, ceci nous donnera la taille de age + la taille de 'nom' + la taille de 'prenom'.
Où est le problème ?
Le problème est que 'nom' et 'prenom' sont deux variables de type pointeur. Et leurs tailles sont les tailles d'un pointeur (généralement 4 octets) et non celles des chaines de caractères sur lesquelles ils pointent.
ainsi le résultat de
sizeof(prsonne1)
donnerait 12 octets (4 + 4 + 4) quelque soient les chaines sur lesquelles ils pointent.
fwrite( &personne1 , sizeof(personne1) , 1 , fichier );
Si on essaie malgré cela, d'utiliser fwrite comme indiqué ci-dessus, cela va sauvegarder les adresses des chaines pointées par 'nom' et 'prenom'. Qui seront, après la fermeture du programme, non signifiantes. Et leur utilisation aboutirait à un SEGFAULT ou ACCESS VIOLATION à coup sûr (sauf cas de chance

).
Comment faire alors dans de tels cas

?
2 solutions sont possibles :
Solution 1 :
Inscrire séparément l'age, le nom et le prénom. Dans ce cas je vous conseille vivement d'utiliser fprintf comme vous l'avez appris
Solution 2 :
Procéder à une sérialisation de nos données (age, nom et prénom).
C'est quoi une sérialisations ?
Une sérialisation de données est un terme informatique qui consiste à mettre toutes nos données en série (les unes à la suite des autres) dans un seul buffer pour être envoyées par réseau ou être inscrites dans un flux. Ceci peut être vu comme une concaténation

.
Donc une sérialisation de notre structure donnerait :
| 4 octets |
5 octets |
5 octets |
| 15
|
"TOTO\0"
|
"TATA\0"
|
Ce buffer sera donc à enregistrer dans le fichier par fwrite.
Je ne rentre pas dans les détails car ceci impliquerait l'écriture d'un tutoriel. Si vous êtes intéressés, vous pouvez faire des recherches sur les techniques de sérialisation de données

.
En conclusion :
L'enregistrement en mode binaire, n'est pas adéquat avec de telles déclarations. Pour y parvenir il faut, quand vous en avez la possibilité, déclarer statiquement les champs d'une structure pour pouvoir la sauvegarder en mode binaire (avec fwrite) sans problèmes et sans manœuvres particulières (sérialisation ou autres).
Mon but n'étant pas de vous inciter à déclarer toujours statiquement vos données (tableaux en particulier), car très souvent on déclare plus qu'on en a besoin. Ce qui n'est pas très optimisé.
Donc à vous de trouver le compromis idéal pour votre application, entre la simplicité de sauvegarde, et l'optimisation de mémoire.