web-dev-qa-db-fra.com

C free (): pointeur invalide

J'apprends moi-même C. Mon objectif est de créer une fonction C qui parcourt simplement une chaîne de requête et se divise sur l'esperluette et le signe égal. Je suis bloqué sur cette erreur de Valgrind.

==5411== Invalid free() / delete / delete[] / realloc()
==5411==    at 0x402AC38: free (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==5411==    by 0x804857C: main (leak.c:28)
==5411==  Address 0x420a02a is 2 bytes inside a block of size 8 free'd
==5411==    at 0x402AC38: free (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==5411==    by 0x804857C: main (leak.c:28)
==5411== 
==5411== 
==5411== HEAP SUMMARY:
==5411==     in use at exit: 0 bytes in 0 blocks
==5411==   total heap usage: 1 allocs, 2 frees, 8 bytes allocated
==5411== 
==5411== All heap blocks were freed -- no leaks are possible
==5411== 
==5411== For counts of detected and suppressed errors, rerun with: -v
==5411== ERROR SUMMARY: 20 errors from 9 contexts (suppressed: 0 from 0)

et le backtrace:

*** Error in `./leak': free(): invalid pointer: 0x08c1d00a ***
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(+0x767c2)[0xb75f17c2]
/lib/i386-linux-gnu/libc.so.6(+0x77510)[0xb75f2510]
./leak[0x804857d]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf5)[0xb7594905]
./leak[0x8048421]
======= Memory map: ========
08048000-08049000 r-xp 00000000 08:05 262764     /home/danny/dev/c-qs-parser/leak
08049000-0804a000 r--p 00000000 08:05 262764     /home/danny/dev/c-qs-parser/leak
0804a000-0804b000 rw-p 00001000 08:05 262764     /home/danny/dev/c-qs-parser/leak
08c1d000-08c3e000 rw-p 00000000 00:00 0          [heap]
b757a000-b757b000 rw-p 00000000 00:00 0 
b757b000-b7729000 r-xp 00000000 08:05 1312132    /lib/i386-linux-gnu/libc-2.17.so
b7729000-b772b000 r--p 001ae000 08:05 1312132    /lib/i386-linux-gnu/libc-2.17.so
b772b000-b772c000 rw-p 001b0000 08:05 1312132    /lib/i386-linux-gnu/libc-2.17.so
b772c000-b772f000 rw-p 00000000 00:00 0 
b772f000-b774a000 r-xp 00000000 08:05 1312589    /lib/i386-linux-gnu/libgcc_s.so.1
b774a000-b774b000 r--p 0001a000 08:05 1312589    /lib/i386-linux-gnu/libgcc_s.so.1
b774b000-b774c000 rw-p 0001b000 08:05 1312589    /lib/i386-linux-gnu/libgcc_s.so.1
b774c000-b7750000 rw-p 00000000 00:00 0 
b7750000-b7751000 r-xp 00000000 00:00 0          [vdso]
b7751000-b7771000 r-xp 00000000 08:05 1312116    /lib/i386-linux-gnu/ld-2.17.so
b7771000-b7772000 r--p 0001f000 08:05 1312116    /lib/i386-linux-gnu/ld-2.17.so
b7772000-b7773000 rw-p 00020000 08:05 1312116    /lib/i386-linux-gnu/ld-2.17.so
bfe93000-bfeb4000 rw-p 00000000 00:00 0          [stack]
Aborted (core dumped)

voici enfin le code:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {

    //char p[] = "t=quote&k=id&v=10";
    char p[] = "t=quote";

    char* token;
    char* tk; 
    char* s;
    unsigned short int found;

    s = strdup(p);

    if (s != NULL) {
        while ((token = strsep(&s, "&")) != NULL) {

            found = 0;

            printf("TOKEN: %s\n\n", token);

            while ((tk = strsep(&token, "=")) != NULL) {

                printf("TK: %s\n\n", tk);

                free(tk);
            }   

            free(token);
        }   
    }   

    free(s);

    return 0;
}

Merci

15
user964491

Vous essayez de libérer quelque chose qui n'est pas un pointeur vers une adresse mémoire "libre". Ce n'est pas parce qu'une chose est une adresse que vous devez ou la libérer .

Il existe deux principaux types de mémoire que vous semblez confondre: la mémoire de pile et la mémoire de tas.

  • Stack la mémoire vit dans la durée de vie de la fonction. C'est un espace temporaire pour des choses qui ne devraient pas devenir trop grandes. Lorsque vous appelez la fonction main, elle met de la mémoire de côté pour les variables que vous avez déclarées (p, token, etc.).

  • Heap la mémoire vit de quand vous malloc à quand vous free elle. Vous pouvez utiliser beaucoup plus de mémoire de tas que vous ne pouvez empiler de mémoire. Vous devez également en faire le suivi - ce n'est pas aussi simple que la mémoire de pile!

Vous avez quelques erreurs:

  • Vous essayez de libérer de la mémoire qui n'est pas un tas de mémoire. Ne fais pas ça.

  • Vous essayez de libérer l'intérieur d'un bloc de mémoire. Lorsque vous avez en fait alloué un bloc de mémoire, vous ne pouvez le libérer que du pointeur renvoyé par malloc. C'est-à-dire niquement depuis le début du bloc. Vous ne pouvez pas libérer une partie du bloc de l'intérieur.

Pour votre morceau de code ici, vous voudrez probablement trouver un moyen de copier la partie pertinente de la mémoire ailleurs ... dites un autre bloc de mémoire que vous avez mis de côté. Ou vous pouvez modifier la chaîne d'origine si vous le souhaitez (indice: la valeur char 0 est le terminateur nul et indique aux fonctions comme printf d'arrêter la lecture de la chaîne).

EDIT: La fonction malloc alloue de la mémoire de tas *.

"9.9.1 Le malloc et les fonctions libres

La bibliothèque standard C fournit un allocateur explicite connu sous le nom de package malloc. Les programmes allouent des blocs à partir du tas en appelant la fonction malloc. "

~ Computer Systems: A Programmer's Perspective, 2e édition, Bryant et O'Hallaron, 2011

EDIT 2: * La norme C ne spécifie en fait rien sur le tas ou la pile. Cependant, pour quiconque apprend sur un ordinateur de bureau/ordinateur portable pertinent, la distinction est probablement inutile et déroutante, surtout si vous apprenez comment votre programme est stocké et exécuté. Lorsque vous vous retrouvez à travailler sur quelque chose comme un microcontrôleur AVR comme H2CO3, il vaut vraiment la peine de noter toutes les différences qui, d'après ma propre expérience avec les systèmes embarqués, s'étendent bien au-delà de l'allocation de mémoire.

34
GraphicsMuncher

D'où vous est venue l'idée que vous deviez free(token) et free(tk)? Non. strsep() n'alloue pas de mémoire, il ne renvoie que des pointeurs à l'intérieur de la chaîne d'origine. Bien sûr, ce ne sont pas des pointeurs alloués par malloc() (ou similaire), donc free() leur est un comportement indéfini. Vous n'avez besoin de free(s) que lorsque vous avez terminé avec la chaîne entière.

Notez également que vous n'avez pas besoin d'allocation de mémoire dynamique pas du tout dans votre exemple. Vous pouvez éviter tout à fait strdup() et free() en écrivant simplement char *s = p;.

11
user529758

Vous ne pouvez pas appeler free sur les pointeurs renvoyés par strsep. Ce ne sont pas des chaînes allouées individuellement, mais simplement des pointeurs dans la chaîne s que vous avez déjà allouée. Lorsque vous avez terminé avec s, vous devez le libérer, mais vous n'avez pas à le faire avec les valeurs de retour de strsep.

3
Tim Pierce