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?
4 Réponses :
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; } } }
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
.
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.
arr
et & arr
peuvent ne pas être identiques au niveau du bit, mais elles représentent finalement le même emplacement.
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; }
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 ...