Quelqu’un peut-il expliquer comment fonctionne le pointeur à pointeur?

Je ne comprends pas vraiment comment fonctionne le pointeur sur le pointeur. Une façon de faire le même travail sans utiliser pointeur à pointeur?

struct customer { char name[20]; char surname[20]; int code; float money; }; typedef struct customer customer; void inserts(customer **tmp) { *tmp = (customer *)malloc(sizeof(customer)); puts("Give me a customer name, surname code and money"); scanf("%s %s %d %f", (*tmp)->name, (*tmp)->surname, &(*tmp)->code, &(*tmp)->money); } 

Pointeurs vers pointeurs 101

Disons que vous avez une variable int x : int x; .

  • Une partie de la mémoire sera affectée à `x` suffisamment grande pour contenir un` int`.
  • La mémoire affectée à `x` a une adresse dans la mappe de mémoire de processus.
  • Pour voir l’adresse où `x` est situé en mémoire, utilisez:
    printf("x's memory address = %p\n", &x);
  • `& x` signifie l’adresse de x.
  • Pour voir la valeur entière stockée dans `x`, utilisez:
    printf("x=%d\n", x);
  • `x` ne peut être manipulé directement que dans son cadre d’existence. Par exemple, il peut être manipulé dans la fonction dans laquelle il est déclaré:
    x = 42;
  • En dehors de son cadre d’existence, la valeur de «x» peut être manipulée si son adresse mémoire est connue.
  • Si la valeur de `x` (c’est-à-dire: 42) est transmise à une fonction ( x ), cette fonction ne peut pas manipuler la valeur de` x`.
  • Si l’adresse de `x` est transmise à une fonction ( &x ), cette fonction peut manipuler la valeur de` x`.

Maintenant, disons que vous avez une variable de pointeur p qui suppose qu’elle pointe sur un int : int *p;

  • Une partie de la mémoire sera affectée à `p` suffisamment grande pour contenir une adresse mémoire.
  • La mémoire affectée à `p` a une adresse dans la mappe de mémoire de processus.
  • `& p` signifie` adresse de` p.
  • Pour voir l’adresse où `p` est situé en mémoire, utilisez:
    printf("p's memory addr = %p\n", &p
  • Pour voir l’adresse où `p` est pointé, utilisez:
    printf("Address where `p` is pointing: %p\n", p);
  • Pour voir le prétendu entier pointé, utilisez:
    printf("int = %d\n", *p);
  • La valeur de «p» est une adresse en mémoire; et `p` peut être configuré pour pointer sur n’importe quelle adresse, que cette adresse existe ou non dans la mappe de mémoire de processus.
  • L’adresse d’un object est un pointeur sur cet object. Par conséquent, pour que `p` pointe sur` x`:
    p = &x
  • Le type de pointeur (type int pour “p”) fait référence à tout ce que `p` désigne.
  • La quantité de mémoire affectée à `p` varie en fonction de l’architecture de la machine; et comment cela représente une adresse.
  • `p` ne peut être manipulé directement que dans son cadre d’existence. Par exemple, il peut être manipulé dans la fonction dans laquelle il est déclaré:
    p = &x; ou p = NULL
  • En dehors de son cadre d’existence, la valeur de «p» peut être manipulée si son adresse mémoire est connue.
  • Si la valeur de «p» (une adresse) est transmise à une fonction ( p ), cette fonction ne peut pas manipuler «p» pour changer sa position.
  • Si l’adresse de `p` est transmise à une fonction ( &p ), cette fonction peut manipuler ce que` p` pointe vers.

Supposons maintenant que vous avez un pointeur sur une variable de pointeur pp qui suppose qu’il pointe sur un pointeur sur un ‘int’: int **pp; … ou: inserts vides (client ** tmp ):

  • L’adresse d’un pointeur est un pointeur sur un pointeur.

entrez la description de l'image ici

Retour à la question

Q: Y a-t-il un moyen de faire le même travail sans utiliser un pointeur à l’autre?

Non. Supposons ce qui suit:

 void inserts(customer **tmp); ... { customer cust; custPtr custPtr = &cust; inserts(&custPtr); ... 

La fonction inserts() nécessite l’adresse d’un pointeur pour pouvoir manipuler où custPtr pointe.

Si à la place:

 void inserts2(customer *tmp); ... { customer cust; custPtr custPtr = &cust; inserts2(custPtr); ... 

insert2() obtiendrait une copie de la valeur de custPtr , qui correspond à l’adresse de cust . Par conséquent, insert2() pourrait modifier la ou les valeurs de cust , mais ne pourrait pas changer la direction custPtr .

Pour tout paramètre de type T , si vous souhaitez modifier la valeur du paramètre et que cette modification soit reflétée dans l’appelant, vous devez passer un pointeur:

 void foo( T *ptr ) { *ptr = new_value(); } void bar( void ) { T var; foo( &var ); // writes to var } 

Si T est un type de pointeur Q * , alors vous vous retrouvez avec un pointeur sur un pointeur:

 void foo( Q **ptr ) { *ptr = new_value(); } void bar( void ) { Q *var; foo( &var ); // writes to var } 

Vous pouvez utiliser typedefs pour masquer la pointerness de la variable, mais dans mon expérience, cacher des indicateurs derrière les typedefs est mauvais.

Vous pouvez modifier votre fonction pour renvoyer la valeur du pointeur au lieu d’écrire dans le paramètre, comme le suggère @Namfuak:

 Q *foo( void ) { Q *val = new_value(); return val; } void bar( void ) { Q *var; var = foo(); } 

Si un pointeur est une entrée de votre répertoire, un pointeur sur ce pointeur peut être pensé pour vous indiquer le répertoire dans lequel cette entrée existe.

En regardant votre code:

 struct customer { char name[20]; char surname[20]; int code; float money; }; 

Premièrement, vous ne devriez pas utiliser un type float pour de l’argent .

Votre question concerne C FAQ 4.8 . En gros, vous avez une fonction pour insérer un enregistrement client. Où voulez-vous insérer le disque? Quelque chose qui détient l’enregistrement évidemment, un annuaire téléphonique, une firebase database, etc. Ainsi, vous avez besoin d’un pointeur sur quelque chose qui contient des pointeurs pointeurs sur les objects que vous souhaitez insérer.

Maintenant, comme pour le rest de votre code, notez tout d’abord que vous ne devez pas convertir la valeur de retour de malloc . Deuxièmement, l’ utilisation de scanf un risque de sécurité grave . Troisièmement, vous allouez un nouvel enregistrement client dans la fonction inserts , mais vous ne pouvez pas communiquer d’échec au code qui appelle votre fonction.

On peut soutenir que le nom de la fonction devrait être quelque chose comme create_customer_interactive pour indiquer qu’elle fait plus que simplement insérer un enregistrement dans une liste d’enregistrements.

Voici comment structurer votre code:

 #include  #include  struct customer { char *name; char *surname; int code; int money; /* in cents */ }; typedef struct customer *PCustomer; int read_customer_record(FILE *fp, PCustomer customer) { /* dummy implementation */ customer->name = "A. Tester"; customer->surname = "McDonald"; customer->code = 123; customer->money = 100 * 100; return 1; } PCustomer insert_record_interactive(PCustomer *db, FILE *fp) { PCustomer tmp = malloc(sizeof(*tmp)); if (!tmp) { return NULL; } if (!read_customer_record(fp, tmp)) { free(tmp); return NULL; } *db = tmp; return tmp; } int main(void) { PCustomer new_customer; PCustomer *db = malloc(3 * sizeof(*db)); if (!db) { perror("Failed to allocate room for customer records"); exit(EXIT_FAILURE); } /* insert a record in the second slot */ new_customer = insert_record_interactive(&db[1], stdin); if (!new_customer) { perror("Failed to read customer record"); exit(EXIT_FAILURE); } printf( "%s %s (%d) : $%.2f\n", new_customer->name, new_customer->surname, new_customer->code, ((double)new_customer->money) / 100.0 ); return 0; } 

Plusieurs niveaux d’indirection peuvent causer de la confusion. Une bonne stratégie consiste à réduire la confusion en réduisant les niveaux d’indirection.

Les préoccupations légitimes liées à l’utilisation de scanf() les suivantes:

 void inserts(customer **tmp) { customer *new_cust = malloc(sizeof *new_cust); if ( !new_cust ) { fprintf(stderr, "Couldn't allocate memory.\n"); exit(EXIT_FAILURE); } puts("Give me a customer name, surname code and money"); scanf("%s %s %d %f", new_cust->name, new_cust->surname, new_cust->code, new_cust->money); /* We delay using multiple levels of indirection until we get to the very last line, here. */ *tmp = new_cust; } 

Comme d’autres l’ont mentionné, dans ce cas particulier, il serait probablement plus facile de renvoyer le pointeur.

 void inserts(customer *tmp[]) { *tmp = malloc(sizeof(customer)); puts("Give me a customer name, surname code and money"); scanf("%s %s %d %f", (*tmp)->name, (*tmp)->surname, &(*tmp)->code, &(*tmp)->money); } 

C’est équivalent. Un pointeur sur un pointeur peut également être exprimé en tant que pointeur sur un tableau de taille inconnue.

Qu’est-ce qui se passe est que * tmp est affecté à un tableau de 1 éléments de type “client”. tmp lui-même fait référence dans les deux sens (** tmp et * tmp []) au pointeur d’un tableau de taille inconnue d’éléments “client”.