2 votes

Déterminer la taille appropriée d'un tableau prédéfini en C ?

Dans le code suivant, la taille du tableau est fixée à 20. Dans Valgrind, le code est testé correctement. Mais dès que je change la taille à 30, il me donne des erreurs (voir ci-dessous). Ce qui me perturbe, c'est que je peux changer la valeur à 40 et les erreurs disparaissent. Je passe à 50 et les erreurs réapparaissent. Puis 60, les tests sont propres et ainsi de suite. Ça continue comme ça. J'espérais donc que quelqu'un pourrait m'expliquer cela. En effet, malgré tous mes efforts, je n'arrive pas à y voir clair. Ces erreurs sont difficiles à cerner car le code est apparemment valide.

#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);
}

Erreurs Valgrind :

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)

4voto

Antti Haapala Points 11542

Le problème est qu'il y a un rembourrage dans la structure pour faire le int a toujours alignés par 4 en mémoire, même dans un tableau de struct record s. Or, 20+4 est divisible par 4, de même que 40+4 et 60+4. Mais 30+4 et 50+4 ne le sont pas. C'est pourquoi il faut ajouter 2 octets de remplissage pour rendre le sizeof (struct record) divisible par 4.

Lorsque vous exécutez le code avec un tableau de taille 34, sizeof (struct record) == 36 et les octets 35 et 36 contiennent des valeurs indéterminées - même si le struct record est par ailleurs entièrement initialisé. Pire encore, le code qui écrit des valeurs indéterminées peut laisser échapper des informations sensibles - la Bogue Heartbleed en est un excellent exemple.

La solution consiste en fait à no écrire la structure en utilisant fwrite . Au lieu de cela, écrivez les membres individuellement, ce qui améliore également la portabilité. Il n'y a pas non plus de grande différence en termes de performances, car fwrite tampons les écritures, et il en va de même pour les fread .


P.S. Le chemin de l'enfer est pavé d'emballages struct il faut les éviter comme la peste dans le code générique.


P.P.S. ferror(file) ne sera presque certainement jamais vraie juste après fopen - et en cas de défaillance normale fopen renverra NULL y ferror(NULL) conduira probablement à un crash.

3voto

chux Points 13185

[modifier]

Ma réponse est liée à une faiblesse dans le code de l'OP, mais le test Valgrind write(buf) points to uninitialized byte(s) est due à d'autres raisons auxquelles d'autres ont répondu.


En cas d'échec de l'ouverture, ferror(file) es comportement non défini (UB).

if (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(file) est utile pour tester le résultat des entrées-sorties, et non de l'ouverture d'un fichier.

0voto

Dacav Points 2536

J'ai initialement mal interprété la sortie de valgrind, donc celle de @chux mérite d'être acceptée. Je vais essayer d'apporter la meilleure réponse possible.

Vérification des erreurs

La première erreur (celle que je n'ai pas immédiatement envisagée) consiste à vérifier la valeur renvoyée par fopen(3) con ferror(3) . Les fopen(3) retour d'appel NULL en cas d'erreur (et met errno ), de sorte que la vérification des NULL con ferror(3) est erronée.

Sérialisation d'une structure sur un fichier.

Lors de 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. Lorsque vous écrivez toute la structure dans le fichier, vous passez en fait des données non initialisées à la fonction fwrite(3) fonction.

En modifiant la taille du tableau, vous modifiez le comportement de Valgrind. Cela est probablement dû au fait que le compilateur modifie la disposition de la structure en mémoire, et qu'il utilise un rembourrage différent.

Essayez d'essuyer le rec variable avec memset(&rec, 0, sizeof(rec)); et Valgrind devrait cesser de se plaindre. Cela ne corrigera que les symptôme cependant : puisque vous sérialisez des données binaires, vous devez marquer struct record con __attribute__((packed)) .

Initialisation de la mémoire

Votre initialisation initiale est bonne.

Une autre façon d'initialiser les données est d'utiliser la fonction strncpy(3) . Strncpy accepte comme paramètres un pointeur sur la destination à écrire, un pointeur sur 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 rec.text tampon. Mais vous devez faire attention à la fin 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 transmise en tant que taille, de manière à laisser de la place pour la fonction \0 terminateur, qui est écrit explicitement comme dernier caractère pour couvrir le cas dans lequel sizeof(rec.text) est plus court que "hello world".

Points faibles

Enfin, les notifications d'erreur doivent être envoyées à stderr , tandis que stdout c'est pour les résultats.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X