5
votes

Allocation dynamique de mémoire pour un tableau de caractères 2D

J'utilise malloc pour allouer dynamiquement un tableau de caractères 2D. À moins de définir chaque index de tableau sur NULL avant free () , j'obtiens un défaut de segmentation en essayant de free () . Pourquoi ai-je une erreur de segmentation?

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

int main(void) {

    int nrows = 20; int ncolumns = 10;
    char ** arr = malloc(nrows * sizeof(char *));
    for(int i = 0; i < nrows; i++)
        arr[i] = malloc(ncolumns * sizeof(char));

    arr[0] = "string1";
    arr[1] = "string2";

    // does not work without the following code:
    // for(int i = 0; i < 20; i++)
    //     arr[i] = NULL;

    for(int i = 0; i < 20; i++)
        free(arr[i]);
    free(arr);

    return 0;
}

c

6 commentaires

arr [0] = "string1"; Vous réaffectez le pointeur. Utilisez plutôt strcpy () .


Notez que [comme d'autres l'ont mentionné], vous avez besoin (par exemple) de strcpy (arr [0], "string1"); Mais, notez que "string1" doit être non plus de 9 caractères (c'est-à-dire ncolonnes - 1 ). Donc sois prudent. Si votre deuxième boucle a fait: arr [i] = NULL; , vous pouvez faire: arr [0] = strdup ("string1"); comme alternative. Ensuite, votre boucle libre pourrait faire: if (arr [i]! = NULL) free (arr [i]); Faire strdup ici est généralement mieux car cela permet string dans le tableau pour avoir la longueur dont il a besoin vs être contraint à une longueur fixe. Ainsi, il accueille des chaînes plus courtes ou plus longues avec un minimum de mémoire gaspillée


@CraigEstey nous avons donc besoin que dans son cas arr [i] point à empiler donc il! = NULL et nous pouvons libérer librement un pinter NULL (ça ne fait rien) peut-être que vous voulez stocker votre texte dans le premier arr [0] alors utilisez strncpy au lieu de strcpy (recommandé par d'autres): pour éviter un débordement de tampon.


@ Et7f3XIV Oui, free (NULL) est inoffensif. Mais ça n'a pas toujours été le cas, alors parfois j'oublie. Mais, je ne ferais pas non plus strcpy / strncpy mais, comme je l'ai dit, strdup . Sinon, le char ** arr n'a pas d'intérêt. Nous ferions: char arr [nrows] [ncolumns] et strcpy / strncpy . Je suis surpris par les suggestions d'utiliser strncpy car d'autres répondants sur d'autres pages ont dit ne pas l'utiliser [sans (par exemple) ajouter / forcer le terminateur EOS après le appel].


Ce n'est pas un tableau 2D. C'est un tableau de pointeurs vers un groupe de tableaux 1d. Consultez Allocation correcte de tableaux multidimensionnels .


@Craig Estey mon commentaire sur strncpy est pour l'exemple que vous avez donné (si nous n'avons pas de limite, nous pouvons avoir un dépassement de tampon qui est une faille de sécurité, c'est comme utiliser gets au lieu de fgets en 2019). Et vous pouvez voir dans ma réponse que je libère après les 2 premières cellules (mais vous avez raison, il est plus pratique de libérer un pointeur et de stocker le pointeur retourné par strdup). J'ajouterai +1 pour vous ^^


5 Réponses :


1
votes
L'opérateur

= ne copie pas la chaîne, il affecte uniquement le pointeur. Donc, votre mémoire malléable n'est plus accessible pour ces éléments de tableau et tenter de la libérer est un comportement indéfini qui peut conduire à l'erreur de segmentation.

Vous devez le copier en utilisant strcpy .


6 commentaires

La troisième réponse qui suggère d'utiliser le strcpy () extrêmement dangereux. Plus tôt nous oublions que strcpy () a jamais existé, mieux c'est. Utilisez plutôt strdup () .


@cmaster ce n'est évidemment pas la vérité. N'essayez pas d'utiliser strdup lors de la programmation de microcontrôleurs.


en passant, utilisez strncpy au lieu de strcpy: pour éviter un débordement de tampon.


Si vous utilisez strncpy , assurez-vous également de définir explicitement le dernier caractère sur un terminateur nul. linux.die.net/man/3/strncpy


strncpy est vraiment dangereux :)


Ok, strcpy () est là pour rester pour les cas d'utilisation où nous sommes obligés de l'utiliser. Cependant, c'est une très mauvaise habitude d'utiliser strcpy () partout où cela semble aller. Ce ne devrait être qu'un dernier recours, et même dans ce cas, chaque utilisation de strcpy () nécessite une preuve écrite dans les commentaires de la raison pour laquelle son utilisation est légitime. Pensez simplement au nombre de hacks rendus possibles grâce à l'utilisation de strcpy () et d'amis!



3
votes

Lorsque vous faites cela:

strcpy(arr[0], "string1");
strcpy(arr[1], "string2");

Vous écrasez le contenu de arr [0] et arr [1] , qui contiennent les adresses de mémoire renvoyées par malloc , avec l'adresse de deux constantes chaîne. Cela provoque une fuite de mémoire, car cette mémoire n'est plus accessible. C'est aussi la raison pour laquelle vous plantez lorsque vous appelez free parce que ces variables ne contiennent plus les adresses de la mémoire allouée.

Ce que vous voudrez probablement faire ici à la place est d'utiliser strcpy , qui copiera le contenu de la chaîne littérale dans la mémoire que vous avez allouée.

arr[0] = "string1";
arr[1] = "string2";

Vous pouvez maintenant libérer la mémoire correctement .


3 commentaires

Non. N'utilisez pas strcpy () . Il remplira volontiers vos tampons. Utilisez plutôt strdup () .


@cmaster strcpy convient si vous savez à quoi vous avez affaire, c'est-à-dire que la source est une constante de chaîne et que vous connaissez la taille de la destination. strdup peut ne pas être approprié en fonction de la situation, et avec strncpy vous devez ajouter manuellement l'octet nul à la fin si la source est trop grande.


C'est précisément ce que le PO ne semble pas savoir. Je sais que je peux utiliser strcpy () de manière relativement sûre. Mais cette capacité ne vient qu'au prix de ne jamais vouloir l'utiliser, à moins d'être forcé. Si vous souhaitez toujours utiliser strcpy () sur strdup () , vous n'avez tout simplement pas encore appris la leçon.



1
votes

Deux choses à savoir:

  • Vous avez deux zones en mémoire (pour faciliter la compréhension): pile et pile
  • malloc, realloc, calloc allouent une ressource à partir du tas. Je dirai seulement malloc (mais c'est pareil)
    • free ne peut libérer que des ressources du tas. Stack est un reserver pour le compilateur (il stocke l'appel de fonction et d'autres données)

La règle pour chaque ressource que vous obtenez de malloc vous devez la libérer. pour libérer appelez simplement la fonction libre (mais nous pouvons éventuellement attribuer un pointeur nul pour être sûr qu'il est libéré).

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

int main(void) {

    int nrows = 20; int ncolumns = 10;
    char ** arr = malloc(nrows * sizeof(char *));
    for(int i = 0; i < nrows; i++)
        arr[i] = malloc(ncolumns * sizeof(char));
    free(arr[0]);//know we can loose it value because it is freed
    arr[0] = NULL;// in fact we assign a value just after so this line is useless but is educationnal purpose
    free(arr[1]);//know we can loose it value because it is freed
    arr[1] = NULL;// in fact we assign a value just after so this line is useless but is educationnal purpose
    arr[0] = "string1";
    arr[1] = "string2";

    // does not work without the following code:
    // for(int i = 0; i < 20; i++)
    //     arr[i] = NULL;

    for(int i = 2; i < 20; i++)//we start at 2 because the first two value are on the stack
    {
        free(arr[i]);
        arr[i] = NULL;//this is useless because we will free arr just after the loop)
    }
    free(arr);
    arr = NULL;// this is useless because we exit the end of program

    return 0;
}

pour libérer

free(a);/* this is mandatory */
a = NULL;/* we can add this to the first line */


0 commentaires

3
votes

Votre code est correct, le problème vient du fait que vous attribuez ici des chaînes littéraux à votre tableau: arr [0] = "string1"; .

Vous remplacez ainsi le pointeur sur arr [0] , qui pointe vers la mémoire allouée, par le pointeur vers une chaîne littérale.

Les pointeurs vers les littéraux sont protégés , vous ne pouvez pas les libérer (ni leur écrire) car vous ne les avez pas alloués.

Pour résoudre ce problème, utilisez strcpy pour copier la valeur de votre littéral dans votre alloué mémoire:

strcpy(arr[0], "string1");
strcpy(arr[1], "string2");


1 commentaires

Même problème qu'avec la réponse de dbush: strcpy () est très dangereux et ne doit pas être utilisé sans preuve d'utilisation correcte. Utilisez plutôt strdup () , et vous vous épargnerez de nombreux maux de tête.



1
votes

Un crash lors d'un appel à free est le signe d'une mauvaise gestion de la mémoire ailleurs dans votre code. Lorsque vous définissez un pointeur sur NULL puis sur free , vous n'allez pas planter, car free (NULL) est garanti inoffensif par la norme C § 7.22.3.3:

7.22.3.3 La fonction libre

... Si ptr est un pointeur nul, aucune action ne se produit . Sinon, si l'argument ne correspond pas à un pointeur précédemment renvoyé par une gestion mémoire fonction , ou si l'espace a été libéré par un appel à free ou à réallouer, le le comportement n'est pas défini.

Insistez sur moi.

Comme d'autres réponses l'ont noté, vous essayez d'appeler free sur une mémoire que vous n'avez pas explicitement allouée avec des fonctions familiales malloc (depuis que vous avez écrasé arr [i] pointeurs avec des pointeurs vers des chaînes littérales)


0 commentaires