2010-02-23 6 views
9

Je sais que cela ne fonctionnera pas parce que la variable x est détruit lorsque la fonction retourne:de retour des données déclarées en fonction

int* myFunction() 
{ 
    int x = 4; return &x; 
} 

alors comment puis-je retourner correctement un pointeur vers quelque chose que je crée au sein de la fonction , et de quoi dois-je m'occuper? Comment éviter les fuites de mémoire?

J'ai aussi utilisé malloc:

int* myFunction2() 
{ 
    int* x = (int*)malloc(sizeof int); *x = 4; return x; 
} 

Comment vous correctement faire - en C et C++?

+3

Vous avez raison, la réponse est 'myFunction2()' vous devez juste vous souvenir de libérer votre mémoire plus tard. C'est le problème de ne pas avoir un garbage collector – tzenes

+4

Encore une fois, alors que C et C++ partagent de nombreuses fonctionnalités, il y a beaucoup de questions avec des réponses complètement différentes selon la langue. Comment éviter les fuites de mémoire est l'un d'eux, car il est de savoir comment créer quelque chose à l'intérieur d'une fonction ... Dans quelle langue êtes-vous vraiment intéressé? –

Répondre

6

Votre deuxième approche est correcte. Vous avez juste besoin de documenter clairement que l'appelant "possède" le pointeur de résultat, et est responsable de le libérer.

En raison de cette complexité supplémentaire, il est rare de faire cela pour les "petits" types comme int, même si je suppose que vous venez d'utiliser un int ici pour un exemple.

Certaines personnes préfèreront également prendre un pointeur vers un objet déjà alloué en tant que paramètre plutôt que d'allouer l'objet en interne. Cela rend plus clair que l'appelant est responsable de désallouer l'objet (puisqu'ils l'ont d'abord attribué), mais rend le site d'appel un peu plus verbeux, donc c'est un compromis.

5

Pour C++, dans de nombreux cas, retour juste par valeur. Même dans le cas d'objets plus grands, RVO évitera fréquemment la copie inutile.

1

En C++, vous devez utiliser new:

int *myFunction() 
{ 
    int blah = 4; 
    return new int(blah); 
}

Et pour vous en débarrasser, utilisez supprimer:

int main(void) 
{ 
    int *myInt = myFunction(); 
    // do stuff 
    delete myInt; 
}

Notez que j'invoquer le constructeur de copie pour int en utilisant new , de sorte que la valeur "4" est copiée dans la mémoire de tas. La seule façon d'obtenir un pointeur sur quelque chose sur la pile de manière fiable est de le copier sur le tas en appelant correctement new.

EDIT: Comme indiqué dans une autre réponse, vous devrez également documenter que le pointeur doit être libéré par l'appelant plus tard. Sinon, vous pourriez avoir une fuite de mémoire.

2

Approche C++ pour éviter les fuites de mémoire. (Au moins lorsque vous ignorez la sortie de fonction)

std::auto_ptr<int> myFunction() { 
    std::auto_ptr<int> result(new int(4)); 
    return result; 
} 

donnons le nom:

std::auto_ptr<int> myFunctionResult = myFunction(); 

EDIT: Comme l'a souligné Joel. std :: auto_ptr a ses propres inconvénients et devrait généralement être évité. Au lieu de std :: auto_ptr Vous pouvez utiliser boost :: shared_ptr (std :: tr1 :: shared_ptr).

boost::shared_ptr<int> myFunction() { 
    boost::shared_ptr<int> result(new int(5)); 
    return result; 
} 

ou lors de l'utilisation du compilateur conforme C++ 0x Vous pouvez utiliser std :: unique_ptr.

std::tr1::unique_ptr<int> myFunction() { 
    std::tr1::unique_ptr<int> result(new int(5)); 
    return result; 
} 

La principale différence est que:

  • shared_ptr permet à plusieurs instances de pointage shared_ptr au même pointeur RAW. Il utilise un mécanisme de comptage de références pour s'assurer que la mémoire ne sera pas libérée tant qu'au moins une instance de shared_ptr existe. Unique_ptr n'autorise qu'une seule instance de son pointeur de maintien, mais a un vrai sémantique de déplacement contrairement à auto_ptr.

+0

auto_ptr est généralement mal vu parce qu'il a une sémantique bizarre. Lorsque vous passez ce résultat à une fonction, vous ne le possédez plus et vous ne pouvez plus y accéder. – Joel

+0

Vous avez raison et je suis entièrement d'accord. C'est pourquoi j'ai écrit (au moins lorsque vous ignorez la sortie de la fonction). Une meilleure solution est par exemple boost :: shared_ptr mais j'ai suggéré une solution actuellement standarized. Et oui je sais à propos de std :: tr1 :: shared_ptr – lollinus

+0

@Joel: Si vous le passez à une autre fonction, cette fonction ne prendra probablement pas un auto_ptr. Donc, vous utiliseriez 'f (myFunctionResult.get())' tout en conservant la propriété. Si la fonction prend un auto_ptr, il est clair que la propriété est transférée. –

7

Pour C++, vous pouvez utiliser un pointeur intelligent pour appliquer le transfert de propriété. auto_ptr ou boost::shared_ptr sont de bonnes options.

3

Une possibilité passe la fonction d'un pointeur:

void computeFoo(int *dest) { 
    *dest = 4; 
} 

C'est agréable parce que vous pouvez utiliser cette fonction avec une variable automatique:

Avec cette approche
int foo; 
computeFoo(&foo); 

vous gardez aussi la mémoire gestion dans la même partie du code, à savoir. vous ne pouvez pas manquer un malloc juste parce qu'il se passe quelque part dans une fonction:

// Compare this: 
int *foo = malloc(…); 
computeFoo(foo); 
free(foo); 

// With the following: 
int *foo = computeFoo(); 
free(foo); 

Dans le second cas, il est plus facile d'oublier la libre que vous ne voyez pas le malloc. Cela est souvent au moins partiellement résolu par convention, par exemple: "Si un nom de fonction commence par XY, cela signifie que vous possédez les données qu'il retourne."

Un cas intéressant de retour du pointeur vers la variable "function" est la déclaration la variable statique:

int* computeFoo() { 
    static int foo = 4; 
    return &foo; 
} 

Bien sûr, cela est mauvais pour la programmation normale, mais il pourrait être utile un jour.

+0

statique est bon; c'est triste ce n'est pas = malloc – xealits

1

Il existe une autre approche - déclarer x statique. Dans ce cas, il sera situé dans le segment de données, pas sur la pile, donc il est disponible (et persistant) pendant l'exécution du programme.

int *myFunction(void) 
{ 
    static int x = 4; 
    return &x; 
} 

S'il vous plaît noter que l'affectation x=4 sera effectuée uniquement sur le premier appel de myFunction:

int *foo = myFunction(); // foo is 4 
*foo = 10;     // foo is 10 
*foo = myFunction();  // foo is 10 

NB! L'utilisation de variables statiques de portée de fonction n'est pas une technique sûre.

+0

Pourquoi la downvote? Ma réponse est complètement correcte. – qrdl

0

Les pointeurs partagés Boost ou TR1 sont généralement la solution. Il évite le surcoût de copie et vous donne une suppression semi-automatique. Donc, votre fonction devrait ressembler à:

boost::shared_ptr<int> myFunction2() 
{ 
    boost::shared_ptr<int> x = new int; 

    *x = 4; 
    return x; 
} 

L'autre option est juste d'autoriser une copie. Ce n'est pas trop grave si l'objet est petit (comme celui-ci) ou vous pouvez organiser pour créer l'objet dans l'instruction return. Le compilateur optimisera généralement une copie si l'objet est créé dans l'instruction return.

0

Je voudrais essayer quelque chose comme ceci:

int myFunction2b(int * px) 
{ 
    if(px) 
    { 
    *px = 4; 
    return 1; 
    } 

    // Choice 1: Assert or Report Error 
    // Choice 2: Allocate memory for x. Caller has to be written accordingly. 

    // My choice is 1 
    assert(0 && "Argument is NULL pointer"); 
    return 0; 

} 
-1

Vous vous demandez comment retourner correctement un pointeur. C'est la mauvaise question, car ce que vous devriez faire est d'utiliser des pointeurs intelligents plutôt que des pointeurs bruts. scoped_ptr et shared_ptr (disponible en boost et TR1) sont de bons conseils pour regarder (par exemple here et here)

Si vous besoin le pointeur brut pour quelque chose (par exemple, le passage à une fonction C), le get() méthode l'fournira.

Si vous devez créer des pointeurs bruts, par ex. pour les devoirs, vous pouvez utiliser malloc() (comme vous l'avez fait) ou nouvelle dans une fonction, et nous espérons que vous vous souvenez de désallouer la mémoire (par libre() et supprimer respectivement) Ou, dans un idiome légèrement moins susceptible de fuir, vous pouvez créer le pointeur avec nouveau, le passer à une fonction, et désallouer avec supprimer lorsque vous avez terminé avec elle. Encore une fois, cependant, utilisez des pointeurs intelligents.

1

Votre second extrait de code est correct.

Pour aider à éviter les fuites de mémoire, je laisse les conventions de codage m'aider. XxxCreate() allouera de la mémoire pour xxx et l'initialisera. XxxDelete() va détruire/corrompre xxx et le libérer.

xxxInit() initialise xxx (jamais allouer) xxxDestroy() détruira/corrompus xxx (jamais gratuit)

De plus, j'essaie d'ajouter le code pour supprimer/détruire/gratuit dès que j'ajouter le code pour créer/init/malloc. Ce n'est pas parfait, mais je trouve que cela m'aide à différencier les éléments qui doivent être libérés et ceux qui ne le sont pas, tout en réduisant la probabilité que j'oublie de libérer quelque chose plus tard.

Questions connexes