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;
}
$ gcc -std = c99 test.c $ a.exe i = 21
Parce que vous passez la valeur du pointeur sur la méthode, puis en le déréférencant pour obtenir le nombre entier pointé.
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 une valeur de passage sans exception. Passer un pointeur en tant que paramètre ne signifie pas passer par référence.
La règle est la suivante:
ne fonction ne peut pas changer la valeur réelle des paramètres.
Essayons de voir les différences entre les paramètres scalaires et les paramètres de pointeur d'une fonction.
Ce court programme montre passage par valeur en utilisant une variable scalaire. param
est appelé le paramètre formel et variable
à l'invocation de la fonction est appelé paramètre réel. Notez que l'incrémentation param
dans 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
Nous modifions légèrement le code. param
est 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é passé par référence. Ce n'était pas. Il a été passé 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.
Comment pouvons-nous montrer/prouver ce fait? Eh bien, peut-être que nous pouvons essayer le premier exemple de variables scalaires, mais au lieu de scalaires, nous utilisons des adresses (pointeurs). Voyons 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 indicateurs sont passés par valeur. Sinon, ptr
serait NULL
après l'invocation de la fonction.
En C, le passage par référence est simulé en passant l'adresse d'une variable (un pointeur) et en déréférencant cette adresse dans la fonction pour lire ou écrire la variable réelle. Cela sera appelé "passe-à-référence de style C".
Parce qu'il n'y a pas de passage par référence dans le code ci-dessus. L'utilisation de pointeurs (tels que void func(int* p)
) est une adresse pass-by-address. Ceci est passé 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 parce que vous passez l'adresse de votre variable à une fonction qui manipule sa valeur avec opérateur de déréférence .
Bien que C ne prenne pas en charge types de données de référence , vous pouvez toujours simuler le passage par référence en transmettant explicitement les 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 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 (emplacement de l'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 transmettez la valeur de p, qui est l'adresse de i.
En C, tout est passe-par-valeur. L'utilisation de pointeurs nous donne l'illusion que nous passons par référence car le valeur de la variable change. Cependant, si vous deviez imprimer l'adresse de la variable de pointeur, vous verrez qu'elle n'est pas affectée. Un copie de la valeur de l'adresse est transmis à 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
Pas de référence par référence dans C, mais p "renvoie" à i, et vous transmettez p par valeur.
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.
Réponse courte: Oui, C implémente les paramètres en passant 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 généralement appelés mode in, mode out et mode inout.
Les concepteurs de langage ont conçu plusieurs modèles pour mettre en œuvre ces trois stratégies de passage de paramètres élémentaires:
Pass-par-Valeur (en sémantique de mode) Pass-à-Résultat (sémantique en mode sortant) Pass-à-Valeur-Résultat (sémantique en mode inout) Pass-à-Référence (sémantique en mode inout) Passé par nom (en mode inout) sémantique)
Le passage par référence est la deuxième technique de passage des paramètres en mode inout. Au lieu de copier les données entre la routine principale 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’il est absolument efficace dans le temps et dans l’espace car il n’est pas nécessaire de dupliquer l’espace ni d’opérations de copie de données.
L'implémentation de passage de paramètre dans C: C implémente la sémantique passage par valeur et passage 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. Cependant, comme un pointeur est un chemin d'accès 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 d’ALGOL68.
Implémentation de passage de paramètres en C++: C++ implémente également la sémantique passe-à-référence (mode inout) à l'aide de pointeurs et 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 transmise par référence.
Le concept clé ici est donc que la procédure de référence par référence implémente un chemin d'accès aux données au lieu de les copier 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 auto-déréférencés (type de référence).
Pour plus d'informations, reportez-vous au livre Concepts of Programming Languages de Robert Sebesta, 10e éd., Chapitre 9.
Vous ne passez pas un int par référence, vous passez un pointeur sur une int par valeur. Syntaxe différente, même sens.
En C, pour passer par référence, vous utilisez l'adresse-de l'opérateur &
qui doit être utilisée avec une variable, mais dans votre cas, puisque vous avez utilisé la variable de pointeur p
, vous n'avez pas besoin de préfixez-le avec l'adresse-de l'opérateur. Cela aurait été vrai si vous utilisiez &i
comme paramètre: f(&i)
.
Vous pouvez également ajouter ceci pour déréférencer p
et voir comment cette valeur correspond à i
:
printf("p=%d \n",*p);
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 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 faire référence à quelque chose, à savoir. il ne peut pas être nul ou modifié. Parfois, le compilateur doit stocker la référence sous forme de pointeur, mais il s’agit d’un détail de la mise en oeuvre.
Avec les références, vous ne rencontrez pas de problèmes avec les pointeurs, comme la gestion de la propriété, la vérification des valeurs nulles, le déréférencement de l’utilisation.
"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 soutient en fait passer 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 du paramètre).
C a également besoin de 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 affirmer que le passage réel par référence ne devrait nécessiter que la syntaxe de la déclaration de paramètre, pas du côté argument.
Mais vient maintenant C # qui ne supporte par référence en passant et requiert un sucre syntaxique sur les deux côtés paramètres et arguments.
L'argument selon lequel C n'a pas de passage par référence, ce qui oblige les éléments syntaxiques à l'exprimer, mettant en évidence 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 passage par ref en C n'est pas une fonctionnalité monolithique, mais combine deux fonctionnalités existantes. (Prenez référence à l'argument de &, attendez-vous à ce que ref se fasse par *.) Par exemple, C # nécessite deux éléments syntaxiques, mais ils ne peuvent pas être utilisés l'un sans l'autre.
Ceci est évidemment un argument dangereux, car beaucoup d'autres fonctionnalités en langues sont composées d'autres fonctionnalités. (comme le support des chaînes en C++)
Ce que vous faites est de passer par valeur, pas de passer par référence. Parce que vous envoyez la valeur d'une variable 'p' à la fonction 'f' (en général as f (p);)
Le même programme en C avec passage par référence ressemblera à (!!! ce programme génère 2 erreurs, car passe 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;
}
Sortie:-
3:12: erreur: attendu ';', ',' ou ') avant' & 'token Void f (int & j); ^ 9: 3: avertissement: déclaration implicite de la fonction 'f' F (a); ^