Passer par référence en C

204

Si C ne prend pas en charge le passage d'une variable par référence, pourquoi cela fonctionne-t-il?

#include <stdio.h>

void f(int *j) {
  (*j)++;
}

int main() {
  int i = 20;
  int *p = &i;
  f(p);
  printf("i = %d\n", i);

  return 0;
}

Production:

$ gcc -std=c99 test.c
$ a.exe
i = 21 
aks
la source
22
Où dans ce code passez-vous la référence ?
Atul
15
Il convient de noter que C n'a pas de passe par référence, il ne peut être émulé qu'à l' aide de pointeurs.
Un programmeur du
6
L'instruction correcte est "C ne prend pas en charge le passage implicite d' une variable par référence" - vous devez créer explicitement une référence (avec &) avant d'appeler la fonction et la déréférencer explicitement (avec *) dans la fonction.
Chris Dodd
2
La sortie de votre code est exactement égale lorsque l'appel f(&i);est une implémentation de passage par référence, qui n'existe pas uniquement en C. C passage par référence
EsmaeelE
@Someprogrammerdude Passer un pointeur, c'est passer par référence. Cela semble être l'un de ces faits dont les programmeurs C "avertis" sont fiers. Comme s'ils en tiraient un coup. "Oh, vous pourriez penser que C a un passage par référence, mais non, c'est en fait juste la valeur d'une adresse mémoire transmise harharhar". Passer par référence signifie simplement passer l'adresse mémoire de l'endroit où une variable est stockée plutôt que la valeur de la variable elle-même. C'est ce que C permet, et il est transmis par référence chaque fois que vous passez un pointeur, car un pointeur est une référence à un emplacement de mémoire de variables.
YungGun

Réponses:

315

Parce que vous transmettez la valeur du pointeur à la méthode et que vous déréférencez-la pour obtenir l'entier pointé.

tvanfosson
la source
4
f (p); -> cela signifie-t-il passer par la valeur? puis le déréférencer pour obtenir l'entier pointé. -> pourriez-vous s'il vous plaît donner plus d'explications.
bapi
4
@bapi, déréférencer un pointeur signifie "obtenir la valeur à laquelle ce pointeur fait référence".
Rauni Lillemets
Ce que nous appelons pour la méthode d'appel de la fonction qui prend l'adresse de la variable au lieu de passer le pointeur. Exemple: func1 (int & a). N'est-ce pas un appel par référence? Dans ce cas, la référence est vraiment prise et en cas de pointeur, nous passons toujours par valeur parce que nous passons pointeur par valeur uniquement.
Jon Wheelock
1
Lorsque vous utilisez des pointeurs, le fait clé est que la copie du pointeur est passée dans la fonction. La fonction utilise ensuite ce pointeur, pas celui d'origine. C'est toujours une valeur de passage, mais cela fonctionne.
Danijel
1
@Danijel Il est possible de passer un pointeur qui n'est pas une copie de quoi que ce soit à un appel de fonction. Par exemple, appeler la fonction func: func(&A);Cela passerait un pointeur vers A vers la fonction sans copier quoi que ce soit. Il s'agit d'une valeur de passage, mais cette valeur est une référence, vous êtes donc en train de «passer par référence» la variable A. Aucune copie n'est requise. Il est valable de dire que c'est un passage par référence.
YungGun
124

Ce n'est pas un passage par référence, c'est un passage par valeur comme d'autres l'ont dit.

Le langage C est pass-by-value sans exception. Passer un pointeur en tant que paramètre ne signifie pas passer par référence.

La règle est la suivante:

Une fonction n'est pas en mesure de modifier la valeur réelle des paramètres.


Essayons de voir les différences entre les paramètres scalaires et pointeurs d'une fonction.

Variables scalaires

Ce programme court montre le passage par valeur en utilisant une variable scalaire. paramest appelé le paramètre formel et variableà l'invocation de la fonction est appelé paramètre réel. Notez que l'incrémentation paramdans la fonction ne change pas variable.

#include <stdio.h>

void function(int param) {
    printf("I've received value %d\n", param);
    param++;
}

int main(void) {
    int variable = 111;

    function(variable);
    printf("variable %d\m", variable);
    return 0;
}

Le résultat est

I've received value 111
variable=111

Illusion de passe-par-référence

Nous modifions légèrement le morceau de code. paramest un pointeur maintenant.

#include <stdio.h>

void function2(int *param) {
    printf("I've received value %d\n", *param);
    (*param)++;
}

int main(void) {
    int variable = 111;

    function2(&variable);
    printf("variable %d\n", variable);
    return 0;
}

Le résultat est

I've received value 111
variable=112

Cela vous fait croire que le paramètre a été transmis par référence. Ce n'était pas. Il a été transmis par valeur, la valeur param étant une adresse. La valeur de type int a été incrémentée, et c'est l'effet secondaire qui nous fait penser qu'il s'agissait d'un appel de fonction passe-par-référence.

Pointeurs - transmis par valeur

Comment pouvons-nous montrer / prouver ce fait? Eh bien, nous pouvons peut-être essayer le premier exemple de variables scalaires, mais au lieu de scalaires, nous utilisons des adresses (pointeurs). Voyons voir si cela peut aider.

#include <stdio.h>

void function2(int *param) {
    printf("param's address %d\n", param);
    param = NULL;
}

int main(void) {
    int variable = 111;
    int *ptr = &variable;

    function2(ptr);
    printf("ptr's address %d\n", ptr);
    return 0;
}

Le résultat sera que les deux adresses sont égales (ne vous inquiétez pas de la valeur exacte).

Exemple de résultat:

param's address -1846583468
ptr's address -1846583468

À mon avis, cela prouve clairement que les pointeurs sont transmis par valeur. Sinon, ce ptrserait NULLaprès l'invocation de la fonction.

Ely
la source
69

En C, le passage par référence est simulé en passant l'adresse d'une variable (un pointeur) et en déréférençant cette adresse dans la fonction pour lire ou écrire la variable réelle. Ceci sera appelé «passe-par-référence de style C».

Source: www-cs-students.stanford.edu

Tamas Czinege
la source
50

Parce qu'il n'y a pas de référence de passage dans le code ci-dessus. L'utilisation de pointeurs (tels que void func(int* p)) est une adresse de passage. Ceci est passe-par-référence en C ++ (ne fonctionnera pas en C):

void func(int& ref) {ref = 4;}

...
int a;
func(a);
// a is 4 now
Alexander Gessler
la source
1
J'aime la réponse passe-par-adresse . A plus de sens.
Afzaal Ahmad Zeeshan
Adresse et référence sont synonymes dans ce contexte. Mais vous pouvez utiliser ces termes pour différencier les deux, ce n'est tout simplement pas honnête dans leur sens d'origine.
YungGun
27

Votre exemple fonctionne parce que vous passez l'adresse de votre variable à une fonction qui manipule sa valeur avec l' opérateur de déréférence .

Bien que C ne prenne pas en charge les types de données de référence , vous pouvez toujours simuler le passage par référence en passant explicitement des valeurs de pointeur, comme dans votre exemple.

Le type de données de référence C ++ est moins puissant mais considéré plus sûr que le type de pointeur hérité de C. Ce serait votre exemple, adapté pour utiliser des références C ++ :

void f(int &j) {
  j++;
}

int main() {
  int i = 20;
  f(i);
  printf("i = %d\n", i);

  return 0;
}
Daniel Vassallo
la source
3
Cet article Wikipedia concerne C ++, pas C. Les références existaient avant C ++ et ne dépendent pas de la syntaxe C ++ spéciale pour exister.
1
@Roger: Bon point ... J'ai supprimé la référence explicite au C ++ de ma réponse.
Daniel Vassallo
1
Et ce nouvel article dit "une référence est souvent appelée un pointeur", ce qui n'est pas tout à fait ce que dit votre réponse.
12

Vous passez un pointeur (emplacement de l'adresse) par valeur .

C'est comme dire "voici l'endroit avec les données que je veux que vous mettiez à jour".

antony.trupe
la source
6

p est une variable pointeur. Sa valeur est l'adresse de i. Lorsque vous appelez f, vous passez la valeur de p, qui est l'adresse de i.

Anssi
la source
6

Pas de passage par référence en C, mais p "se réfère" à i, et vous passez p par valeur.

Pavel Radzivilovsky
la source
5

En C, tout est pass-by-value. L'utilisation de pointeurs nous donne l'illusion que nous passons par référence car la valeur de la variable change. Cependant, si vous imprimez l'adresse de la variable de pointeur, vous verrez qu'elle ne sera pas affectée. Une copie de la valeur de l'adresse est transmise à la fonction. Ci-dessous est un extrait illustrant cela.

void add_number(int *a) {
    *a = *a + 2;
}

int main(int argc, char *argv[]) {
   int a = 2;

   printf("before pass by reference, a == %i\n", a);
   add_number(&a);
   printf("after  pass by reference, a == %i\n", a);

   printf("before pass by reference, a == %p\n", &a);
   add_number(&a);
   printf("after  pass by reference, a == %p\n", &a);

}

before pass by reference, a == 2
after  pass by reference, a == 4
before pass by reference, a == 0x7fff5cf417ec
after  pass by reference, a == 0x7fff5cf417ec
Arun Suresh
la source
4

Parce que vous passez un pointeur (adresse mémoire) à la variable p dans la fonction f. En d'autres termes, vous passez un pointeur et non une référence.

Dave
la source
4

Réponse courte: Oui, C implémente le passage de paramètres par référence à l'aide de pointeurs.

Lors de la mise en œuvre du passage de paramètres, les concepteurs de langages de programmation utilisent trois stratégies différentes (ou modèles sémantiques): transférer des données vers le sous-programme, recevoir des données du sous-programme ou faire les deux. Ces modèles sont communément appelés en mode, en mode sortie et en mode sortie, en conséquence.

Plusieurs modèles ont été imaginés par les concepteurs de langage pour implémenter ces trois stratégies de passage de paramètres élémentaires:

Pass-by-Value (sémantique en mode) Pass-by-Result (sémantique en mode out) Pass-by-Value-Result (sémantique en mode inout) Pass-by-Reference (sémantique en mode inout) Pass-by-Name (mode inout sémantique)

Le passage par référence est la deuxième technique pour le passage de paramètres en mode inout. Au lieu de copier des données entre le sous-programme principal et le sous-programme, le système d'exécution envoie un chemin d'accès direct aux données du sous-programme. Dans cette stratégie, le sous-programme a un accès direct aux données partageant efficacement les données avec la routine principale. Le principal avantage de cette technique est qu'elle est absolument efficace dans le temps et l'espace car il n'y a pas besoin de dupliquer l'espace et il n'y a pas d'opérations de copie de données.

L'implémentation de passage de paramètres en C: C implémente une sémantique passe-par-valeur et passe-par-référence (mode inout) en utilisant des pointeurs comme paramètres. Le pointeur est envoyé au sous-programme et aucune donnée réelle n'est copiée du tout. Cependant, comme un pointeur est un chemin d'accès aux données du sous-programme principal, le sous-programme peut modifier les données dans le sous-programme principal. C a adopté cette méthode d'ALGOL68.

Implémentation de passage de paramètres en C ++: C ++ implémente également la sémantique de passage par référence (mode inout) à l'aide de pointeurs et à l'aide d'un type spécial de pointeur, appelé type de référence. Les pointeurs de type référence sont implicitement déréférencés à l'intérieur du sous-programme, mais leur sémantique est également passe-par-référence.

Donc, le concept clé ici est que le passage par référence implémente un chemin d'accès aux données au lieu de copier les données dans le sous-programme. Les chemins d'accès aux données peuvent être des pointeurs explicitement déréférencés ou des pointeurs déréférencés automatiquement (type de référence).

Pour plus d'informations, veuillez vous reporter au livre Concepts of Programming Languages ​​de Robert Sebesta, 10e éd., Chapitre 9.

Francisco Zapata
la source
3

Vous ne passez pas un int par référence, vous passez un pointeur vers un int par valeur. Syntaxe différente, même sens.

xan
la source
1
+1 "Syntaxe différente, même signification." .. Donc la même chose, car la signification importe plus que la syntaxe.
Pas vrai. En appelant void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }avec int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }, il imprime 111 au lieu de 500. Si vous passez par référence, il doit imprimer 500. C ne prend pas en charge le passage de paramètre par référence.
Konfle Dolex
@Konfle, si vous passez syntaxiquement par référence, ptr = &newvalueserait interdit. Quelle que soit la différence, je pense que vous faites remarquer que le "même sens" n'est pas exactement vrai car vous avez également des fonctionnalités supplémentaires en C (la possibilité de réaffecter la "référence" elle-même).
xan
Nous n'écrivons jamais quelque chose comme ptr=&newvalues'il est passé par référence. Au lieu de cela, nous écrivons ptr=newvalueVoici un exemple en C ++: void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }La valeur du paramètre passé dans func () deviendra 500.
Konfle Dolex
Dans le cas de mon commentaire ci-dessus, il est inutile de passer param par référence. Cependant, si le paramètre est un objet au lieu de POD, cela fera une différence significative car tout changement après à l' param = new Class()intérieur d'une fonction n'aurait aucun effet pour l'appelant s'il est passé par valeur (pointeur). Si paramest passé par référence, les modifications seraient visibles pour l'appelant.
Konfle Dolex
3

En C, pour passer par référence, vous utilisez l'opérateur d'adresse &qui doit être utilisé contre une variable, mais dans votre cas, puisque vous avez utilisé la variable de pointeur p, vous n'avez pas besoin de la préfixer avec l'opérateur d'adresse. Il aurait été vrai si vous avez utilisé &icomme paramètre: f(&i).

Vous pouvez également ajouter ceci, pour déréférencer pet voir comment cette valeur correspond i:

printf("p=%d \n",*p);
t0mm13b
la source
Pourquoi avez-vous ressenti le besoin de répéter tout le code (y compris ce bloc de commentaires ..) pour lui dire qu'il devrait ajouter un printf?
@Neil: Cette erreur a été introduite par l'édition de @ William, je vais l'inverser maintenant. Et maintenant, il est évident que tommieb n'a généralement raison: vous pouvez appliquer & à n'importe quel objet, pas seulement aux variables.
2

les pointeurs et les références sont deux thigngs différents.

Deux ou trois choses que je n'ai pas vues ont été mentionnées.

Un pointeur est l'adresse de quelque chose. Un pointeur peut être stocké et copié comme n'importe quelle autre variable. Il a donc une taille.

Une référence doit être considérée comme un ALIAS de quelque chose. Il n'a pas de taille et ne peut pas être stocké. Il DOIT référencer quelque chose, c'est-à-dire. il ne peut pas être nul ou modifié. Eh bien, le compilateur a parfois besoin de stocker la référence en tant que pointeur, mais c'est un détail d'implémentation.

Avec les références, vous n'avez pas de problèmes avec les pointeurs, comme la gestion de la propriété, la vérification nulle, le déréférencement lors de l'utilisation.

user2187033
la source
1

«Passer par référence» (en utilisant des pointeurs) est en C depuis le début. Pourquoi pensez-vous que ce n'est pas le cas?

Maurits Rijk
la source
7
Parce que techniquement, cela ne passe pas par référence.
Mehrdad Afshari
7
Passer une valeur de pointeur n'est pas la même chose que passer par référence. La mise à jour de la valeur de j( not *j ) in f()n'a aucun effet sur iin main().
John Bode
6
C'est sémantiquement la même chose que de passer par référence, et c'est suffisant pour dire que c'est passer par référence. Certes, la norme C n'utilise pas le terme "référence", mais cela ne me surprend pas et n'est pas un problème. Nous ne parlons pas non plus standardese sur SO, même si nous pouvons nous référer à la norme, sinon nous ne verrions personne parler de rvalues ​​(la norme C n'utilise pas le terme).
4
@ Jim: Merci de nous avoir dit que c'est vous qui avez voté pour le commentaire de John.
1

Je pense que C soutient en fait passer par référence.

La plupart des langages nécessitent le sucre syntaxique pour passer par référence au lieu de valeur. (C ++ par exemple nécessite & dans la déclaration de paramètre).

C nécessite également du sucre syntaxique pour cela. C'est * dans la déclaration du type de paramètre et & sur l'argument. Donc * et & est la syntaxe C pour passer par référence.

On pourrait maintenant faire valoir que le véritable passage par référence ne devrait nécessiter que la syntaxe sur la déclaration de paramètre, pas sur le côté argument.

Mais vient maintenant C # qui prend en charge le passage de référence et nécessite du sucre syntaxique à la fois du côté des paramètres et de l'argument.

L'argument selon lequel C n'a pas de référence by-pass fait que les éléments syntaxiques pour l'exprimer présentent l'implémentation technique sous-jacente n'est pas du tout un argument, car cela s'applique plus ou moins à toutes les implémentations.

Le seul argument restant est que le fait de passer par ref en C n'est pas une caractéristique monolithique mais combine deux caractéristiques existantes. (Prenez l'argument ref par &, attendez-vous à taper ref par *.) C #, par exemple, nécessite deux éléments syntaxiques, mais ils ne peuvent pas être utilisés l'un sans l'autre.

C'est évidemment un argument dangereux, car beaucoup d'autres fonctionnalités dans les langues sont composées d'autres fonctionnalités. (comme la prise en charge des chaînes en C ++)

Johannes
la source
1

Ce que vous faites, c'est passer par valeur et non pas par référence. Parce que vous envoyez la valeur d'une variable 'p' à la fonction 'f' (en principal comme f (p);)

Le même programme en C avec passage par référence ressemblera, (!!! ce programme donne 2 erreurs car le passage par référence n'est pas supporté en C)

#include <stdio.h>

void f(int &j) {    //j is reference variable to i same as int &j = i
  j++;
}

int main() {
  int i = 20;
  f(i);
  printf("i = %d\n", i);

  return 0;
}

Production:-

3:12: erreur: attendue ';', ',' ou ')' avant le '&' jeton
             void f (int & j);
                        ^
9: 3: avertissement: déclaration implicite de la fonction 'f'
               FA);
               ^
Sunay
la source