web-dev-qa-db-fra.com

Comment puis-je allouer de la mémoire et la renvoyer (via un paramètre de pointeur) à la fonction appelante?

J'ai du code dans quelques fonctions différentes qui ressemble à ceci:

void someFunction (int *data) {
  data = (int *) malloc (sizeof (data));
}

void useData (int *data) {
  printf ("%p", data);
}

int main () {
  int *data = NULL;

  someFunction (data);

  useData (data);

  return 0;
}

someFunction () et useData () sont définis dans des modules distincts (fichiers * .c).

Le problème est que, même si malloc fonctionne correctement et que la mémoire allouée est utilisable dans someFunction, la même mémoire n'est plus disponible une fois la fonction retournée.

Un exemple d'exécution du programme peut être vu ici , avec une sortie montrant les différentes adresses mémoire.

Quelqu'un peut-il m'expliquer ce que je fais mal ici, et comment faire fonctionner ce code?


EDIT: Il semble donc que je doive utiliser des pointeurs doubles pour ce faire - comment pourrais-je faire la même chose quand j'ai réellement besoin d'utiliser double pointeurs? Donc par exemple les données sont

int **data = NULL; //used for 2D array

Dois-je alors utiliser des pointeurs triples dans les appels de fonction?

31
a_m0d

Vous souhaitez utiliser un pointeur à pointeur:

void someFunction (int **data) {
  *data = malloc (sizeof (int));
}

void useData (int *data) {
  printf ("%p", data);
}

int main () {
  int *data = NULL;

  someFunction (&data);

  useData (data);

  return 0;
}

Pourquoi? Eh bien, vous voulez changer votre pointeur data dans la fonction principale. En C, si vous voulez changer quelque chose qui est passé en paramètre (et que ce changement apparaisse dans la version de l'appelant), vous devez passer un pointeur sur ce que vous voulez changer. Dans ce cas, "quelque chose que vous voulez changer" est un pointeur - donc pour pouvoir changer ce pointeur, vous devez utiliser un pointeur à pointeur ...

Notez qu'en plus de votre problème principal, il y avait un autre bogue dans le code: sizeof(data) vous donne le nombre d'octets requis pour stocker le pointeur (4 octets sur un OS 32 bits ou 8 octets sur un 64 -bit OS), alors que vous voulez vraiment le nombre d'octets requis pour stocker ce vers quoi le pointeur pointe (un int, soit 4 octets sur la plupart des OS). Parce que typiquement sizeof(int *)>=sizeof(int), cela n'aurait probablement pas causé de problème, mais c'est quelque chose dont il faut être conscient. J'ai corrigé cela dans le code ci-dessus.

Voici quelques questions utiles sur les pointeurs à pointeurs:

Comment fonctionne le pointeur vers les pointeurs en C?

tilise plusieurs niveaux de déréférences de pointeur?

58
Martin B

Un piège courant, surtout si vous avez déplacé le formulaire Java vers C/C++

N'oubliez pas que lorsque vous passez un pointeur, il passe par valeur, c'est-à-dire que la valeur du pointeur est copiée. C'est bon pour apporter des modifications aux données pointées par le pointeur mais toutes les modifications apportées au pointeur lui-même sont juste locales car c'est une copie !!

L'astuce consiste à utiliser le pointeur par référence, car vous voulez le changer, c'est-à-dire le malloc, etc.

** pointeur -> effrayera un programmeur noobie C;)

8
Sid Sarasvati

Vous devez passer un pointeur sur le pointeur si vous souhaitez modifier le pointeur.

c'est à dire. :

void someFunction (int **data) {
  *data = malloc (sizeof (int)*ARRAY_SIZE);
}

edit: Ajout de ARRAY_SIZE, à un moment donné, vous devez savoir combien d'entiers vous souhaitez allouer.

4
Ben

En effet, les données du pointeur sont transmises par valeur à someFunction.

int *data = NULL;
//data is passed by value here.
someFunction (data); 
//the memory allocated inside someFunction  is not available.

Le pointeur vers le pointeur ou le retour du pointeur alloué résoudrait le problème.

void someFunction (int **data) {
  *data = (int *) malloc (sizeof (data));
}


int*  someFunction (int *data) {
  data = (int *) malloc (sizeof (data));
return data;
}
2
aJ.

someFunction () prend son paramètre comme int *. Ainsi, lorsque vous l'appelez depuis main (), une copie de la valeur que vous avez transmise a été créée. Tout ce que vous modifiez à l'intérieur de la fonction est cette copie et donc les changements ne seront pas reflétés à l'extérieur. Comme d'autres l'ont suggéré, vous pouvez utiliser int ** pour obtenir les modifications reflétées dans les données. Une autre façon de le faire est de retourner int * à partir de someFunction ().

2
Naveen

Outre l'utilisation de la technique du double pointeur, s'il n'y a qu'un seul paramètre de retour, la réécriture est la suivante:

 int *someFunction () {
   return (int *) malloc (sizeof (int *));
 }

et l'utiliser:

 int *data = someFunction ();
2
Toad

Voici le schéma général d'allocation de mémoire dans une fonction et de retour du pointeur via un paramètre:

void myAllocator (T **p, size_t count)
{
  *p = malloc(sizeof **p * count);
}
...
void foo(void)
{
  T *p = NULL;
  myAllocator(&p, 100);
  ...
}

Une autre méthode consiste à faire du pointeur la valeur de retour de la fonction (ma méthode préférée):

T *myAllocator (size_t count)
{
  T *p = malloc(sizeof *p * count);
  return p;
}
...
void foo(void)
{
  T *p = myAllocator(100);
  ...
}

Quelques notes sur la gestion de la mémoire:

  1. La meilleure façon d'éviter les problèmes de gestion de la mémoire est d'éviter la gestion de la mémoire; ne détruisez pas la mémoire dynamique à moins que vous vraiment en ayez besoin.
  2. Ne convertissez pas le résultat de malloc () sauf si vous utilisez une implémentation antérieure à la norme ANSI 1989 ou si vous avez l'intention de compiler le code en C++. Si vous oubliez d'inclure stdlib.h ou si vous n'avez pas de prototype pour malloc () dans la portée, la conversion de la valeur de retour supprimera un diagnostic de compilation précieux.
  3. Utilisez la taille de l'objet alloué au lieu de la taille du type de données (c'est-à-dire sizeof *p Au lieu de sizeof (T)); cela vous évitera quelques brûlures d'estomac si le type de données doit changer (par exemple de int à long ou flottant à double). Cela rend également le code un peu meilleur IMO.
  4. Isoler les fonctions de gestion de la mémoire derrière les fonctions d'allocation et de désallocation de niveau supérieur; ceux-ci peuvent gérer non seulement l'allocation mais aussi l'initialisation et les erreurs.
1
John Bode

Plutôt que d'utiliser le double pointeur, nous pouvons simplement allouer un nouveau pointeur et le renvoyer, pas besoin de passer le double pointeur car il n'est utilisé nulle part dans la fonction.

Revenir void * peut donc être utilisé pour tout type d'allocation.

void *someFunction (size_t size) {
    return  malloc (size);
}

et l'utiliser comme:

int *data = someFunction (sizeof(int));
0
GG.

Ici, vous essayez de modifier le pointeur, c'est-à-dire de "data == Null" à "data == 0xabcd", une autre mémoire que vous avez allouée. Donc, pour modifier les données dont vous avez besoin, passez l'adresse des données, c'est-à-dire & données.

void someFunction (int **data) {
  *data = (int *) malloc (sizeof (int));
}
0
Learner

En réponse à votre question supplémentaire que vous avez modifiée dans:

'*' indique un pointeur vers quelque chose. Donc '**' serait un pointeur vers un pointeur vers quelque chose, '***' un pointeur vers un pointeur vers un pointeur vers quelque chose, etc.

L'interprétation habituelle des `` données int ** '' (si les données ne sont pas un paramètre de fonction) serait un pointeur vers la liste des tableaux int (par exemple, `` int a [100] [100] '').

Vous devez donc d'abord allouer vos tableaux int (j'utilise un appel direct à malloc () pour des raisons de simplicité):

data = (int**) malloc(arrayCount); //allocate a list of int pointers
for (int i = 0; i < arrayCount; i++) //assign a list of ints to each int pointer
   data [i] = (int*) malloc(arrayElemCount);
0
karx11erx