web-dev-qa-db-fra.com

Les pointeurs comme arguments de fonction en C

Si je devais avoir ce code, par exemple:

int num = 5;
int *ptr = #

Quelle est la différence entre les deux fonctions suivantes?

void func(int **foo);
void func(int *foo); 

Où j'appelle la fonction:

func(&ptr); 

Je me rends compte que le premier des deux prend un pointeur sur un pointeur en tant que paramètre, tandis que le second ne prend qu'un pointeur.

Si je passe dans func(&ptr), je passe effectivement dans un pointeur. Quelle différence cela fait-il que le pointeur pointe sur un autre pointeur? 

Je crois que ce dernier donnera un avertissement d'incompatibilité, mais il semble que les détails n'ont pas d'importance tant que vous savez ce que vous faites. Il semble que par souci de lisibilité et de compréhension, le premier constitue une meilleure option (pointeur à deux étoiles), mais d’un point de vue logique, quelle est la différence? 

25
sherrellbc

Une règle raisonnable est que vous ne pouvez pas changer exactement ce qui est transmis est une manière telle que l'appelant voit le changement. Passer des indicateurs est la solution de contournement.

Pass By Value: void fcn(int foo)

En passant par valeur, vous obtenez une copie de la valeur. Si vous modifiez la valeur dans votre fonction, l'appelant voit toujours la valeur d'origine, quelles que soient vos modifications.

Passer par le pointeur à la valeur: void fcn(int* foo)

Passer par le pointeur vous donne une copie du pointeur - il pointe vers le même emplacement mémoire que l'original. Cet emplacement de mémoire est l'endroit où l'original est stocké. Cela vous permet de modifier la valeur pointée. Toutefois, vous ne pouvez pas modifier le pointeur réel sur les données, car vous n’avez reçu qu’une copie du pointeur.

Transmettre un pointeur à un autre pointeur: void fcn(int** foo)

Vous contournez ce qui précède en passant un pointeur vers un pointeur vers une valeur. Comme ci-dessus, vous pouvez modifier la valeur pour que l'appelant voie le changement, car il s'agit du même emplacement mémoire que celui utilisé par le code de l'appelant. Pour la même raison, vous pouvez remplacer le pointeur par la valeur. Cela vous permet d’allouer de la mémoire dans la fonction et de la restituer; &arg2 = calloc(len);. Vous ne pouvez toujours pas changer le pointeur sur le pointeur, car c'est la chose dont vous recevez une copie.

56
atk

La différence est simplement dite dans les opérations avec lesquelles le processeur gérera le code. la valeur elle-même n'est qu'une adresse dans les deux cas, c'est vrai. Mais comme l'adresse est déréférencée, il est important pour le processeur, et donc aussi pour le compilateur, de savoir, après le déréférencement, avec quoi elle sera traitée.

8
dhein

Si je devais avoir ce code, par exemple:

int num = 5;
int *ptr = #

Quelle est la différence entre les deux fonctions suivantes ?:

void func(int **foo);
void func(int *foo);

Le premier veut un pointeur sur un pointeur sur un int, le second veut un pointeur qui pointe directement sur un int.

Où j'appelle la fonction:

func(&ptr);

Comme ptr est un pointeur sur un int, &ptr est une adresse compatible avec un int **.

La fonction prenant un int * fera quelque chose de différent comme avec int **. Le résultat de la conversation sera complètement différent, conduisant à un comportement indéfini, pouvant provoquer un crash.

Si je passe dans func (& ptr), je passe effectivement dans un pointeur. Quelle différence cela fait-il que le pointeur pointe sur un autre pointeur?

               +++++++++++++++++++
adr1 (ptr):    +  adr2           +
               +++++++++++++++++++

               +++++++++++++++++++
adr2 (num):    +  42             +
               +++++++++++++++++++

Au adr2, nous avons une valeur int, 42.

À adr1, nous avons l'adresse adr2, ayant la taille d'un pointeur.

&ptr nous donne adr1, ptr, contient la valeur de &num, qui est adr2.

Si j'utilise adr1 en tant que int *, adr2 sera traité à tort comme un entier, conduisant à un nombre (éventuellement assez grand).

Si j'utilise adr2 en tant que int **, le premier déréférencement mène à 42, ce qui sera interprété de manière erronée comme une adresse et provoquera éventuellement le blocage du programme.

C’est plus que de l’optique d’avoir une différence entre int * et int **.

Je crois que ce dernier donnera un avertissement d'incompatibilité, 

... qui a un sens ...

mais il semble que les détails ne comptent pas tant que vous savez ce que vous faites.

Le faites vous?

Il semble que par souci de lisibilité et de compréhension, le premier constitue une meilleure option (pointeur à deux étoiles), mais d’un point de vue logique, quelle est la différence?

Cela dépend de ce que la fonction fait avec le pointeur.

6
glglgl

Il y a deux différences pratiques principales:

  1. Le passage d'un pointeur à un pointeur permet à la fonction de modifier le contenu de ce pointeur de manière à ce que l'appelant puisse voir. Un exemple classique est le deuxième argument de strtol(). Suite à un appel à strtol(), le contenu de ce pointeur doit pointer vers le premier caractère de la chaîne qui n'a pas été analysé pour calculer la valeur long. Si vous venez de passer le pointeur sur strtol(), alors toutes les modifications apportées seraient locales et il serait impossible d'informer l'appelant de la localisation. En transmettant l'adresse de ce pointeur, strtol() peut le modifier de manière visible pour l'appelant. C'est comme passer l'adresse d'une autre variable.

  2. Plus fondamentalement, le compilateur doit connaître le type pointé pour pouvoir déréférencer. Par exemple, lors de la déréférence d'un double *, le compilateur interprétera (sur une implémentation où double consomme 8 octets) les 8 octets commençant à l'emplacement de la mémoire comme la valeur du double. Mais, sur une implémentation 32 bits, lors de la déréférence d'un double **, le compilateur interprétera les 4 octets commençant à cet emplacement comme l'adresse d'un autre double. Lors de la déréférence d'un pointeur, le type pointé est la seule information dont dispose le compilateur sur la façon d'interpréter les données à cette adresse. Il est donc essentiel de connaître le type exact. C'est pourquoi il serait erroné de penser "elles sont tous des indicateurs, alors quelle est la différence "?

2
Paul Griffiths

Généralement, la différence indique que la fonction sera assignée au pointeur et que cette affectation ne devrait pas être uniquement locale à la fonction. Par exemple (et gardez à l'esprit ces exemples ont pour but d'examiner la nature de foo et non des fonctions complètes, pas plus que le code de votre message d'origine est censé être un vrai code de travail):

void func1 (int *foo) {
    foo = malloc (sizeof (int));
}

int a = 5;
func1 (&a);

Est similaire à

void func2 (int foo) {
    foo = 12;
}

int b = 5;
func2 (b);

En ce sens que foo peut être égal à 12 dans func2 (), mais lorsque func2 () revient, b sera toujours égal à 5. Dans func1 (), foo pointe sur un nouvel int, mais a est toujours a lorsque func1 () est renvoyé. 

Et si nous voulions changer la valeur de a ou b? WRT b, un int normal:

void func3 (int *foo) {
    *foo = 12;
}    

int b = 5;
func2 (&b);

Will work - notez qu'il nous fallait un pointeur sur un int. Pour changer la valeur in un pointeur (c'est-à-dire que l'adresse de l'int renvoie à, et pas seulement la valeur dans l'int, elle pointe vers):

void func4 (int **foo) {
    *foo = malloc (sizeof (int));
}

int *a;
foo (&a);

'a' pointe maintenant sur la mémoire renvoyée par malloc dans func4 (). L'adresse &a est l'adresse de a, un pointeur vers un int . Un pointeur int contient l'adresse d'un int. func4() prend l'adresse d'un pointeur int afin de pouvoir mettre l'adresse d'un int dans cette adresse, tout comme func3 () prend l'adresse d'un int pour pouvoir y mettre une nouvelle valeur int. 

C'est ainsi que les différents styles d'argument sont utilisés. 

Cela fait un moment que cette question a été posée, mais voici ce que je pense de ceci. J'essaie maintenant d'apprendre le C et les pointeurs confondent indéfiniment ... Je profite donc de ce temps pour clarifier les pointeurs sur les pointeurs, du moins pour moi. Voici comment j'y pense.
J'ai pris un exemple de ici :

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

int allocstr(int len, char **retptr)
{
    char *p = malloc(len + 1);  /* +1 for \0 */
    if(p == NULL)
        return 0;
    *retptr = p;
    return 1;
}

int main()  
{
    char *string = "Hello, world!";
    char *copystr;
    if(allocstr(strlen(string), &copystr))
        strcpy(copystr, string);
    else    fprintf(stderr, "out of memory\n");
    return 0;
}

Je me demandais pourquoi allocstr a besoin d'un double pointeur. Si c'est un pointeur, cela signifie que vous pouvez le transmettre et il sera changé après le retour ...
.__ Si vous faites cet exemple, cela fonctionne bien. Mais si vous changez le allocstr pour avoir juste un pointeur * au lieu de ** pointeur (et copystr au lieu de & copystr dans main), vous obtenez segmentation fault . Pourquoi? J'ai mis quelques printfs dans le code et cela fonctionne bien jusqu'à la ligne avec strcpy. Donc, je suppose qu'il n'a pas alloué de mémoire pour copystr. Encore une fois, pourquoi? 
Revenons à ce que signifie passer par pointeur. Cela signifie que vous transmettez l'emplacement de la mémoire et que vous pouvez y écrire directement la valeur souhaitée. Vous pouvez modifier la valeur car vous avez accès à l'emplacement mémoire de votre valeur.
De même, lorsque vous passez un pointeur à un pointeur, vous transmettez l’emplacement mémoire de votre pointeur, c’est-à-dire l’emplacement mémoire de votre emplacement mémoire. Et maintenant (pointeur vers pointeur), vous pouvez modifier l’emplacement de la mémoire, tout comme vous pourriez changer la valeur lorsque vous utilisiez uniquement un pointeur. 
La raison pour laquelle le code fonctionne est que vous transmettez l'adresse d'un emplacement de mémoire. La fonction allocstr modifie la taille de cet emplacement mémoire afin qu'il puisse contenir "Hello world!" et il retourne un pointeur sur cet emplacement mémoire. 
C’est vraiment la même chose que de passer un pointeur, mais au lieu d’une valeur, nous avons un emplacement de mémoire. 

0
adi