Structures et fonctions en C

30 Aug 2019 30 Aug 2019 26132 vues ESSADDOUKI Mostafa 9 min de lecture

Structures et fonctions en C

En C, une structure peut être passée à une fonction et retournée par une fonction exactement comme tout autre type. Il existe plusieurs stratégies de passage, chacune avec ses avantages en termes de lisibilité, de performance et de capacité à modifier les données originales.

Prérequis Ce cours suppose la maîtrise des fonctions (paramètres, retour), des pointeurs (adresse, déréférencement, opérateur ->) et des structures (struct, initialisation, accès aux membres).
Mode de passageSyntaxe (appel)Copie ?Modifie l'original ?Recommandé pour
Par valeurf(et)Oui (copie complète)NonPetites structures, lecture seule
Par pointeurf(&et)Non (adresse seule)OuiGrandes structures, modification
Retour par valeuret = f(...)Oui (copie retournée)Création/construction d'une struct
Retour par pointeurp = f(&et)Non (adresse retournée)Chaînage, allocation dynamique

1. Passage d'une structure par valeur

Passage par valeur Lorsqu'une structure est passée par valeur, une copie complète de tous ses membres est créée dans le paramètre formel de la fonction. Toute modification dans la fonction n'affecte pas la variable originale.

Syntaxe C
/* Déclaration */
void maFonction(struct NomType param);

/* Appel — la structure est copiée */
maFonction(variable);

Exemple n°1 — Afficher une structure par valeur

#include <stdio.h>

struct etudiant {
    char prenom[20];
    int  age;
};

/* Reçoit une copie — et est indépendant de et1 */
void afficher(struct etudiant et)
{
    printf("Prénom : %s\n", et.prenom);
    printf("Âge    : %d\n", et.age);
}

int main(void)
{
    struct etudiant et1 = {"Mostafa", 24};
    afficher(et1);    /* copie de et1 transmise */
    return 0;
}
Sortie
Prénom : Mostafa
Âge    : 24
Attention — Coût de la copie par valeur Passer une structure par valeur copie tous ses membres en mémoire. Pour une petite structure (2–3 champs scalaires) le coût est négligeable. Pour une grande structure ou un appel fréquent, cette copie peut nuire aux performances. Préférer le passage par pointeur dans ce cas.

2. Passage d'une structure par pointeur

Passage par pointeur (passage par référence) Lorsqu'on passe l'adresse d'une structure, la fonction reçoit un pointeur vers la variable originale. Seule l'adresse (généralement 8 octets) est copiée — pas la structure entière. La fonction peut lire et modifier les membres de l'original.

Syntaxe C
/* Déclaration */
void maFonction(struct NomType *param);

/* Appel — seule l'adresse est transmise */
maFonction(&variable);

Exemple n°2 — Afficher par pointeur

#include <stdio.h>

struct etudiant {
    char prenom[20];
    int  age;
};

/* Reçoit l'adresse — accès via -> */
void afficher(struct etudiant *et)
{
    printf("Prénom : %s\n", et->prenom);
    printf("Âge    : %d\n", et->age);
}

int main(void)
{
    struct etudiant et1 = {"Mostafa", 24};
    afficher(&et1);   /* adresse de et1 transmise */
    return 0;
}
Sortie
Prénom : Mostafa
Âge    : 24

Exemple n°3 — Addition de nombres complexes (résultat par pointeur)

#include <stdio.h>

struct complexe {
    float R;   /* partie réelle      */
    float I;   /* partie imaginaire  */
};

/* c1 et c2 passés par valeur (lecture seule), res par pointeur (écriture) */
void ajouter(struct complexe c1, struct complexe c2, struct complexe *res)
{
    res->R = c1.R + c2.R;
    res->I = c1.I + c2.I;
}

int main(void)
{
    struct complexe c1 = {2.5f, 3.0f};
    struct complexe c2 = {1.24f, 4.0f};
    struct complexe somme;

    ajouter(c1, c2, &somme);

    printf("Résultat : %.2f + %.2fi\n", somme.R, somme.I);
    return 0;
}
Sortie
Résultat : 3.74 + 7.00i
Stratégie mixte — valeur + pointeur Dans l'exemple n°3, c1 et c2 sont passées par valeur (lecture seule — on ne veut pas les modifier), tandis que res est passée par pointeur (on veut écrire le résultat dedans). C'est une technique idiomatique en C pour les fonctions qui calculent un résultat dans une variable fournie par l'appelant.
Astuce — const avec passage par pointeur Pour indiquer qu'une fonction reçoit un pointeur mais ne modifie pas la structure, utiliser le qualificateur const. Cela documente l'intention et laisse le compilateur détecter les modifications accidentelles :
/* Lecture seule — const interdit la modification */
void afficher(const struct etudiant *et)
{
    printf("%s\n", et->prenom);
    /* et->age = 0; — erreur de compilation */
}

/* Modification autorisée — sans const */
void modifier(struct etudiant *et)
{
    et->age += 1;
}

3. Retourner une structure par valeur

Une fonction peut retourner une structure complète. Le type de retour est alors struct NomType. Une copie de la structure locale est retournée à l'appelant.


Syntaxe C
struct NomType maFonction(paramètres)
{
    struct NomType var;
    /* ... remplissage ... */
    return var;   /* retourne une copie */
}

Exemple n°4 — Saisir et retourner une structure

#include <stdio.h>

struct etudiant {
    char prenom[20];
    int  age;
};

/* Crée une struct locale, la remplit et retourne une copie */
struct etudiant saisir(void)
{
    struct etudiant e;

    printf("Prénom : ");
    scanf("%s", e.prenom);

    printf("Âge    : ");
    scanf("%d", &e.age);

    return e;   /* copie retournée vers l'appelant */
}

void afficher(const struct etudiant *et)
{
    printf("\n--- Informations ---\n");
    printf("Prénom : %s\n", et->prenom);
    printf("Âge    : %d\n", et->age);
}

int main(void)
{
    struct etudiant et = saisir();   /* copie reçue et affectée */
    afficher(&et);
    return 0;
}
Sortie
Prénom : Mostafa
Âge    : 25

--- Informations ---
Prénom : Mostafa
Âge    : 25
Danger — Ne jamais retourner un pointeur sur une variable locale Une variable locale est détruite à la fin de la fonction. Retourner son adresse produit un pointeur fantôme (dangling pointer) :
/* Dangereux — e est détruite à la fin de saisir() */
struct etudiant *saisir(void)
{
    struct etudiant e;
    scanf("%s", e.prenom);
    return &e;   /* pointeur invalide dès le retour */
}

/* Solutions correctes */
/* 1 — retourner par valeur */
struct etudiant saisir(void) { struct etudiant e; ...; return e; }

/* 2 — allouer dynamiquement */
struct etudiant *saisir(void) {
    struct etudiant *e = malloc(sizeof(*e));
    ...; return e;   /* appelant doit appeler free() */
}

/* 3 — recevoir un pointeur en paramètre */
void saisir(struct etudiant *e) { scanf("%s", e->prenom); }

4. Retourner un pointeur sur une structure

Une fonction peut aussi retourner un pointeur sur une structure. Cela évite la copie et permet le chaînage d'appels. La structure pointée doit rester valide après le retour — elle doit être soit allouée dynamiquement, soit reçue en paramètre.


Syntaxe C
struct NomType *maFonction(struct NomType *param)
{
    /* ... modification via param->membre ... */
    return param;   /* retourne l'adresse reçue */
}

Exemple n°5 — Saisir via pointeur et retourner le pointeur

#include <stdio.h>

struct etudiant {
    char prenom[20];
    int  age;
};

/* Reçoit un pointeur, le remplit, le retourne */
struct etudiant *saisir(struct etudiant *e)
{
    printf("Prénom : ");
    scanf("%s", e->prenom);

    printf("Âge    : ");
    scanf("%d", &e->age);

    return e;
}

int main(void)
{
    struct etudiant et;
    struct etudiant *p = saisir(&et);   /* p pointe sur et */

    printf("\nPrénom : %s\n", p->prenom);
    printf("Âge    : %d\n",   p->age);

    /* Vérification : p et &et pointent sur la même zone */
    printf("p == &et : %s\n", (p == &et) ? "oui" : "non");

    return 0;
}
Sortie
Prénom : Mostafa
Âge    : 26

Prénom : Mostafa
Âge    : 26
p == &et : oui

Exemple n°6 — Allocation dynamique et retour de pointeur

#include <stdio.h>
#include <stdlib.h>

struct etudiant {
    char prenom[20];
    int  age;
};

/* Alloue une struct sur le tas, la remplit, retourne l'adresse */
struct etudiant *creer(const char *prenom, int age)
{
    struct etudiant *e = (struct etudiant *) malloc(sizeof(struct etudiant));
    if (e == NULL) return NULL;

    /* Copie sécurisée du prénom */
    int i;
    for (i = 0 ; prenom[i] != '\0' && i < 19 ; i++)
        e->prenom[i] = prenom[i];
    e->prenom[i] = '\0';

    e->age = age;
    return e;
}

int main(void)
{
    struct etudiant *e1 = creer("Mostafa", 23);
    struct etudiant *e2 = creer("Dounia",  22);

    if (e1) printf("e1 : %s, %d ans\n", e1->prenom, e1->age);
    if (e2) printf("e2 : %s, %d ans\n", e2->prenom, e2->age);

    free(e1); e1 = NULL;
    free(e2); e2 = NULL;
    return 0;
}
Sortie
e1 : Mostafa, 23 ans
e2 : Dounia, 22 ans

Récapitulatif

ModeDéclaration fonctionAppelAvantageInconvénient
Passage valeurvoid f(struct T s)f(var)Sécurité (original intact)Copie coûteuse si grande struct
Passage pointeurvoid f(struct T *s)f(&var)Rapide, modifie l'originalRisque de modification accidentelle
Pointeur constvoid f(const struct T *s)f(&var)Rapide + lecture seule garantie
Retour valeurstruct T f(...)var = f(...)Lisible, pas de pointeur fantômeCopie à la sortie
Retour pointeurstruct T *f(...)p = f(&var)Pas de copie, chaînableRisque de pointeur fantôme

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.