2 votes

Ai-je bien compris les notions de passage par valeur et de passage par référence en C ?

Dans l'une des vidéos tutorielles de mon cours, j'ai rencontré une situation où le passage par valeur et le passage par référence se produisent. Dans l'exemple qu'il nous donne, il admet d'abord que le code est mal écrit, puis il nous demande de trouver ce qui serait imprimé exactement. Le code en question est le suivant :

void set_array(int array[4]);
void set_int(int x);

int main(void){
    int a = 10; 
    int b[4] = {0, 1, 2, 3);
    set_int(a);
    set_array(b);
    printf("%d %d\n", a, b[0]);
}

void set_array(int array[4]){
    array[0] = 22;
}

void set_int(int x){
    x = 22;
}

Ce dont je veux m'assurer, après avoir initialement obtenu un résultat erroné, c'est que je comprends pourquoi le résultat correct de "10, 22" est imprimé.

La valeur 10 est imprimée parce que lorsque la fonction set_int est appelée, son paramètre, étant une variable, signifie que n'importe quelle variable peut être passée. Elle n'est pas exclusive de la valeur 22 telle que définie dans la fonction set_int.

La valeur 22 est imprimée parce que, lorsque la fonction set_array est appelée, son paramètre, qui est un tableau, signifie que seule la valeur originale peut être transmise car il pointe vers un emplacement mémoire spécifique où la valeur 22 est stockée.

Ai-je mal compris ce qui se passe, et ai-je manqué des points cruciaux ?

10voto

Sourav Ghosh Points 54713

Par définition, il n'y a pas de passage par référence dans les appels de fonction en C. Vos deux exemples utilisent le passage par valeur. Cependant, selon l'utilisation, le résultat varie (c'est-à-dire que nous pouvons émuler le comportement du passage par référence).

  • Dans le premier cas, la variable a est transmis par valeur, il ne peut donc pas être modifié à partir de l'appel de la fonction.

  • Dans le second cas, la variable b est transmis par valeur, il ne peut donc pas être modifié à partir de l'appel de la fonction.

    Cependant, dans le second cas, un phénomène particulier se produit. L'argument qui est passé, b est un tableau. Un tableau, lorsqu'il est passé comme argument de fonction (parmi de nombreux autres usages), se transforme en un pointeur vers le premier élément du tableau. Donc, essentiellement, vous passez un pointeur, et la valeur à l'adresse mémoire pointée par le pointeur peut être modifiée (mais pas le pointeur lui-même). C'est pourquoi, le changement de la méthode set_array() persiste dans le main() .

    En d'autres termes, le deuxième cas est équivalent à

    void set_pointer(int *ptr);
    void set_int(int x);
    
    int main(void){
        int a = 10; 
        int b[4] = {0, 1, 2, 3);
        int * p = &(b[0]);         // address of the first element
        set_int(a);
        set_pointer(p);           // pass the pointer
        printf("%d %d\n", a, b[0]);
    }
    
    void set_array(int *ptr){
        *ptr = 22;                 // operate on pointer
    }
    
    void set_int(int x){
        x = 22;
    }

3voto

John Bode Points 33046

Les expressions de tableau sont spéciales en C.

6.3.2.1 Valeurs L, tableaux et désignateurs de fonctions
...
3 Sauf lorsqu'il s'agit de l'opérande de la fonction sizeof l'opérateur, le _Alignof ou l'opérateur unaire & ou est un littéral de chaîne de caractères utilisé pour initialiser un tableau, une expression qui a type ''tableau de type'' est convertie en une expression de type ''pointeur de type'' qui pointe sur l'élément vers l'élément initial de l'objet tableau et qui n'est pas une lvalue. Si l'objet tableau a classe de stockage de registre, le comportement est indéfini.

C Projet en ligne 2011

Lorsque vous appelez

set_array(b);

el expression b est converti du type "tableau à 4 éléments de int "en "pointeur vers int " ( int * ), et la valeur de l'expression est l'adresse de b[0] . Par conséquent, ce qui set_array reçoit est un pointeur valeur ( &b[0] ), et non un tableau.

6.7.6.3 Déclarateurs de fonctions (y compris les prototypes)
...
7 La déclaration d'un paramètre en tant que ''tableau de type'' doit être adaptée en ''pointeur qualifié de type ", où les qualificatifs de type (s'il y en a) sont ceux spécifiés dans l'expression [ y ] de la dérivation du type de tableau. Si le mot-clé static apparaît également dans le [ y ] de la de la dérivation du type de tableau, alors, pour chaque appel à la fonction, la valeur de l'argument effectif correspondant doit permettre d'accéder au premier élément d'un tableau comportant au moins autant d'éléments que d'éléments. d'éléments que ceux spécifiés par l'expression de la taille.

ibid.

Fondamentalement, dans une définition de fonction, tout paramètre déclaré en tant que T a[N] o T a[] doit être interprété comme T *a - Autrement dit, le paramètre est traité comme un pointeur, et non comme un tableau. Votre définition de fonction

void set_array(int array[4]){
    array[0] = 22;
}

est traité comme s'il avait été écrit

void set_array(int *array){
    array[0] = 22;
}

Les tableaux sont donc, en quelque sorte, passés par référence en C. Ce qui se passe réellement, c'est qu'un pointeur vers le premier élément du tableau est passé par valeur . Le site array dans set_array désigne un objet distinct en mémoire de b Ainsi, toute modification apportée à array lui-même n'ont aucun effet sur b (C'est-à-dire que vous pourriez attribuer une nouvelle valeur à array et le faire pointer vers un objet différent, mais cette affectation n'affecte pas b du tout). Au lieu de cela, ce que vous faites est d'accéder à des éléments de b par le biais de array .

En image :

       +---+
    b: |   | b[0] <---+
       +---+          |
       |   | b[1]     |
       +---+          |
       |   | b[2]     |
       +---+          |
       |   | b[3]     |
       +---+          |
        ...           |
       +---+          |
array: |   | ---------+
       +---+

Une conséquence pratique de ceci est que sizeof array donne la taille de la pointeur type int * et non la taille du tableau, contrairement à sizeof b . Cela signifie que vous ne pouvez pas compter le nombre d'éléments dans le tableau en utilisant la fonction sizeof array / sizeof array[0] tour. Lorsque vous passez un tableau en tant qu'argument à une fonction, vous devez également passer la taille du tableau (c'est-à-dire le nombre d'éléments) comme un paramètre séparé. sauf si le tableau contient une valeur sentinelle bien définie (comme le terminateur 0 dans les chaînes de caractères).

Rappelez-vous que l'expression a[i] es défini comme *(a + i) - avec une adresse de départ a , offset i éléments (pas des octets !) à partir de cette adresse et déférer le résultat. Si a est une expression de tableau, elle est d'abord convertie en expression de pointeur avant que ce calcul ne soit effectué. C'est pourquoi vous pouvez accéder aux éléments de b par le biais de array - array stocke simplement le adresse du premier élément de b .

En C, tous les arguments de fonction sont transmis par valeur - l'argument formel dans la définition de la fonction et l'argument réel dans l'appel de fonction font référence à des objets distincts en mémoire, et la valeur de l'argument réel est copiée dans l'argument formel. Les modifications apportées à l'argument formel ne sont pas reflétées dans l'argument réel.

Nous faux sémantique pass-by-reference en transmettant un pointeur au paramètre réel et écrire au pointeur déréférencé :

void foo( T *ptr )
{
  *ptr = new_value();  // write a new value to the thing ptr points to
}

void bar( void )
{
  T var;
  foo( &var ); // have foo write a new value to var
}

Je veux dire, écrire à *ptr est la même chose que d'écrire dans var . Écrire à ptr par contre, n'a aucun effet en dehors de foo .

Dans un véritable système pass-by-reference (comme, par exemple, Fortran), l'argument formel dans la définition de la fonction et l'argument réel dans l'appel de la fonction désignent tous deux le même objet (au sens de "chose qui occupe de la mémoire et peut stocker des valeurs", et non au sens orienté objet "instance d'une classe"), de sorte que dans ces systèmes, toute modification de l'argument formel sont reflétée dans l'argumentation proprement dite (ce qui conduit à une question classique sur les Test de piratage "Avez-vous déjà changé la valeur de 4 ? De manière non intentionnelle ? Dans un langage autre que Fortran ?")

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X