Dans le code suivant, la taille du tableau est définie sur 20. Dans Valgrind, le code teste clean. Mais dès que je change la taille à 30, cela me donne des erreurs (voir ci-dessous). La partie qui me déroute est que je peux changer la valeur à 40 et les erreurs disparaissent. Changez-le à 50, erreurs à nouveau. Ensuite, 60 tests propres et ainsi de suite. Ça continue comme ça. J'espérais donc que quelqu'un pourrait m'expliquer cela. Parce que cela ne m'est pas tout à fait clair malgré mes meilleurs efforts pour m'envelopper la tête. Ces erreurs étaient difficiles à identifier car le code par toutes apparences était valide.
valgrind --leak-check=full --show-leak-kinds=all\
--track-origins=yes ./fileio
==6675== Memcheck, a memory error detector
==6675== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6675== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==6675== Command: ./fileio
==6675==
==6675== Syscall param write(buf) points to uninitialised byte(s)
==6675== at 0x496A818: write (in /usr/lib/libc-2.28.so)
==6675== by 0x48FA85C: _IO_file_write@@GLIBC_2.2.5 (in /usr/lib/libc-2.28.so)
==6675== by 0x48F9BBE: new_do_write (in /usr/lib/libc-2.28.so)
==6675== by 0x48FB9D8: _IO_do_write@@GLIBC_2.2.5 (in /usr/lib/libc-2.28.so)
==6675== by 0x48F9A67: _IO_file_sync@@GLIBC_2.2.5 (in /usr/lib/libc-2.28.so)
==6675== by 0x48EEDB0: fflush (in /usr/lib/libc-2.28.so)
==6675== by 0x109288: main (fileio.c:24)
==6675== Address 0x4a452d2 is 34 bytes inside a block of size 4,096 alloc'd
==6675== at 0x483777F: malloc (vg_replace_malloc.c:299)
==6675== by 0x48EE790: _IO_file_doallocate (in /usr/lib/libc-2.28.so)
==6675== by 0x48FCBBF: _IO_doallocbuf (in /usr/lib/libc-2.28.so)
==6675== by 0x48FBE47: _IO_file_overflow@@GLIBC_2.2.5 (in /usr/lib/libc-2.28.so)
==6675== by 0x48FAF36: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib/libc-2.28.so)
==6675== by 0x48EFBFB: fwrite (in /usr/lib/libc-2.28.so)
==6675== by 0x10924C: main (fileio.c:19)
==6675== Uninitialised value was created by a stack allocation
==6675== at 0x109199: main (fileio.c:11)
==6675==
==6675==
==6675== HEAP SUMMARY:
==6675== in use at exit: 0 bytes in 0 blocks
==6675== total heap usage: 2 allocs, 2 frees, 4,648 bytes allocated
==6675==
==6675== All heap blocks were freed -- no leaks are possible
==6675==
==6675== For counts of detected and suppressed errors, rerun with: -v
==6675== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Erreurs Valgrind:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct record {
int number;
char text[30];
};
int main(int argc, char *argv[])
{
FILE *file = fopen("testfile.bin", "w+");
if (ferror(file)) {
printf("%d: Failed to open file.", ferror(file));
}
struct record rec = { 69, "Some testing" };
fwrite(&rec, sizeof(struct record), 1, file);
if (ferror(file)) {
fprintf(stdout,"Error writing file.");
}
fflush(file);
fclose(file);
}
3 Réponses :
Au départ, j'ai mal interprété la sortie de valgrind, donc @ chux mérite d'être accepté. Je vais essayer de trouver la meilleure réponse possible.
La première erreur (celle que je n'ai pas immédiatement prise en compte) est de vérifier la valeur renvoyée par fopen (3) avec ferror (3) . L'appel fopen (3) renvoie NULL en cas d'erreur (et définit errno ), donc en vérifiant NULL avec ferror (3) est faux.
Avec l'initialisation vous écrivez tous les champs de votre structure, mais vous n'initialisez pas toute la mémoire qu'elle couvre. Votre compilateur peut par exemple laisser un peu de remplissage dans la structure, afin d'obtenir de meilleures performances lors de l'accès aux données. Au fur et à mesure que vous écrivez la structure entière sur le fichier, vous passez en fait des données non initialisées à la fonction fwrite (3) .
En changeant la taille du tableau, vous changez celle de Valgrind comportement. Cela est probablement dû au fait que le compilateur modifie la disposition de la structure en mémoire et utilise un remplissage différent.
Essayez d'effacer la variable rec avec memset (& rec, 0, sizeof (rec)); et Valgrind devraient arrêter de se plaindre. Cela ne résoudra que le symptôme : puisque vous sérialisez des données binaires, vous devez marquer struct record avec __attribute __ ((compressé)) .
Votre initialisation d'origine est bonne.
Une autre façon d'initialiser les données est d'utiliser strncpy (3) . Strncpy acceptera comme paramètres un pointeur vers la destination à écrire, un pointeur vers le bloc de mémoire source (d'où les données doivent être extraites) et la taille d'écriture disponible.
En utilisant strncpy (& rec. text, "hello world", sizeof (rec.text) vous écrivez "hello world" sur le tampon rec.text . Mais vous devez faire attention à la terminaison de la chaîne: strncpy n'écrira pas au-delà de la taille donnée, et si la chaîne source est plus longue que cela, il n'y aura pas de terminateur de chaîne.
Strncpy peut être utilisé en toute sécurité comme suit
strncpy(&rec.text, "hello world", sizeof(rec.text) - 1); rec.text[sizeof(rec.text) - 1] = '\0';
La première ligne copie "hello world" dans la chaîne cible. sizeof (rec.text) - 1 est passé comme size, de sorte que nous laissez de la place pour le terminateur \ 0 , qui est écrit explicitement comme dernier caractère pour couvrir le cas où sizeof (rec.text) est plus court que "hello world".
Enfin, les notifications d'erreur doivent aller à stderr , tandis que stdout est pour les résultats.
Mais notez que je peux changer 30 à 40, du coup ça n'envoie plus d'octets initialisés. La taille 50 fait, 60 pas.
struct record rec = {69, "Some testing"}; me semble initialisé.
Ne vous concentrez pas là-dessus. Des comportements non définis se produisent lorsque la mémoire n'est pas initialisée.
"Avec l'initialisation, vous n'écrivez pas tous les octets du champ de texte." et "vous manipulez en fait des données non initialisées." sont incorrects. L'initialisation est soit tout ou rien, pas partielle en C. struct record rec = {69, "Some testing"}; initialise tout rec . L'erreur d'OP est toute autre construction, file est NULL
@chux, oui, je devrais reformuler cela.
Je suis étudiant, je n'ai pas encore utilisé __attribute __ ((emballé)) ou autre. Je vais l'examiner. C'était quelque chose que je faisais pour m'entraîner davantage à lire et à écrire des fichiers et les erreurs de mémoire dans Valgrind m'ont déconcerté et étaient difficiles à réduire. J'essaie d'appliquer votre suggestion de memset, mais cela le fait donc j'obtiens une affectation à une erreur d'expression pendant que j'essaye de créer une chaîne vers rec.text. Je ne peux pas faire une initialisation régulière car j'ai dû diviser cela pour insérer le memtest.
La vérification (file == NULL) n'a aucune incidence sur les erreurs valgrind.
@ lee8oi - Pour affecter correctement la chaîne, utilisez strncpy (& rec.text, "Some testing", sizeof (rec.text)) . Cela vous permettra de séparer la déclaration et la cession. Attention: text est un tableau d'octets, et il y a une certaine ambiguïté syntaxique entre les tableaux et les pointeurs qui déroutent généralement les novices. Au fur et à mesure que vous progresserez dans l'apprentissage du C, vous comprendrez :)
Et strncpy () est toujours un mauvais conseil (enfin: presque toujours)
@wildplasser compare strncpy et strcpy . strncpy est bon si vous savez ce que vous faites. Bien sûr, le terminateur nul peut être laissé de côté, s'il n'y a pas d'espace.
@Dacav ne peut toujours pas affecter une expression à un type de tableau. Pouvez-vous me montrer un exemple de ce à quoi cette section devrait ressembler avec cette commande strncpy? fileio.c: 20: 14: erreur: affectation à une expression avec le type de tableau rec.text = strncpy (& rec.text, "Quelques tests", sizeof (rec.text));
@ lee8oi - struct record rec; strncpy (& rec.text, "quelques tests", sizeof (rec.text) - 1); rec.text [sizeof (rec.text) - 1] = '\ 0'; `Mais si vous corrigez votre vérification d'erreur et déclarez une structure compressée, votre code est bon. Vous pouvez éviter strncpy
Ah, je pensais que je devais aussi le renvoyer au texte rec. Duh. Je suppose que je dois continuer à lire.
strncpy me permet d'utiliser la suggestion memset, mais ne prend pas en charge les erreurs.
Cette réponse semble avoir changé 100 fois lol. Mais c'est utile dans sa forme finale que je vois ici. J'avais le sentiment qu'il y avait des octets supplémentaires utilisés qui ne correspondaient pas à un caractère complet (ou quelque chose), alors que la taille du tableau n'était pas «égale». Mais je n'ai pas très bien connecté les points. Merci de rester avec moi et de travailler si dur sur votre réponse.
@ lee8oi - bien sûr, pas de problème. Je ne savais pas trop ce qui avait causé l'erreur, et je l'ai simplement inventée en corrigeant la réponse et en éliminant la confusion que j'avais faite. C'est juste une question de responsabilité :)
[modifier]
Ma réponse concerne une faiblesse dans le code de l'OP, mais le write (buf) Valgrind pointe vers des octets non initialisés est dû à d'autres raisons auxquelles d'autres ont répondu.
/ p>
Lorsque l'ouverture échoue, ferror (fichier) est comportement non défini (UB).
si (ferror (file)) n'est pas le bon test pour déterminer le succès de l'ouverture.
FILE *file = fopen("testfile.bin", "w+");
// if (ferror(file)) {
// printf("%d: Failed to open file.", ferror(file));
// }
if (file == NULL) {
printf("Failed to open file.");
return -1; // exit code, do not continue
}
Je ne vois pas d'autres erreurs évidentes.
ferror (fichier) est utile pour tester le résultat des E / S, pas de l'ouverture d'un fichier.
@Dacav Peut-être. Pourtant, if (ferror (NULL)) est un comportement indéfini . Sans une bonne vérification du fichier , le reste du code est discutable.
Merci, je m'en souviendrai à l'avenir et je serai sûr de lire plus attentivement les valeurs de retour / etc.
Il s'avère qu'OP est un troll géant, heh @chux: D
J'ai changé mon code pour inclure ce correctif également. Je me suis déjà rendu compte que c'était une bonne suggestion, j'ai juste noté que ce n'était pas la réponse ou la solution au problème que j'essayais de ne pas comprendre.
Le problème est qu'il y a un remplissage dans la structure pour rendre le int a toujours aligné par 4 en mémoire, même dans un tableau de struct record < / code> s. Maintenant, 20 + 4 est divisible par 4, tout comme 40 + 4 et 60 + 4. Mais 30 + 4 et 50 + 4 ne le sont pas. Par conséquent, 2 octets de remplissage doivent être ajoutés pour rendre la sizeof (struct record) divisible par 4.
Lorsque vous exécutez le code avec une taille de tableau 34, La solution est en fait de ne pas écrire la structure en utilisant P.S. la route de l'enfer est pavée de P.P.S. sizeof (struct record) == 36 et les octets 35 et 36 contiennent des valeurs indéterminées - même si struct record code> est sinon complètement initialisé. Pire encore, le code qui écrit des valeurs indéterminées peut laisser échapper des informations sensibles - le bug Heartbleed en est un excellent exemple. >
fwrite . Écrivez plutôt les membres individuellement - cela améliore également la portabilité. Il n'y a pas non plus beaucoup de différence de performances, car fwrite tampons les écritures, tout comme fread .
struct s emballés, vous voulez les éviter comme la peste dans le code générique.
ferror (file) ne sera presque certainement jamais vrai juste après fopen - et en cas d'échecs normaux, fopen renverra NULL et ferror (NULL) entraînera probablement un crash.
Je pense que vous avez raison en ce que la divisibilité par 4 varie avec l'erreur OP, mais fwrite (& rec, sizeof (struct record), 1, file); convient en soi quelle que soit la taille de rec . Je soupçonne l'UB de if (ferror (NULL)) est simplement de se manifester de diverses manières - ou l'OP ne nous montre pas quelque chose d'important.
@chux Je suis à peu près sûr que ferror (NULL) mènera à SIGSEGV - et que OP n'a jamais atteint cela.
Il est vrai que SIGSEGV , un événement / signal standard non C, est raisonnable à attendre d'UB.
Je suis d'accord que les plaintes de Valgrind proviennent probablement du rembourrage final. "write (buf) pointe vers des octets non initialisés"
C initialise-t-il le remplissage de la structure à zéro? traite des informations pertinentes.
Compte tenu de la réponse liée, IMO, comme tout remplissage est initialisé à zéro bit; Valgrind "write (buf) pointe vers des octets non initialisés" semble sans fondement.
Eh bien, implémenté la suggestion fwrite sur le code d'origine et cela fonctionne en effet sans erreurs. Maintenant, pour écrire le code pour relire le fichier dans une structure. Merci!