0
votes

Passer par référence ou valeur pour le tableau de pointeurs 2D

J'ai une question sur l'utilisation du passage par référence pour les tableaux 2D (variantes VLA) en C. Il semble que la plupart des exemples illustrés, comme # 2 ici: Comment passer un tableau 2D (matrice) dans une fonction en C? montre que vous ne le faites pas ' t doivent utiliser la convention de passage par référence. Juste pour montrer mon exemple:

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

void assign(double*** testMatrix, int* dim){
    for(int row=0; row < *dim; ++row){
        for(int column=0; column< *dim; ++column){
            (*testMatrix)[row][column] = 0;
        }
    }
    
}

int main(void) {
    
    int dim = 200;
    
    double** testMatrix = malloc(sizeof(double*) * dim);
    for(int i=0; i < dim; ++i){
        testMatrix[i] = malloc(sizeof(double) * dim);
    }
    
    assign(&testMatrix, &dim);
    
    //deallocate test matrix
    for(int i=0; i< dim; ++i){
        free(testMatrix[i]);
    }
    free(testMatrix);
    
    return 0;
}

l'exemple de code ci-dessus affectant le tableau 2D sans utiliser de conventions pour le passage par référence, comme l'exemple ci-dessous (voir assigner la fonction avec le &): p>

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

void assign(double** testMatrix, int* dim){
   for(int row=0; row < *dim; ++row){
       for(int column=0; column< *dim; ++column){
           testMatrix[row][column] = 0;
       }
   }
   
}

int main(void) {
   
   int dim = 200;
   
   double** testMatrix = malloc(sizeof(double*) * dim);
   for(int i=0; i < dim; ++i){
       testMatrix[i] = malloc(sizeof(double) * dim);
   }
   
   assign(testMatrix, &dim);
   
   //deallocate test matrix
   for(int i=0; i< dim; ++i){
       free(testMatrix[i]);
   }
   free(testMatrix);
   
   return 0;
}

Ma question est de savoir comment le tableau 2D du premier exemple est-il modifié sans passer la référence du tableau?


3 commentaires

La première observation est que testMatrix, malgré son nom, n'est qu'un tableau unidimensionnel (de pointeurs).


@TamilSelvanV: Il n'y a pas de variables de tableau dans le code de la question.


@EricPostpischil Oui, vous avez raison, et ma formulation était inexacte: testMatrix est un simple pointeur vers le premier élément d'un tableau de pointeurs alloué dynamiquement ...


4 Réponses :


2
votes

Pour commencer, vous n'avez pas de tableau bidimensionnel ni de tableau VLA. Vous avez un pointeur de type double ** qui pointe vers une mémoire allouée.

Dans cette fonction

p[0] = 1
p[0] = 1, p[1] = 2

le pointeur lui-même n'est pas modifié. Ce sont les données pointées qui sont modifiées et les données pointées sont passées à la fonction par référence en utilisant le pointeur déclaré dans main.

Dans cette fonction

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

int change( int **p )
{
    int *tmp = realloc( *p, 2 * sizeof( int ) );
    int success = tmp != NULL;
    
    if ( success )
    {
        tmp[1] = 2;
        
        *p = tmp;
    }
    
    return success;
}

int main(void) 
{
    int *p = malloc( sizeof( int ) );
    *p = 1;
    
    printf( "p[0] = %d\n", p[0] );
    
    if ( change( &p ) )
    {
        printf( "p[0] = %d, p[1] = %d\n", p[0], p[1] );
    }
    
    free( p );

    return 0;
}

il y a à nouveau le pointeur passé par référencé n'est pas modifié. Il n'y a donc aucun sens à passer le pointeur d'origine par référence.

Voici un programme démonstratif qui montre quand vous devez passer un pointeur par référence pour le changer lui-même.

void assign(double*** testMatrix, int* dim){
    for(int row=0; row < *dim; ++row){
        for(int column=0; column< *dim; ++column){
            (*testMatrix)[row][column] = 0;
        }
    }
    
}


0 commentaires

1
votes

Le code double ** testMatrix = malloc (sizeof (double *) * dim); crée un pointeur vers un pointeur vers un double et le définit pour qu'il pointe vers stockage alloué. La boucle qui le suit remplit l'espace de stockage alloué avec des pointeurs vers double .

Ensuite, l'appel de fonction assign (testMatrix, & dim); passe ce premier pointeur à assign .

Puisque assign a l'adresse du stockage alloué, il peut accéder aux pointeurs qu'il contient. Puisqu'il a ces pointeurs, il peut accéder au stockage vers lequel ils pointent. Cela répond à la question «comment le tableau 2D du premier exemple est-il modifié…»: Lorsque vous passez un pointeur vers quelque chose, vous passez un moyen d'accéder à l'objet.

En fait, passer un pointeur vers quelque chose, c'est passer une référence à quelque chose. Le pointeur fait référence à la chose. (C ++ introduit une nouvelle fonctionnalité appelée «référence», et c'est une sorte de référence gérée automatiquement. Mais toute façon de faire référence à une chose - donner son adresse, son nom, une description de l'endroit où le trouver, une citation bibliographique , une URL ou un pointeur vers une structure contenant de telles informations - est une sorte de référence.)

Ainsi, en passant testMatrix à assigner, vous avez passé la valeur de testMatrix , qui est également une référence au stockage vers lequel il pointe, et ce stockage contient des références ( sous forme de pointeurs) vers le stockage des valeurs double .


0 commentaires

0
votes

Tout d'abord, juste pour la pédanterie, C passe tous les arguments de fonction par valeur, point. Parfois, ces valeurs sont des pointeurs, et nous pouvons modifier les choses grâce à ces pointeurs, ce que la plupart d'entre nous entendons lorsque nous parlons de "passer par référence" en C, mais à proprement parler, ce que nous faisons est de passer un pointeur par valeur .

Clair comme de la boue? Ok.

Ensuite, votre testMatrix n'est pas un tableau 2D - c'est un pointeur vers le premier d'une séquence de pointeurs. En mémoire, cela ressemble à ceci:

int rows = get_rows();
int cols = get_cols();
int myvla[rows][cols];

foo( rows, cols, myvla );

L ' expression testMatrix a un type de pointeur ( int ** ), pas de type tableau. Lorsque vous appelez assign vous passez une valeur de pointeur, ce qu'il attend:

void foo( int rows, int cols, int arr[rows][cols] )

Si vous déclarez un vrai tableau 2D tel que p>

void foo( int arr[][2], int rows )

vous obtenez ceci en mémoire:

void foo( int (*arr)[2], int rows )

À moins que ce ne soit l'opérande de la sizeof ou Opérateurs unaires & , ou est un littéral de chaîne utilisé pour initialiser un tableau de caractères dans une déclaration, une expression de type "Tableau à N éléments de T code > "est converti (" decays ") en une expression de type" pointeur vers T "et la valeur de l'expression sera l'adresse du premier élément du tableau.

L ' expression arr a le type "tableau à 2 éléments du tableau à 2 éléments de int "; à moins qu'il ne s'agisse de l'opérande des opérateurs sizeof ou unaire & , il "se désintègre" en une expression de type "pointeur vers un tableau à 2 éléments de int code> "ou int (*) [2] . Si vous passez l'expression arr à une fonction, la fonction recevra en fait un pointeur vers le premier élément du tableau, pas une copie du tableau.

Encore une fois, ce n'est pas vraiment "passer par référence", nous passons un pointeur par valeur . Cependant, l'effet pratique est que le paramètre formel de la fonction peut être en indice, et toutes les modifications apportées aux éléments du tableau dans la fonction sont reflétées dans le tableau passé par l'appelant.

C'est pourquoi vous n'avez presque jamais besoin pour utiliser l'opérateur & lors du passage d'une expression de tableau comme paramètre de fonction - la fonction reçoit déjà l'adresse du premier élément du tableau. Oui, l'adresse d'un tableau est la même que l'adresse de son premier élément, donc la valeur de arr et & arr serait la même 1 , ils auraient juste différents types ( int (*) [2] vs int (**) [2] ).

Donc, si j'appelle une fonction foo avec

foo( arr, 2 );

alors le prototype de fonction sera

      int
      +---+
 arr: |   | arr[0][0]
      +---+ 
      |   | arr[0][1]
      +---+
      |   | arr[1][0]
      +---+
      |   | arr[1][1]
      +---+

Dans le cadre d'une déclaration de paramètre de fonction, T a [N] et T a [] sont identiques à T * a - tous les trois déclarent a comme pointeur vers T , vous pouvez donc également écrire cette déclaration sous la forme

int arr[2][2];

Encore une fois, c'est seulement vrai pour les déclarations de paramètres de fonction.

Les VLA fonctionnent principalement comme des tableaux réguliers - ce que vous pouvez faire dans le prototype de fonction est écrire

void assign(double** testMatrix, int* dim)

Cela fonctionne si vous déclarez un VLA comme

            int **      int *                    int
            +---+       +---+                    +---+
testMatrix: |   | ----> |   | testMatrix[0] ---> |   | testMatrix[0][0]
            +---+       +---+                    +---+
                        |   | testMatrix[1] --+  |   | testMatrix[0][1]
                        +---+                 |  +---+
                         ...                  |   ...
                                              |   
                                              |  +---+
                                              +->|   | testMatrix[1][0]
                                                 +---+ 
                                                 |   | testMatrix[1][1]
                                                 +---+
                                                  ...

Encore une fois, qu'est-ce que foo reçoit est un pointeur vers le premier élément de myvla , pas une copie du tableau.


  1. Différents types de pointeurs peuvent avoir des représentations différentes, donc les valeurs de arr et & arr peuvent ne pas être identiques au niveau du bit, mais elles représentent finalement le même emplacement.


0 commentaires

0
votes

Vous pouvez comprendre la différence lorsque vous essayez de modifier l'adresse (qui est le type de valeur qu'un pointeur stocke!) à l'intérieur de la fonction.

//here you create a copy of the matrix address, so changing 
//this address here wont change the real address
void f1(double **m) 
{
     //m is a copy of the address
     m = malloc(sizeof(double*)*200);
}
//Here we change the address of what as passed, it will assign a new address
//to the matrix that was passed
void f2(double ***m)
{
    //when you dereference with one * you get the real address and any changes
    // on *m will reflect on the parameter passed
    *m = malloc(sizeof(double*)*200);
}

int main(void) {

  int dim = 200;
  double** testMatrix = malloc(sizeof(double*) * dim);
  for(int i=0; i < dim; ++i){
    testMatrix[i] = malloc(sizeof(double) * dim);
  }
  double **other = testMatrix;
  f1(testMatrix);
  printf("point to the same place ? %d\n", other == testMatrix); 
  f2(&testMatrix);
  printf("point to the same place ? %d\n", other == testMatrix);       
  return 0;
}


0 commentaires