Pourquoi utiliser la double indirection? ou Pourquoi utiliser des pointeurs vers des pointeurs?

272

Quand utiliser une double indirection en C? Quelqu'un peut-il expliquer avec un exemple?

Ce que je sais, c'est qu'une double indirection est un pointeur vers un pointeur. Pourquoi aurais-je besoin d'un pointeur vers un pointeur?

manju
la source
49
Faites attention; l'expression "double pointeur" fait également référence au type double*.
Keith Thompson
1
Prenez note: la réponse à cette question est différente pour C et C ++ - n'ajoutez pas de balise c + à cette très vieille question.
BЈовић

Réponses:

479

Si vous voulez avoir une liste de caractères (un mot), vous pouvez utiliser char *word

Si vous voulez une liste de mots (une phrase), vous pouvez utiliser char **sentence

Si vous voulez une liste de phrases (un monologue), vous pouvez utiliser char ***monologue

Si vous voulez une liste de monologues (une biographie), vous pouvez utiliser char ****biography

Si vous voulez une liste de biographies (une bio-bibliothèque), vous pouvez utiliser char *****biolibrary

Si vous voulez une liste de bio-bibliothèques (a ?? lol), vous pouvez utiliser char ******lol

... ...

oui, je sais que ce ne sont peut-être pas les meilleures structures de données


Exemple d'utilisation avec un lol très très très ennuyeux

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

int wordsinsentence(char **x) {
    int w = 0;
    while (*x) {
        w += 1;
        x++;
    }
    return w;
}

int wordsinmono(char ***x) {
    int w = 0;
    while (*x) {
        w += wordsinsentence(*x);
        x++;
    }
    return w;
}

int wordsinbio(char ****x) {
    int w = 0;
    while (*x) {
        w += wordsinmono(*x);
        x++;
    }
    return w;
}

int wordsinlib(char *****x) {
    int w = 0;
    while (*x) {
        w += wordsinbio(*x);
        x++;
    }
    return w;
}

int wordsinlol(char ******x) {
    int w = 0;
    while (*x) {
        w += wordsinlib(*x);
        x++;
    }
    return w;
}

int main(void) {
    char *word;
    char **sentence;
    char ***monologue;
    char ****biography;
    char *****biolibrary;
    char ******lol;

    //fill data structure
    word = malloc(4 * sizeof *word); // assume it worked
    strcpy(word, "foo");

    sentence = malloc(4 * sizeof *sentence); // assume it worked
    sentence[0] = word;
    sentence[1] = word;
    sentence[2] = word;
    sentence[3] = NULL;

    monologue = malloc(4 * sizeof *monologue); // assume it worked
    monologue[0] = sentence;
    monologue[1] = sentence;
    monologue[2] = sentence;
    monologue[3] = NULL;

    biography = malloc(4 * sizeof *biography); // assume it worked
    biography[0] = monologue;
    biography[1] = monologue;
    biography[2] = monologue;
    biography[3] = NULL;

    biolibrary = malloc(4 * sizeof *biolibrary); // assume it worked
    biolibrary[0] = biography;
    biolibrary[1] = biography;
    biolibrary[2] = biography;
    biolibrary[3] = NULL;

    lol = malloc(4 * sizeof *lol); // assume it worked
    lol[0] = biolibrary;
    lol[1] = biolibrary;
    lol[2] = biolibrary;
    lol[3] = NULL;

    printf("total words in my lol: %d\n", wordsinlol(lol));

    free(lol);
    free(biolibrary);
    free(biography);
    free(monologue);
    free(sentence);
    free(word);
}

Production:

total des mots dans mon lol: 243
pmg
la source
7
Je voulais juste souligner que un arr[a][b][c]n'est pas un ***arr. Le pointeur des pointeurs utilise des références de références, tandis que arr[a][b][c]est stocké comme un tableau habituel dans l'ordre principal des lignes.
MCCCS
170

L'une des raisons est que vous souhaitez modifier la valeur du pointeur transmis à une fonction comme argument de fonction, pour ce faire, vous avez besoin d'un pointeur vers un pointeur.

En termes simples, utilisez **lorsque vous souhaitez conserver (OU conserver la modification) l'allocation ou l'affectation de mémoire même en dehors d'un appel de fonction.(Donc, passez une telle fonction avec l'argument du double pointeur.)

Ce n'est peut-être pas un très bon exemple, mais vous montrera l'utilisation de base:

void allocate(int** p)
{
  *p = (int*)malloc(sizeof(int));
}

int main()
{
  int* p = NULL;
  allocate(&p);
  *p = 42;
  free(p);
}
Asha
la source
14
qu'est-ce qui serait différent si allocate était void allocate(int *p)et vous l'appeliez comme allocate(p)?
ア レ ッ ク ス
@AlexanderSupertramp Oui. Le code sera défectueux. Veuillez voir la réponse de Silviu.
Abhishek
@Asha quelle est la différence entre allocate (p) et allocate (& p)?
user2979872
1
@Asha - Ne pouvons-nous pas simplement retourner le pointeur? Si nous devons le garder vide, quel est un cas d'utilisation pratique de ce scénario?
Shabirmean
91
  • Disons que vous avez un pointeur. Sa valeur est une adresse.
  • mais maintenant vous voulez changer cette adresse.
  • vous pourriez. ce faisant pointer1 = pointer2, vous donnez à pointer1 l'adresse de pointer2.
  • mais! si vous effectuez cette opération dans une fonction et que vous souhaitez que le résultat persiste une fois la fonction terminée, vous devez effectuer un travail supplémentaire. vous avez besoin d'un nouveau pointeur3 pour pointer vers pointeur1. passez pointer3 à la fonction.

  • Voici un exemple. regardez d'abord la sortie ci-dessous, pour comprendre.

#include <stdio.h>

int main()
{

    int c = 1;
    int d = 2;
    int e = 3;
    int * a = &c;
    int * b = &d;
    int * f = &e;
    int ** pp = &a;  // pointer to pointer 'a'

    printf("\n a's value: %x \n", a);
    printf("\n b's value: %x \n", b);
    printf("\n f's value: %x \n", f);
    printf("\n can we change a?, lets see \n");
    printf("\n a = b \n");
    a = b;
    printf("\n a's value is now: %x, same as 'b'... it seems we can, but can we do it in a function? lets see... \n", a);
    printf("\n cant_change(a, f); \n");
    cant_change(a, f);
    printf("\n a's value is now: %x, Doh! same as 'b'...  that function tricked us. \n", a);

    printf("\n NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' \n");
     printf("\n change(pp, f); \n");
    change(pp, f);
    printf("\n a's value is now: %x, YEAH! same as 'f'...  that function ROCKS!!!. \n", a);
    return 0;
}

void cant_change(int * x, int * z){
    x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", x);
}

void change(int ** x, int * z){
    *x = z;
    printf("\n ----> value of 'a' is: %x inside function, same as 'f', BUT will it be the same outside of this function? lets see\n", *x);
}

Voici la sortie: ( lisez ceci en premier )

 a's value: bf94c204

 b's value: bf94c208 

 f's value: bf94c20c 

 can we change a?, lets see 

 a = b 

 a's value is now: bf94c208, same as 'b'... it seems we can, but can we do it in a function? lets see... 

 cant_change(a, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c208, Doh! same as 'b'...  that function tricked us. 

 NOW! lets see if a pointer to a pointer solution can help us... remember that 'pp' point to 'a' 

 change(pp, f); 

 ----> value of 'a' is: bf94c20c inside function, same as 'f', BUT will it be the same outside of this function? lets see

 a's value is now: bf94c20c, YEAH! same as 'f'...  that function ROCKS!!!. 
Brian Joseph Spinos
la source
4
C'est une excellente réponse et m'a vraiment aidé à visualiser le but et l'utilité d'un double pointeur.
Justin
1
@Justin avez-vous vérifié ma réponse au-dessus de celle-ci? c'est plus propre :)
Brian Joseph Spinos
10
Excellente réponse, il suffit d'expliquer que <code> void cant_change (int * x, int * z) </code> échoue parce que ses paramètres ne sont que de nouveaux pointeurs de portée locale qui sont initialisés de la même manière que les pointeurs a et f (donc ils ne le sont pas comme a et f).
Pedro Reis
1
Facile? Vraiment? ;)
alk
1
cette réponse explique vraiment l'une des utilisations les plus courantes du pointeur vers des pointeurs, merci!
tonyjosi
50

Ajout à la réponse d' Asha , si vous utilisez un seul pointeur vers l'exemple ci-dessous (par exemple alloc1 ()), vous perdrez la référence à la mémoire allouée à l'intérieur de la fonction.

void alloc2(int** p) {
   *p = (int*)malloc(sizeof(int));
   **p = 10;
}

void alloc1(int* p) {
   p = (int*)malloc(sizeof(int));
   *p = 10;
}

int main(){
   int *p = NULL;
   alloc1(p);
   //printf("%d ",*p);//undefined
   alloc2(&p);
   printf("%d ",*p);//will print 10
   free(p);
   return 0;
}

La raison pour laquelle cela se produit comme ceci est que alloc1le pointeur est transmis par valeur. Ainsi, lorsqu'il est réaffecté au résultat de l' mallocappel à l'intérieur de alloc1, la modification ne concerne pas le code dans une portée différente.

Silviu
la source
1
Que se passe-t-il si p est un pointeur entier statique? Obtention d'un défaut de segmentation.
kapilddit
free(p)ne suffit pas, vous devez if(p) free(*p)aussi
Shijing Lv
@ShijingLv: No. *pévalue à une intdétention la valeur de 10, passer ceci intà free () `est une mauvaise idée.
alk
L'allocation effectuée dans alloc1()introduit une fuite de mémoire. La valeur du pointeur à transmettre gratuitement est perdue en revenant de la fonction.
alk
1
Pas (!) Besoin de lancer le résultat de malloc en C.
alk
23

J'ai vu un très bon exemple aujourd'hui, à partir de ce billet de blog , comme je le résume ci-dessous.

Imaginez que vous ayez une structure pour les nœuds dans une liste chaînée, qui est probablement

typedef struct node
{
    struct node * next;
    ....
} node;

Maintenant, vous voulez implémenter une remove_iffonction, qui accepte un critère de suppression rmcomme l'un des arguments et traverse la liste liée: si une entrée satisfait le critère (quelque chose comme rm(entry)==true), son nœud sera supprimé de la liste. Au final, remove_ifrenvoie la tête (qui peut être différente de la tête d'origine) de la liste chaînée.

Vous pouvez écrire

for (node * prev = NULL, * curr = head; curr != NULL; )
{
    node * const next = curr->next;
    if (rm(curr))
    {
        if (prev)  // the node to be removed is not the head
            prev->next = next;
        else       // remove the head
            head = next;
        free(curr);
    }
    else
        prev = curr;
    curr = next;
}

comme forboucle. Le message est que, sans pointeurs doubles, vous devez conserver une prevvariable pour réorganiser les pointeurs et gérer les deux cas différents.

Mais avec des pointeurs doubles, vous pouvez réellement écrire

// now head is a double pointer
for (node** curr = head; *curr; )
{
    node * entry = *curr;
    if (rm(entry))
    {
        *curr = entry->next;
        free(entry);
    }
    else
        curr = &entry->next;
}

Vous n'avez pas besoin d'un prevmaintenant car vous pouvez directement modifier ce qui prev->nextpointait .

Pour rendre les choses plus claires, suivons un peu le code. Lors de la suppression:

  1. if entry == *head: ce sera *head (==*curr) = *head->next- headpointe maintenant vers le pointeur du nouveau noeud de titre. Pour ce faire, vous modifiez directement headle contenu de vers un nouveau pointeur.
  2. si entry != *head: de la même manière, *currc'est ce qui prev->nextpointait et pointe maintenant entry->next.

Dans tous les cas, vous pouvez réorganiser les pointeurs de manière unifiée avec des pointeurs doubles.

ziyuang
la source
22

1. Concept de base -

Lorsque vous déclarez ce qui suit: -

1. char * ch - (appelé pointeur de caractère)
- ch contient l'adresse d'un seul caractère.
- (* ch) déréférera à la valeur du caractère.

2. char ** ch -
'ch' contient l'adresse d'un tableau de pointeurs de caractères. (comme dans 1)
'* ch' contient l'adresse d'un seul caractère. (Notez qu'il est différent de 1, en raison de la différence de déclaration).
(** ch) déréférencera la valeur exacte du caractère.

L'ajout de pointeurs étend la dimension d'un type de données, du caractère à la chaîne, au tableau de chaînes, etc. Vous pouvez le relier à une matrice 1d, 2d, 3d ..

Ainsi, l'utilisation du pointeur dépend de la façon dont vous le déclarez.

Voici un code simple ..

int main()
{
    char **p;
    p = (char **)malloc(100);
    p[0] = (char *)"Apple";      // or write *p, points to location of 'A'
    p[1] = (char *)"Banana";     // or write *(p+1), points to location of 'B'

    cout << *p << endl;          //Prints the first pointer location until it finds '\0'
    cout << **p << endl;         //Prints the exact character which is being pointed
    *p++;                        //Increments for the next string
    cout << *p;
}

2. Une autre application des pointeurs doubles -
(cela couvrirait également le passage par référence)

Supposons que vous souhaitiez mettre à jour un caractère à partir d'une fonction. Si vous essayez ce qui suit: -

void func(char ch)
{
    ch = 'B';
}

int main()
{
    char ptr;
    ptr = 'A';
    printf("%c", ptr);

    func(ptr);
    printf("%c\n", ptr);
}

La sortie sera AA. Cela ne fonctionne pas, car vous avez "passé par valeur" à la fonction.

La bonne façon de procéder serait -

void func( char *ptr)        //Passed by Reference
{
    *ptr = 'B';
}

int main()
{
    char *ptr;
    ptr = (char *)malloc(sizeof(char) * 1);
    *ptr = 'A';
    printf("%c\n", *ptr);

    func(ptr);
    printf("%c\n", *ptr);
}

Étendez maintenant cette exigence pour mettre à jour une chaîne au lieu d'un caractère.
Pour cela, vous devez recevoir le paramètre dans la fonction sous la forme d'un double pointeur.

void func(char **str)
{
    strcpy(str, "Second");
}

int main()
{
    char **str;
    // printf("%d\n", sizeof(char));
    *str = (char **)malloc(sizeof(char) * 10);          //Can hold 10 character pointers
    int i = 0;
    for(i=0;i<10;i++)
    {
        str = (char *)malloc(sizeof(char) * 1);         //Each pointer can point to a memory of 1 character.
    }

    strcpy(str, "First");
    printf("%s\n", str);
    func(str);
    printf("%s\n", str);
}

Dans cet exemple, la méthode attend un double pointeur comme paramètre pour mettre à jour la valeur d'une chaîne.

Bhavuk Mathur
la source
#include <stdio.h> int main() { char *ptr = 0; ptr = malloc(255); // allocate some memory strcpy( ptr, "Stack Overflow Rocks..!!"); printf("%s\n", ptr); printf("%d\n",strlen(ptr)); free(ptr); return 0; } Mais vous pouvez aussi le faire sans utiliser de pointeur double.
kumar
" char ** ch - 'ch' contient l'adresse d'un tableau de pointeurs de caractères. " Non, il contient l'adresse du 1er élément d'un tableau de charpointeurs. Un pointeur vers un tableau de char*serait par exemple comme ceci: char(*(*p)[42])définit pcomme pointeur vers un tableau de 42 pointeurs vers char.
alk
Le dernier extrait est complètement cassé. Pour commencer: voici un *str = ... strappel non défini initialisé non référencé.
alk
Cela malloc(sizeof(char) * 10);n'alloue pas de place pour 10 pointeurs charmais pour 10 charseulement ..
alk
Cette boucle for(i=0;i<10;i++) { str = ... manque d'utiliser l'index i.
alk
17

Les pointeurs vers les pointeurs sont également utiles en tant que «poignées» vers la mémoire où vous voulez passer une «poignée» entre les fonctions vers la mémoire re-localisable. Cela signifie essentiellement que la fonction peut modifier la mémoire pointée par le pointeur à l'intérieur de la variable de poignée, et chaque fonction ou objet qui utilise la poignée pointera correctement vers la mémoire nouvellement déplacée (ou allouée). Les bibliothèques aiment faire cela avec des types de données "opaques", c'est-à-dire des types de données où vous n'avez pas à vous soucier de ce qu'ils font avec la mémoire pointée, vous passez simplement la "poignée" entre les fonctions de la bibliothèque pour effectuer certaines opérations sur cette mémoire ...

Par exemple:

#include <stdlib.h>

typedef unsigned char** handle_type;

//some data_structure that the library functions would work with
typedef struct 
{
    int data_a;
    int data_b;
    int data_c;
} LIB_OBJECT;

handle_type lib_create_handle()
{
    //initialize the handle with some memory that points to and array of 10 LIB_OBJECTs
    handle_type handle = malloc(sizeof(handle_type));
    *handle = malloc(sizeof(LIB_OBJECT) * 10);

    return handle;
}

void lib_func_a(handle_type handle) { /*does something with array of LIB_OBJECTs*/ }

void lib_func_b(handle_type handle)
{
    //does something that takes input LIB_OBJECTs and makes more of them, so has to
    //reallocate memory for the new objects that will be created

    //first re-allocate the memory somewhere else with more slots, but don't destroy the
    //currently allocated slots
    *handle = realloc(*handle, sizeof(LIB_OBJECT) * 20);

    //...do some operation on the new memory and return
}

void lib_func_c(handle_type handle) { /*does something else to array of LIB_OBJECTs*/ }

void lib_free_handle(handle_type handle) 
{
    free(*handle);
    free(handle); 
}


int main()
{
    //create a "handle" to some memory that the library functions can use
    handle_type my_handle = lib_create_handle();

    //do something with that memory
    lib_func_a(my_handle);

    //do something else with the handle that will make it point somewhere else
    //but that's invisible to us from the standpoint of the calling the function and
    //working with the handle
    lib_func_b(my_handle); 

    //do something with new memory chunk, but you don't have to think about the fact
    //that the memory has moved under the hood ... it's still pointed to by the "handle"
    lib_func_c(my_handle);

    //deallocate the handle
    lib_free_handle(my_handle);

    return 0;
}

J'espère que cela t'aides,

Jason

Jason
la source
Quelle est la raison pour laquelle le type de poignée est un caractère non signé **? Est-ce que void ** fonctionnerait aussi bien?
Connor Clark
5
unsigned charest spécifiquement utilisé car nous stockons un pointeur sur des données binaires qui seront représentées sous forme d'octets bruts. L'utilisation voidnécessitera un cast à un moment donné et n'est généralement pas aussi lisible que l'intention de ce qui est fait.
Jason
7

Exemple simple que vous avez probablement déjà vu à plusieurs reprises

int main(int argc, char **argv)

Dans le deuxième paramètre, vous l'avez: pointeur vers pointeur vers char.

Notez que la notation du pointeur ( char* c) et la notation du tableau ( char c[]) sont interchangeables dans les arguments de fonction. Vous pouvez donc aussi écrire char *argv[]. En d'autres termes char *argv[]etchar **argv sont interchangeables.

Ce que cela représente est en fait un tableau de séquences de caractères (les arguments de ligne de commande qui sont donnés à un programme au démarrage).

Voir également cette réponse pour plus de détails sur la signature de fonction ci-dessus.

plats1
la source
2
"la notation du pointeur ( char* c) et la notation du tableau ( char c[]) sont interchangeables" (et ont la même signification exacte) dans les arguments de fonction . Ce sont cependant des arguments de fonction différents.
pmg
6

Les chaînes sont un excellent exemple d'utilisation des pointeurs doubles. La chaîne elle-même est un pointeur, donc chaque fois que vous devez pointer vers une chaîne, vous aurez besoin d'un double pointeur.

drysdam
la source
5

Par exemple, vous pouvez vous assurer que lorsque vous libérez la mémoire de quelque chose, vous définissez le pointeur sur null par la suite.

void safeFree(void** memory) {
    if (*memory) {
        free(*memory);
        *memory = NULL;
    }
}

Lorsque vous appelez cette fonction, vous l'appelez avec l'adresse d'un pointeur

void* myMemory = someCrazyFunctionThatAllocatesMemory();
safeFree(&myMemory);

Now myMemoryest défini sur NULL et toute tentative de réutilisation sera manifestement fausse.

Jeff Foster
la source
1
il devrait être if(*memory)etfree(*memory);
Asha
1
Bon point, perte de signal entre le cerveau et le clavier. Je l'ai édité pour donner un peu plus de sens.
Jeff Foster
Pourquoi ne pouvons-nous pas faire ce qui suit ... void safeFree (void * memory) {if (memory) {free (memory); memory = NULL; }}
Peter_pk
@Peter_pk Assigner de la mémoire à null ne serait d'aucune utilité car vous avez passé un pointeur par valeur, pas par référence (d'où l'exemple d'un pointeur sur un pointeur).
Jeff Foster
2

Par exemple, si vous souhaitez un accès aléatoire à des données non contiguës.

p -> [p0, p1, p2, ...]  
p0 -> data1
p1 -> data2

- en C

T ** p = (T **) malloc(sizeof(T*) * n);
p[0] = (T*) malloc(sizeof(T));
p[1] = (T*) malloc(sizeof(T));

Vous stockez un pointeur pqui pointe vers un tableau de pointeurs. Chaque pointeur pointe vers une donnée.

Si sizeof(T)est grand, il peut ne pas être possible d'allouer un bloc contigu (c'est-à-dire en utilisant malloc) d' sizeof(T) * noctets.

log0
la source
1
Pas (!) Besoin de lancer le résultat de malloc en C.
alk
2

Une chose pour laquelle je les utilise constamment, c'est quand j'ai un tableau d'objets et que je dois effectuer des recherches (recherche binaire) sur eux par différents champs.
Je garde le tableau d'origine ...

int num_objects;
OBJECT *original_array = malloc(sizeof(OBJECT)*num_objects);

Créez ensuite un tableau de pointeurs triés vers les objets.

int compare_object_by_name( const void *v1, const void *v2 ) {
  OBJECT *o1 = *(OBJECT **)v1;
  OBJECT *o2 = *(OBJECT **)v2;
  return (strcmp(o1->name, o2->name);
}

OBJECT **object_ptrs_by_name = malloc(sizeof(OBJECT *)*num_objects);
  int i = 0;
  for( ; i<num_objects; i++)
    object_ptrs_by_name[i] = original_array+i;
  qsort(object_ptrs_by_name, num_objects, sizeof(OBJECT *), compare_object_by_name);

Vous pouvez créer autant de tableaux de pointeurs triés que vous le souhaitez, puis utiliser une recherche binaire sur le tableau de pointeurs triés pour accéder à l'objet dont vous avez besoin à partir des données dont vous disposez. Le tableau d'objets d'origine peut rester non trié, mais chaque tableau de pointeurs sera trié par son champ spécifié.

DavidMFrey
la source
2

Pourquoi des pointeurs doubles?

L'objectif est de changer ce vers quoi pointe studentA, à l'aide d'une fonction.

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


typedef struct Person{
    char * name;
} Person; 

/**
 * we need a ponter to a pointer, example: &studentA
 */
void change(Person ** x, Person * y){
    *x = y; // since x is a pointer to a pointer, we access its value: a pointer to a Person struct.
}

void dontChange(Person * x, Person * y){
    x = y;
}

int main()
{

    Person * studentA = (Person *)malloc(sizeof(Person));
    studentA->name = "brian";

    Person * studentB = (Person *)malloc(sizeof(Person));
    studentB->name = "erich";

    /**
     * we could have done the job as simple as this!
     * but we need more work if we want to use a function to do the job!
     */
    // studentA = studentB;

    printf("1. studentA = %s (not changed)\n", studentA->name);

    dontChange(studentA, studentB);
    printf("2. studentA = %s (not changed)\n", studentA->name);

    change(&studentA, studentB);
    printf("3. studentA = %s (changed!)\n", studentA->name);

    return 0;
}

/**
 * OUTPUT:
 * 1. studentA = brian (not changed)
 * 2. studentA = brian (not changed)
 * 3. studentA = erich (changed!)
 */
Brian Joseph Spinos
la source
1
Pas (!) Besoin de lancer le résultat de malloc en C.
alk
2

Voici un exemple C ++ très simple qui montre que si vous souhaitez utiliser une fonction pour définir un pointeur pour pointer vers un objet, vous avez besoin d'un pointeur vers un pointeur . Autrement, le pointeur continuera de revenir à null .

(Une réponse C ++, mais je pense que c'est la même chose en C.)

(Aussi, pour référence: Google ("pass by value c ++") = "Par défaut, les arguments en C ++ sont passés par valeur. Lorsqu'un argument est passé par valeur, la valeur de l'argument est copiée dans le paramètre de la fonction.")

Nous voulons donc définir le pointeur bégal à la chaîne a.

#include <iostream>
#include <string>

void Function_1(std::string* a, std::string* b) {
  b = a;
  std::cout << (b == nullptr);  // False
}

void Function_2(std::string* a, std::string** b) {
  *b = a;
  std::cout << (b == nullptr);  // False
}

int main() {
  std::string a("Hello!");
  std::string* b(nullptr);
  std::cout << (b == nullptr);  // True

  Function_1(&a, b);
  std::cout << (b == nullptr);  // True

  Function_2(&a, &b);
  std::cout << (b == nullptr);  // False
}

// Output: 10100

Que se passe-t-il à la ligne Function_1(&a, b);?

  • La "valeur" de &main::a(une adresse) est copiée dans le paramètre std::string* Function_1::a. C'est donc Function_1::aun pointeur vers (c'est-à-dire l'adresse mémoire de) la chaîne main::a.

  • La "valeur" de main::b(une adresse en mémoire) est copiée dans le paramètre std::string* Function_1::b. Par conséquent, il y a maintenant 2 de ces adresses en mémoire, les deux pointeurs nuls. À la ligne b = a;, la variable locale Function_1::best alors modifiée en égal Function_1::a(= &main::a), mais la variable main::best inchangée. Après l'appel à Function_1, main::best toujours un pointeur nul.

Que se passe-t-il à la ligne Function_2(&a, &b);?

  • Le traitement de la avariable est le même: au sein de la fonction, se Function_2::atrouve l'adresse de la chaîne main::a.

  • Mais la variable best maintenant passée en tant que pointeur vers un pointeur. La "valeur" de &main::b(l' adresse du pointeur main::b ) est copiée dans std::string** Function_2::b. Par conséquent, au sein de Function_2, déréférencer cela comme *Function_2::bva accéder et modifier main::b. Donc, la ligne *b = a;définit réellement main::b(une adresse) égale à Function_2::a(= adresse de main::a), ce que nous voulons.

Si vous voulez utiliser une fonction pour modifier une chose, que ce soit un objet ou une adresse (pointeur), vous devez passer un pointeur sur cette chose. La chose que vous transmettez réellement ne peut pas être modifiée (dans la portée d'appel) car une copie locale est effectuée.

(Une exception est si le paramètre est une référence, comme std::string& a. Mais généralement, ce sont const. Généralement, si vous appelez f(x), if xest un objet, vous devriez pouvoir supposer qu'il f ne sera pas modifié x. Mais s'il xs'agit d'un pointeur, alors vous devriez supposons que cela f puisse modifier l'objet pointé par x.)

jt117
la source
Le code C ++ pour répondre à une question C n'est pas la meilleure idée.
alk
1

Un peu tard pour la fête, mais j'espère que cela aidera quelqu'un.

Dans les tableaux C, allouez toujours de la mémoire sur la pile, donc une fonction ne peut pas retourner un tableau (non statique) car la mémoire allouée sur la pile est libérée automatiquement lorsque l'exécution atteint la fin du bloc en cours. C'est vraiment ennuyeux lorsque vous souhaitez traiter des tableaux bidimensionnels (c'est-à-dire des matrices) et implémenter quelques fonctions qui peuvent modifier et renvoyer des matrices. Pour ce faire, vous pouvez utiliser un pointeur vers pointeur pour implémenter une matrice avec une mémoire allouée dynamiquement:

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows float-pointers
    double** A = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(A == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols floats
    for(int i = 0; i < num_rows; i++){
        A[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(A[i] == NULL){
            for(int j = 0; j < i; j++){
                free(A[j]);
            }
            free(A);
            return NULL;
        }
    }
    return A;
} 

Voici une illustration:

double**       double*           double
             -------------       ---------------------------------------------------------
   A ------> |   A[0]    | ----> | A[0][0] | A[0][1] | A[0][2] | ........ | A[0][cols-1] |
             | --------- |       ---------------------------------------------------------
             |   A[1]    | ----> | A[1][0] | A[1][1] | A[1][2] | ........ | A[1][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             |   A[i]    | ----> | A[i][0] | A[i][1] | A[i][2] | ........ | A[i][cols-1] |
             | --------- |       ---------------------------------------------------------
             |     .     |                                    .
             |     .     |                                    .
             |     .     |                                    .
             | --------- |       ---------------------------------------------------------
             | A[rows-1] | ----> | A[rows-1][0] | A[rows-1][1] | ... | A[rows-1][cols-1] |
             -------------       ---------------------------------------------------------

Le pointeur double sur pointeur double A pointe vers le premier élément A [0] d'un bloc de mémoire dont les éléments sont eux-mêmes des pointeurs doubles. Vous pouvez imaginer ces doubles pointeurs comme les lignes de la matrice. C'est la raison pour laquelle chaque double pointeur alloue de la mémoire aux éléments num_cols de type double. De plus, A [i] pointe vers la i-ème ligne, c'est-à-dire que A [i] pointe vers A [i] [0] et ce n'est que le premier double élément du bloc de mémoire pour la i-ème ligne. Enfin, vous pouvez accéder facilement à l'élément de la i-ème ligne et de la j-ème colonne avec A [i] [j].

Voici un exemple complet qui illustre l'utilisation:

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

/* Initializes a matrix */
double** init_matrix(int num_rows, int num_cols){
    // Allocate memory for num_rows double-pointers
    double** matrix = calloc(num_rows, sizeof(double*));
    // return NULL if the memory couldn't allocated
    if(matrix == NULL) return NULL;
    // For each double-pointer (row) allocate memory for num_cols
    // doubles
    for(int i = 0; i < num_rows; i++){
        matrix[i] = calloc(num_cols, sizeof(double));
        // return NULL if the memory couldn't allocated
        // and free the already allocated memory
        if(matrix[i] == NULL){
            for(int j = 0; j < i; j++){
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
    }
    return matrix;
}

/* Fills the matrix with random double-numbers between -1 and 1 */
void randn_fill_matrix(double** matrix, int rows, int cols){
    for (int i = 0; i < rows; ++i){
        for (int j = 0; j < cols; ++j){
            matrix[i][j] = (double) rand()/RAND_MAX*2.0-1.0;
        }
    }
}


/* Frees the memory allocated by the matrix */
void free_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        free(matrix[i]);
    }
    free(matrix);
}

/* Outputs the matrix to the console */
void print_matrix(double** matrix, int rows, int cols){
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < cols; j++){
            printf(" %- f ", matrix[i][j]);
        }
        printf("\n");
    }
}


int main(){
    srand(time(NULL));
    int m = 3, n = 3;
    double** A = init_matrix(m, n);
    randn_fill_matrix(A, m, n);
    print_matrix(A, m, n);
    free_matrix(A, m, n);
    return 0;
}
joni
la source
0

J'ai utilisé des pointeurs doubles aujourd'hui alors que je programmais quelque chose pour le travail, donc je peux expliquer pourquoi nous avons dû les utiliser (c'est la première fois que je dois utiliser des pointeurs doubles). Nous avons dû gérer le codage en temps réel des trames contenues dans les tampons qui sont membres de certaines structures. Dans l'encodeur, nous avons dû utiliser un pointeur vers l'une de ces structures. Le problème était que notre pointeur était modifié pour pointer vers d'autres structures à partir d'un autre thread. Afin d'utiliser la structure actuelle dans l'encodeur, j'ai dû utiliser un double pointeur, afin de pointer vers le pointeur qui était en cours de modification dans un autre thread. Il n'était pas évident au début, du moins pour nous, que nous devions adopter cette approche. Beaucoup d'adresses ont été imprimées au cours du processus :)).

Vous DEVEZ utiliser des pointeurs doubles lorsque vous travaillez sur des pointeurs modifiés à d'autres endroits de votre application. Vous pouvez également trouver des pointeurs doubles indispensables lorsque vous traitez du matériel qui vous est renvoyé et qui vous est adressé.

Axenie Ionut
la source
0

Comparez la valeur de modification de la variable avec la valeur de modification du pointeur :

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

void changeA(int (*a))
{
  (*a) = 10;
}

void changeP(int *(*P))
{
  (*P) = malloc(sizeof((*P)));
}

int main(void)
{
  int A = 0;

  printf("orig. A = %d\n", A);
  changeA(&A);
  printf("modi. A = %d\n", A);

  /*************************/

  int *P = NULL;

  printf("orig. P = %p\n", P);
  changeP(&P);
  printf("modi. P = %p\n", P);

  free(P);

  return EXIT_SUCCESS;
}

Cela m'a aidé à éviter de renvoyer la valeur du pointeur lorsque le pointeur a été modifié par la fonction appelée (utilisée dans la liste liée individuellement).

VIEUX (mauvais):

int *func(int *P)
{
  ...
  return P;
}

int main(void)
{
  int *pointer;
  pointer = func(pointer);
  ...
}    

NOUVEAU (mieux):

void func(int **pointer)
{
  ...
}

int main(void)
{
  int *pointer;
  func(&pointer);
  ...
}    
Sany
la source