Pourquoi est-ce que j'obtiens une double erreur libre avec realloc ()?

J'ai essayé d'écrire une fonction de remplacement de chaîne dans C, qui fonctionne sur un char * , qui a été alloué en utilisant malloc() . C'est un peu différent en ce sens qu'il trouvera et remplacera des chaînes, plutôt que des caractères dans la chaîne de départ.

C'est trivial à faire si les chaînes de recherche et de remplacement ont la même longueur (ou si la chaîne de remplacement est plus courte que la chaîne de recherche), car j'ai suffisamment d'espace alloué. Si j'essaie d'utiliser realloc() , j'obtiens une erreur qui me dit que je fais un double gratuit - que je ne vois pas comment je suis, puisque j'utilise seulement realloc ( ) .

Peut-être qu'un petit code aidera:

void strrep(char *input, char *search, char *replace) {
    int searchLen = strlen(search);
    int replaceLen = strlen(replace);
    int delta = replaceLen - searchLen;
    char *find = input;

    while (find = strstr(find, search)) {

        if (delta > 0) {
            realloc(input, strlen(input) + delta);
            find = strstr(input, search);            
        }

        memmove(find + replaceLen, find + searchLen, strlen(input) - (find - input));
        memmove(find, replace, replaceLen);
    }
}

Le programme fonctionne, jusqu'à ce que j'essaie de realloc() dans une instance où la chaîne remplacée sera plus longue que la chaîne initiale. (Ça fonctionne encore, ça crache juste les erreurs aussi bien que le résultat).

Si cela aide, le code appelant ressemble à:

#include 
#include 
#include 

void strrep(char *input, char *search, char *replace);

int main(void) {
    char *input = malloc(81);

    while ((fgets(input, 81, stdin)) != NULL) {
        strrep(input, "Noel", "Christmas");
    }
}
0

12 Réponses

Vous pouvez acheter modules WiFi avec une interface série , mais ils sont un peu cher. Fondamentalement, vous les contrôlez via UART, et vous leur envoyez des commandes AT similaires à la façon dont vous contrôliez les modems d'accès à distance dans le passé.

WiFi module

5
ajouté
C'est une excellente idée, et j'ai même trouvé un tableau de bord avec cela pour faciliter l'intégration. Mes seules réserves étaient avec les exigences de puissance et la taille. Merci pour votre suggestion! Je m'en souviendrai certainement pour de futurs projets :-)
ajouté l'auteur Mario Marinato, source

Expansion un peu plus.

Davr a suggéré ce que j'allais suggérer aussi. Roving Networks fabrique également un bon ensemble de modules BT.

Étendre. ZigBee est un protocole distinct du Wi-Fi. ZigBee est similaire à BT en termes de puissance rayonnée, ce qui se rapporte à une grande partie de la portée, mais est différent en ce sens qu'il est conçu pour permettre à plusieurs nœuds de créer un réseau, chaque nœud étendant efficacement le réseau par sa portée. J'espère que cela éclaircit qu'ils sont similaires mais pas liés.

3
ajouté
Merci pour l'info. A aidé à éclaircir quelques questions que j'avais :)
ajouté l'auteur Mario Marinato, source

Je voulais juste partager ma solution actuelle avec vous tous:

Après avoir discuté avec Marcus et Madeleine chez LittleBirdElectronics, nous avons trouvé la solution suivante:

1) Xbee sur le lilypad,

2) USB Arduino, Ethernet Sheild et Xbee combo pour transférer des messages vers le WWW.

Ceci est juste l'une des nombreuses solutions possibles, mais pour moi cela semble fournir les avantages de la faible puissance et les exigences de taille sur la fin de lilypad en utilisant le xbee au lieu d'utiliser 802.11 directement sur le lilypad.

D'ailleurs, j'avais déjà un blindage Arduino et Ethernet inutilisé, et je voulais une excuse pour jouer avec xbee quand même! ;-)

Merci à tous pour votre contribution! C'était très utile pour concevoir une solution.

3
ajouté
Nous avons considéré les deux, mais le module que nous avons trouvé était cher et à l'étranger. Étant donné que j'avais déjà un Arduino et un bouclier Ethernet, cela est devenu un peu plus simple :-) Merci beaucoup pour votre suggestion!
ajouté l'auteur Mario Marinato, source
Je pense que la solution proposée par moi serait moins chère. Mais peut-être que Votre est plus solide, compact et plus facile à construire. Et vous le matériel papa déjà.
ajouté l'auteur Chris Bunch, source

Puis-je utiliser un module XBee et interagir avec mon routeur domestique?

Oui mais vous devez connecter le module XBee à votre routeur. Vous pouvez essayer de trouver un port série sur votre carte de routeur ou un adaptateur USB vers série si votre routeur est équipé d'un port USB. Votre routeur devrait également fonctionner sous Linux (probablement openWRT).

1
ajouté

En règle générale, vous ne devriez jamais faire un free ou un realloc sur un tampon fourni par l'utilisateur. Vous ne savez pas où l'utilisateur a alloué l'espace (dans votre module, dans une autre DLL), vous ne pouvez donc utiliser aucune des fonctions d'allocation sur un tampon utilisateur.

Pourvu que vous ne puissiez pas effectuer de réallocation dans votre fonction, vous devez changer un peu son comportement, comme faire un seul remplacement, afin que l'utilisateur puisse calculer la longueur maximale de la chaîne résultante et vous fournir un tampon assez long pour celui-ci remplacement à se produire.

Vous pouvez ensuite créer une autre fonction pour effectuer les remplacements multiples, mais vous devrez allouer l'espace entier pour la chaîne résultante et copier la chaîne d'entrée de l'utilisateur. Ensuite, vous devez fournir un moyen de supprimer la chaîne que vous avez allouée.

Résultant en:

void  strrep(char *input, char *search, char *replace);
char* strrepm(char *input, char *search, char *replace);
void  strrepmfree(char *input);
0
ajouté

Mes astuces rapides.

Instead of:
void strrep(char *input, char *search, char *replace)
try:
void strrep(char *&input, char *search, char *replace)

and than in the body:
input = realloc(input, strlen(input) + delta);

Généralement lire sur le passage des arguments de la fonction comme valeurs / référence et description realloc() :).

0
ajouté
La notation void strrep (char * & entrée, char * recherche, char * remplacer) n'est pas valide dans C? Bien qu'il soit valide en C ++. La question n'est pas, et AFAICT n'a jamais été, marqué avec C ++. Au mieux, le code devrait être void strrep (entrée char **, char * search, char * replace) , bien qu'il soit facile d'argumenter que char * strrep (const char * entrée, const char * search, const char * replace) est une interface exploitable (les chaînes d'entrée ne sont pas modifiées, la chaîne modifiée est allouée et renvoyée).
ajouté l'auteur Jonathan Leffler, source

Notez que vous devez modifier votre code pour supprimer les codes d'échappement html.

Eh bien, même si j'ai utilisé C / C ++ il y a longtemps, realloc qui se développe ne réutilise que la valeur du pointeur mémoire s'il y a de la place en mémoire après votre bloc d'origine.

Par exemple, considérez ceci:

(xxxxxxxxxx ..........)

Si votre pointeur pointe vers le premier x, et. signifie l'emplacement de la mémoire libre, et vous augmentez la taille de la mémoire pointée par votre variable de 5 octets, ça va réussir. Ceci est bien sûr un exemple simplifié car les blocs sont arrondis à une certaine taille pour l'alignement, mais de toute façon.

Cependant, si vous essayez par la suite de l'augmenter de 10 octets supplémentaires, et qu'il n'y en a que 5 disponibles, il faudra déplacer le bloc en mémoire et mettre à jour votre pointeur.

Cependant, dans votre exemple, vous passez la fonction un pointeur sur le caractère, pas un pointeur vers votre variable, et donc, alors que la fonction strrep peut ajuster la variable utilisée, c'est une variable locale à la fonction strrep et votre code d'appel sera laissé avec la valeur de la variable de pointeur d'origine.

Cette valeur de pointeur a cependant été libérée.

Dans votre cas, l'entrée est le coupable.

Cependant, je ferais une autre suggestion. Dans votre cas, il semble que la variable input soit effectivement entrée, et si c'est le cas, elle ne devrait pas être modifiée du tout.

Je vais donc essayer de trouver une autre façon de faire ce que vous voulez faire, sans changer input , car les effets secondaires comme celui-ci peuvent être difficiles à repérer.

0
ajouté

Cela semble fonctionner;

char *strrep(char *string, const char *search, const char *replace) {
    char *p = strstr(string, search);

    if (p) {
        int occurrence = p - string;
        int stringlength = strlen(string);
        int searchlength = strlen(search);
        int replacelength = strlen(replace);

        if (replacelength > searchlength) {
            string = (char *) realloc(string, strlen(string) 
                + replacelength - searchlength + 1);
        }

        if (replacelength != searchlength) {
            memmove(string + occurrence + replacelength, 
                        string + occurrence + searchlength, 
                        stringlength - occurrence - searchlength + 1);
        }

        strncpy(string + occurrence, replace, replacelength);
    }

    return string;
}

Soupir, y a-t-il de toute façon à poster du code sans que ça suce?

0
ajouté
Ajouter un commentaire, puisque le commentaire a été écrit comme une réponse, avant que le commentaire ne soit disponible: Cela semble changer seulement la première occurrence. Ce qui est probablement raisonnable, puisque je n'ai pas vraiment dit qu'il fallait tout changer!
ajouté l'auteur Matthew Schinckel, source

Quelqu'un d'autre s'est excusé d'être en retard à la fête - il y a deux mois et demi. Eh bien, je passe beaucoup de temps à faire de l'archéologie logicielle.

Je suis intéressé que personne n'a commenté explicitement la fuite de mémoire dans la conception originale, ou l'erreur off-by-one. Et c'est en observant la fuite de mémoire qui me dit exactement pourquoi vous obtenez l'erreur double-libre (parce que, pour être précis, vous libérez la même mémoire plusieurs fois - et vous le faites après piétinement de la mémoire déjà libérée).

Avant de procéder à l'analyse, je suis d'accord avec ceux qui disent que votre interface est moins que stellaire; Toutefois, si vous avez traité les problèmes de fuite / de piétinement de la mémoire et documenté l'exigence «doit être alloué de la mémoire», cela pourrait être «OK».

Quels sont les problèmes? Eh bien, vous passez un tampon à realloc (), et realloc() vous renvoie un nouveau pointeur vers la zone que vous devez utiliser - et vous ignorez cette valeur de retour. Par conséquent, realloc() a probablement libéré la mémoire d'origine, puis vous lui passez à nouveau le même pointeur, et il se plaint que vous libérez deux fois le même mémoire parce que vous lui passez à nouveau la valeur d'origine. Cela non seulement fuit la mémoire, mais signifie que vous continuez à utiliser l'espace d'origine - et le tir de John Downey dans le noir indique que vous utilisez abusivement realloc (), mais ne souligne pas à quel point vous le faites. Il y a aussi une erreur "off-by-one" car vous n'allouez pas suffisamment d'espace pour le '\ 0' NUL qui termine la chaîne.

La fuite de mémoire se produit car vous ne fournissez pas de mécanisme pour indiquer à l'appelant la dernière valeur de la chaîne. Parce que vous avez continué à piétiner la chaîne d'origine plus l'espace après, le code a fonctionné, mais si votre code d'appel libérait de l'espace, il obtiendrait aussi une erreur double-free, ou bien un fichier core ou équivalent. les informations de contrôle de la mémoire sont complètement brouillées.

Votre code ne protège pas non plus contre une croissance indéfinie - pensez à remplacer 'Noel' par 'Joyeux Noel'. À chaque fois, vous ajouteriez 7 caractères, mais vous trouveriez un autre Noel dans le texte remplacé, et l'étendriez, et ainsi de suite. Mon correctif (ci-dessous) ne résout pas ce problème - la solution simple est probablement de vérifier si la chaîne de recherche apparaît dans la chaîne de remplacement; une alternative est d'ignorer la chaîne de remplacement et de poursuivre la recherche après celle-ci. La seconde a quelques problèmes de codage non triviaux à résoudre.

Donc, ma suggestion de révision de votre fonction appelée est:

char *strrep(char *input, char *search, char *replace) {
    int searchLen = strlen(search);
    int replaceLen = strlen(replace);
    int delta = replaceLen - searchLen;
    char *find = input;

    while ((find = strstr(find, search)) != 0) {
        if (delta > 0) {
            input = realloc(input, strlen(input) + delta + 1);
            find = strstr(input, search);            
        }

        memmove(find + replaceLen, find + searchLen, strlen(input) + 1 - (find - input));
        memmove(find, replace, replaceLen);
    }

    return(input);
}

Ce code ne détecte pas les erreurs d'allocation de mémoire - et se bloque probablement (mais sinon, fuit la mémoire) si realloc() échoue. Voir le livre 'Writing Solid Code' de Steve Maguire pour une discussion approfondie des problèmes de gestion de la mémoire.

0
ajouté
Merci, c'est une très bonne analyse de ce que je faisais de mal (et que le double-libre était en quelque sorte un sous-produit de plusieurs choses que je faisais mal.) Je pense que j'avais dans ma tête ce realloc ( ) juste prolongé l'allocation de mémoire - ce qui n'a aucun sens du tout, quand j'y pense!
ajouté l'auteur Matthew Schinckel, source

realloc est étrange, compliqué et ne devrait être utilisé que pour traiter beaucoup de mémoire plusieurs fois par seconde. c'est-à-dire - où il rend votre code plus rapide.

J'ai vu le code où

realloc(bytes, smallerSize);

a été utilisé et travaillé pour redimensionner le tampon, le rendant plus petit. Travaillé environ un million de fois, alors pour une raison quelconque, realloc a décidé que même si vous étiez en train de raccourcir le tampon, cela vous donnerait une nouvelle copie sympa. Donc, vous vous écrasez dans un endroit aléatoire 1/2 seconde après que les mauvaises choses sont arrivées.

Utilisez toujours la valeur de retour de realloc.

0
ajouté

Juste un coup dans l'obscurité parce que je ne l'ai pas encore essayé mais quand vous réalisez il renvoie le pointeur beaucoup comme malloc. Étant donné que realloc peut déplacer le pointeur si nécessaire, vous utilisez probablement un pointeur non valide si vous ne procédez pas comme suit:

input = realloc(input, strlen(input) + delta);
0
ajouté
Et si realloc échoue, il renvoie NULL et laisse le tampon existant seul. Vous venez de perdre le pointeur ... :-(
ajouté l'auteur Roger Lipscombe, source

Tout d'abord, désolé, je suis en retard à la fête. Ceci est ma première réponse stackoverflow. :)

Comme cela a été souligné, lorsque realloc() est appelé, vous pouvez potentiellement changer le pointeur vers la mémoire en cours de réallocation. Lorsque cela se produit, l'argument "chaîne" devient invalide. Même si vous le réaffectez, la modification est hors de portée une fois la fonction terminée.

Pour répondre à l'OP, realloc() renvoie un pointeur vers la mémoire nouvellement réallouée. La valeur de retour doit être stockée quelque part. Généralement, vous feriez ceci:

data *foo = malloc(SIZE * sizeof(data));
data *bar = realloc(foo, NEWSIZE * sizeof(data));

/* Test bar for safety before blowing away foo */
if (bar != NULL)
{
   foo = bar;
   bar = NULL;
}
else
{
   fprintf(stderr, "Crap. Memory error.\n");
   free(foo);
   exit(-1);
}

Comme TyBoer le fait remarquer, vous ne pouvez pas changer la valeur du pointeur qui est passé en entrée de cette fonction. Vous pouvez assigner ce que vous voulez, mais le changement sera hors de portée à la fin de la fonction. Dans le bloc suivant, "input" peut ou non être un pointeur invalide une fois la fonction terminée:

void foobar(char *input, int newlength)
{
   /* Here, I ignore my own advice to save space. Check your return values! */
   input = realloc(input, newlength * sizeof(char));
}

Mark essaie de contourner ce problème en renvoyant le nouveau pointeur en tant que sortie de la fonction. Si vous faites cela, il incombe à l'appelant de ne plus jamais utiliser le pointeur qu'il a utilisé pour l'entrée. Si elle correspond à la valeur de retour, alors vous avez deux pointeurs au même endroit et vous n'avez qu'à appeler free() sur l'un d'entre eux. Si elles ne correspondent pas, le pointeur d'entrée pointe maintenant vers la mémoire qui peut appartenir ou non au processus. Le déréférencement pourrait provoquer une erreur de segmentation.

Vous pouvez utiliser un double pointeur pour l'entrée, comme ceci:

void foobar(char **input, int newlength)
{
   *input = realloc(*input, newlength * sizeof(char));
}

Si l'appelant a une copie du pointeur d'entrée quelque part, ce doublon peut être invalide maintenant.

Je pense que la solution la plus propre ici est d'éviter d'utiliser realloc() en essayant de modifier l'entrée de l'appelant de la fonction. Juste malloc() un nouveau tampon, renvoyez-le, et laissez l'appelant décider de libérer ou non l'ancien texte. Cela a l'avantage supplémentaire de laisser l'appelant garder la chaîne d'origine!

0
ajouté