Passage par renvoi en C

Si C ne supporte pas le passage d’une variable par référence, pourquoi cela fonctionne-t-il?

#include  void f(int *j) { (*j)++; } int main() { int i = 20; int *p = &i; f(p); printf("i = %d\n", i); return 0; } 

Sortie

 $ gcc -std = c99 test.c
 $ a.exe
 i = 21 

Parce que vous transmettez la valeur du pointeur à la méthode, puis la déréférençons pour obtenir le nombre entier pointé.

Ce n’est pas une référence par référence, c’est-à-dire la valeur par référence, comme d’autres ont déclaré.

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 ne peut pas modifier la valeur des parameters réels.


Essayons de voir les différences entre les parameters scalaires et les parameters de pointeur d’une fonction.

Variables scalaires

Ce programme court montre pass-by-value en utilisant une variable scalaire. param est appelé paramètre formel et variable à l’invocation de la fonction s’appelle paramètre réel. Notez que le param incrémentant dans la fonction ne change pas la variable .

 #include  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 passage par référence

Nous changeons légèrement le morceau de code. param est un pointeur maintenant.

 #include  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é passé par référence. Ce n’était pas. Il a été passé par valeur, la valeur du paramètre é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 de référence par référence.

Pointeurs – Pass-by-value

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

 #include  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 indicateurs sont transmis par valeur. Sinon, ptr serait NULL après l’appel de la fonction.

En C, on simule le passage par référence 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. Cela sera appelé “référence de style C”.

Source: www-cs-students.stanford.edu

Parce qu’il n’y a pas de référence par référence dans le code ci-dessus. L’utilisation de pointeurs (tels que void func(int* p) ) est une adresse par adresse. Ceci est une référence 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 

Votre exemple fonctionne car vous transmettez 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é comme 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; } 

Vous passez un pointeur (adresse) par valeur .

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

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

Pas de référence par référence dans C, mais p “se réfère” à i, et vous passez p par valeur.

Réponse courte: Oui, C implémente le passage de parameters par référence à l’aide de pointeurs.

Lors de la mise en œuvre du passage de parameters, les concepteurs de langages de programmation utilisent trois stratégies (ou modèles sémantiques) différents: transférer des données vers le sous-programme, recevoir des données du sous-programme ou faire les deux. Ces modèles sont généralement connus en mode, en mode sortie et en mode inout, en conséquence.

Plusieurs modèles ont été conçus par les concepteurs de langage pour mettre en œuvre ces trois stratégies de transmission de parameters élémentaires:

Pass-by-Value (sémantique en mode) Pass-by-result (sémantique en mode out) Résultat de la valeur (sémantique du mode inout) Pass-by-reference (sémantique du mode inout) Pass-by-name (mode inout sémantique)

Pass-by-reference est la deuxième technique de transmission des parameters en mode inout. Au lieu de copier des données entre la routine principale et le sous-programme, le système d’exécution envoie un chemin d’access direct aux données du sous-programme. Dans cette stratégie, le sous-programme a un access 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 dans l’espace car il n’est pas nécessaire de dupliquer de l’espace et il n’ya pas d’opérations de copie de données.

L’implémentation des parameters dans C: C implémente la sémantique pass-by-value et aussi pass-by-reference (mode inout) en utilisant des pointeurs comme parameters. Le pointeur est envoyé au sous-programme et aucune donnée réelle n’est copiée. Cependant, un pointeur étant un chemin d’access aux données de la routine principale, le sous-programme peut modifier les données de la routine principale. C a adopté cette méthode à partir d’ALGOL68.

Implémentation de passage de parameters en C ++: C ++ implémente également la sémantique pass-by-reference (mode inout) en utilisant des pointeurs et en utilisant également 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 dans le sous-programme, mais leur sémantique est également une référence pour chaque référence.

Le concept clé ici est que le passage par référence implémente un chemin d’access aux données au lieu de copier les données dans le sous-programme. Les chemins d’access 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, reportez-vous au livre Concepts of Languages ​​Languages ​​de Robert Sebesta, 10ème édition, chapitre 9.

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 du pointeur, vous verrez qu’il n’est pas affecté. Une copie de la valeur de l’adresse est transmise à la fonction. Ci-dessous 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 

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.

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

Dans C, pour passer par référence, vous utilisez l’adresse de l’opérateur & qui devrait être utilisé contre une variable, mais dans votre cas, puisque vous avez utilisé la variable pointeur p , vous n’avez pas besoin de lui append l’adresse de l’opérateur . Cela aurait été vrai si vous utilisiez &i comme paramètre: f(&i) .

Vous pouvez également append ceci, à dereference p et voir comment cette valeur correspond à:

 printf("p=%d \n",*p); 

“Passer par référence” (en utilisant des pointeurs) a été en C depuis le début. Pourquoi pensez-vous que ce n’est pas?

Je pense que C appuie en fait le passage par référence.

La plupart des langues exigent que le sucre syntaxique passe par référence plutôt que par 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 de type de paramètre et & sur l’argument. Donc * et & est la syntaxe C pour passer par référence.

On pourrait maintenant affirmer que la référence par référence réelle ne devrait exiger que la syntaxe de la déclaration de paramètre, et non du côté argument.

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

L’argument selon lequel C n’a pas de renvoi par référence entraîne que les éléments syntaxiques qui l’expriment ne présentent pas l’implémentation technique sous-jacente, car cela s’applique plus ou moins à toutes les implémentations.

Le seul argument restant est que le passage par ref dans C n’est pas une caractéristique monolithique mais combine deux entités existantes. (Prenez l’argument de & par, et attendez que ref taper 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 le support de chaîne en C ++)

Ce que vous faites est que passer par valeur ne passe pas par référence. Parce que vous envoyez la valeur d’une variable ‘p’ à la fonction ‘f’ (dans main comme f (p);)

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

 #include  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; } 

Sortie:-

 3:12: error: attendu ';', ',' ou ')' avant '&' jeton
              annuler f (int & j);
                         ^
 9: 3: avertissement: déclaration implicite de la fonction 'f'
                FA);
                ^

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

Un couple de choses que je n’ai pas vu mentionné.

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

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

Avec les références, vous ne rencontrez pas de problèmes avec les pointeurs, tels que la gestion de propriété, la vérification de nullité, le dé-référencement à l’utilisation.