Langage C/C++

Fonctions qui Retournent des Tableaux en C

La programmation en C est un outil puissant pour gérer des structures de données et manipuler des tableaux. Une fonction qui retourne un tableau en C peut être particulièrement utile, mais elle présente aussi certains défis en raison de la gestion de la mémoire. Dans cet article, nous allons examiner comment créer et retourner un tableau à partir d’une fonction en C.

Pré-requis

Avant de commencer, vous devez être à l’aise avec les concepts de base de la programmation en C, y compris les pointeurs, l’allocation dynamique de mémoire, et la manipulation de tableaux.

Création et Retour d’un Tableau en C

Déclaration d’une Fonction qui Retourne un Pointeur

En C, les fonctions ne peuvent pas retourner des tableaux directement. Cependant, elles peuvent retourner un pointeur vers le premier élément du tableau. Voici un exemple de la déclaration d’une telle fonction :

int* createArray(int size);
Allocation Dynamique de Mémoire

Pour retourner un tableau, nous devons allouer de la mémoire dynamiquement. Cela peut être fait en utilisant la fonction malloc de la bibliothèque standard. Voici un exemple de fonction qui alloue de la mémoire pour un tableau d’entiers et retourne un pointeur vers ce tableau :

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

int* createArray(int size) {
    int* array = (int*)malloc(size * sizeof(int));
    if (array == NULL) {
        printf("Memory allocation failed!\n");
        return NULL;
    }
    return array;
}
Initialisation du Tableau

Nous pouvons initialiser le tableau avant de le retourner. Voici comment modifier la fonction précédente pour initialiser le tableau avec des valeurs séquentielles :

int* createArray(int size) {
    int* array = (int*)malloc(size * sizeof(int));
    if (array == NULL) {
        printf("Memory allocation failed!\n");
        return NULL;
    }
    for (int i = 0; i < size; i++) {
        array[i] = i;
    }
    return array;
}
Utilisation de la Fonction dans le Code Principal

Voyons maintenant comment utiliser cette fonction dans le programme principal :

int main() {
    int size = 10;
    int* myArray = createArray(size);

    if (myArray == NULL) {
        return 1;
    }

    for (int i = 0; i < size; i++) {
        printf("%d ", myArray[i]);
    }
    printf("\n");

    // Libérer la mémoire allouée
    free(myArray);

    return 0;
}
Gestion de la Mémoire

Il est crucial de libérer la mémoire allouée dynamiquement une fois qu’elle n’est plus nécessaire pour éviter les fuites de mémoire. Cela se fait avec la fonction free :

free(myArray);
Applications des Fonctions qui Retournent des Tableaux en C

Les fonctions qui retournent des tableaux peuvent être utilisées dans une variété d’applications. Voici quelques exemples concrets où ces fonctions sont particulièrement utiles.

1. Traitement d’Images

Dans le traitement d’images, les images sont souvent représentées sous forme de tableaux bidimensionnels de pixels. Une fonction qui retourne un tableau peut être utilisée pour lire une image depuis un fichier et la retourner sous forme de tableau pour un traitement ultérieur.

unsigned char* loadImage(const char* filename, int* width, int* height);

Cette fonction pourrait lire une image d’un fichier, allouer la mémoire nécessaire pour stocker les pixels de l’image et retourner un pointeur vers ce tableau.

2. Génération de Séries Numériques

Les fonctions qui retournent des tableaux peuvent être utilisées pour générer des séries numériques. Par exemple, pour générer une séquence de nombres premiers, nous pourrions utiliser une fonction qui alloue un tableau pour stocker les nombres premiers et retourne un pointeur vers ce tableau.

int* generatePrimes(int n);

Cette fonction pourrait utiliser un algorithme comme le crible d’Ératosthène pour remplir un tableau avec les premiers n nombres premiers.

3. Tri et Recherche

Dans de nombreuses applications, il est nécessaire de trier et de rechercher des données. Une fonction qui retourne un tableau trié pourrait être utilisée pour ces opérations.

int* sortArray(int* array, int size);

Cette fonction pourrait implémenter un algorithme de tri (par exemple, quicksort) pour trier un tableau et retourner le tableau trié.

4. Simulation et Modélisation

Les simulations et les modèles numériques, tels que les simulations de Monte Carlo ou les modèles physiques, utilisent souvent des tableaux pour stocker des données. Une fonction qui retourne un tableau peut être utilisée pour initialiser les conditions de simulation ou pour stocker les résultats de la simulation.

double* runSimulation(int steps);

Cette fonction pourrait exécuter une simulation sur un nombre spécifié d’étapes, allouer un tableau pour stocker les résultats à chaque étape et retourner ce tableau.

5. Analyse de Données

Dans l’analyse de données, les fonctions qui retournent des tableaux peuvent être utilisées pour manipuler et analyser des ensembles de données. Par exemple, pour extraire une colonne spécifique de données d’un tableau bidimensionnel, nous pourrions utiliser une fonction comme celle-ci :

double* extractColumn(double** data, int numRows, int columnIndex);

Cette fonction pourrait allouer un tableau pour stocker les valeurs de la colonne spécifiée et retourner ce tableau.

6. Calculs Mathématiques et Algébriques

Des fonctions qui retournent des tableaux peuvent également être utilisées dans des calculs mathématiques avancés, comme la multiplication de matrices ou la transformation de Fourier.

double* multiplyMatrices(double* matrixA, double* matrixB, int size);

Cette fonction pourrait effectuer la multiplication de deux matrices carrées de taille size et retourner le résultat sous forme de tableau.

Exemple Concret : Génération de Séries Numériques

Prenons un exemple détaillé de génération de séries numériques. Voici une implémentation de la fonction generatePrimes mentionnée ci-dessus :

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

int* generatePrimes(int n, int* count) {
    bool* isPrime = (bool*)malloc((n + 1) * sizeof(bool));
    for (int i = 2; i <= n; i++) {
        isPrime[i] = true;
    }

    for (int p = 2; p * p <= n; p++) {
        if (isPrime[p]) {
            for (int i = p * p; i <= n; i += p) {
                isPrime[i] = false;
            }
        }
    }

    int* primes = (int*)malloc(n * sizeof(int));
    *count = 0;
    for (int i = 2; i <= n; i++) {
        if (isPrime[i]) {
            primes[(*count)++] = i;
        }
    }

    free(isPrime);
    return primes;
}

int main() {
    int n = 50;
    int count;
    int* primes = generatePrimes(n, &count);

    for (int i = 0; i < count; i++) {
        printf("%d ", primes[i]);
    }
    printf("\n");

    free(primes);
    return 0;
}

Dans cet exemple, la fonction generatePrimes utilise le crible d’Ératosthène pour générer les nombres premiers jusqu’à n et retourne un tableau contenant ces nombres premiers.


Fonction qui retourne un tableau en C

En langage C, une fonction ne peut pas retourner directement un tableau classique. Plusieurs solutions permettent toutefois d’obtenir un résultat équivalent : pointeur, tableau transmis en paramètre, allocation dynamique ou structure.

À retenir : le choix de la méthode dépend principalement de la taille du tableau, de sa durée de vie et de la personne chargée de libérer la mémoire.
Pourquoi une fonction ne retourne-t-elle pas directement un tableau ?

Le langage C ne permet pas d’utiliser un tableau comme type de retour direct. La déclaration suivante est donc invalide :

int[] creerTableau(void); /* Syntaxe invalide en C */

Une fonction peut en revanche retourner une valeur simple, un pointeur ou une structure. Ces possibilités servent à produire ou transmettre plusieurs valeurs.

Il ne faut jamais retourner l’adresse d’un tableau local automatique. Ce tableau cesse d’exister dès que la fonction se termine.
Cas 1 — Retourner un pointeur vers un tableau statique

Un tableau déclaré avec le mot-clé static conserve son contenu après la fin de la fonction. Son adresse peut donc être retournée.

#include <stdio.h>

int *creerTableau(void)
{
    static int valeurs[5] = {10, 20, 30, 40, 50};

    return valeurs;
}

int main(void)
{
    int *tableau = creerTableau();

    for (int i = 0; i < 5; i++)
    {
        printf("%d ", tableau[i]);
    }

    return 0;
}
Cette méthode est simple et ne nécessite aucune allocation dynamique.
Le même tableau est réutilisé à chaque appel. Toute modification reste donc visible lors des appels suivants.
Cas 2 — Remplir un tableau passé en paramètre

La fonction appelante crée le tableau, puis transmet son adresse et sa taille à la fonction chargée de le remplir.

#include <stdio.h>

void remplirTableau(int tableau[], int taille)
{
    for (int i = 0; i < taille; i++)
    {
        tableau[i] = (i + 1) * 10;
    }
}

int main(void)
{
    int valeurs[5];

    remplirTableau(valeurs, 5);

    for (int i = 0; i < 5; i++)
    {
        printf("%d ", valeurs[i]);
    }

    return 0;
}

Les deux écritures suivantes sont équivalentes :

void remplirTableau(int tableau[], int taille);

void remplirTableau(int *tableau, int taille);
Cette solution est généralement la plus simple lorsque la taille est connue par la fonction appelante.
Cas 3 — Retourner un tableau créé avec malloc

L’allocation dynamique permet de créer un tableau dont la taille est déterminée pendant l’exécution du programme.

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

int *creerTableau(int taille)
{
    if (taille <= 0)
    {
        return NULL;
    }

    int *tableau = malloc((size_t)taille * sizeof(int));

    if (tableau == NULL)
    {
        return NULL;
    }

    for (int i = 0; i < taille; i++)
    {
        tableau[i] = i * i;
    }

    return tableau;
}

int main(void)
{
    int taille = 5;
    int *valeurs = creerTableau(taille);

    if (valeurs == NULL)
    {
        fprintf(stderr, "Impossible de créer le tableau.\n");
        return 1;
    }

    for (int i = 0; i < taille; i++)
    {
        printf("%d ", valeurs[i]);
    }

    free(valeurs);
    valeurs = NULL;

    return 0;
}
Toute zone mémoire obtenue avec malloc, calloc ou realloc doit être libérée avec free.
Cas 4 — Retourner le tableau et transmettre sa taille

Un pointeur seul ne permet pas de connaître le nombre d’éléments du tableau. La taille peut être transmise grâce à un paramètre de sortie.

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

int *genererValeurs(int *taille)
{
    if (taille == NULL)
    {
        return NULL;
    }

    *taille = 6;

    int *tableau = malloc((size_t)(*taille) * sizeof(int));

    if (tableau == NULL)
    {
        *taille = 0;
        return NULL;
    }

    for (int i = 0; i < *taille; i++)
    {
        tableau[i] = (i + 1) * 5;
    }

    return tableau;
}

int main(void)
{
    int taille = 0;
    int *valeurs = genererValeurs(&taille);

    if (valeurs == NULL)
    {
        fprintf(stderr, "Erreur de création du tableau.\n");
        return 1;
    }

    for (int i = 0; i < taille; i++)
    {
        printf("%d ", valeurs[i]);
    }

    free(valeurs);

    return 0;
}
Cas 5 — Retourner une structure contenant un tableau fixe

Une structure peut être retournée directement par valeur. Elle peut contenir un tableau fixe et le nombre d’éléments utilisés.

#include <stdio.h>

#define TAILLE 5

typedef struct
{
    int valeurs[TAILLE];
    int nombreElements;
} TableauEntiers;

TableauEntiers creerTableau(void)
{
    TableauEntiers resultat;
    resultat.nombreElements = TAILLE;

    for (int i = 0; i < TAILLE; i++)
    {
        resultat.valeurs[i] = (i + 1) * 10;
    }

    return resultat;
}

int main(void)
{
    TableauEntiers tableau = creerTableau();

    for (int i = 0; i < tableau.nombreElements; i++)
    {
        printf("%d ", tableau.valeurs[i]);
    }

    return 0;
}
Cette technique rassemble les valeurs et leur taille dans un même objet.
Cas particulier — Retourner un tableau à deux dimensions

Pour retourner une matrice statique, le type du pointeur doit préciser le nombre de colonnes.

#include <stdio.h>

#define LIGNES 2
#define COLONNES 3

int (*creerMatrice(void))[COLONNES]
{
    static int matrice[LIGNES][COLONNES] =
    {
        {1, 2, 3},
        {4, 5, 6}
    };

    return matrice;
}

int main(void)
{
    int (*matrice)[COLONNES] = creerMatrice();

    for (int i = 0; i < LIGNES; i++)
    {
        for (int j = 0; j < COLONNES; j++)
        {
            printf("%d ", matrice[i][j]);
        }

        printf("\n");
    }

    return 0;
}
La déclaration int (*fonction(void))[COLONNES] désigne une fonction qui retourne un pointeur vers un tableau de COLONNES entiers.
Cas particulier — Retourner un tableau de chaînes de caractères

Une fonction peut retourner un tableau statique de pointeurs vers des chaînes constantes.

#include <stdio.h>

const char **obtenirJours(void)
{
    static const char *jours[] =
    {
        "Lundi",
        "Mardi",
        "Mercredi",
        "Jeudi",
        "Vendredi"
    };

    return jours;
}

int main(void)
{
    const char **jours = obtenirJours();

    for (int i = 0; i < 5; i++)
    {
        printf("%s\n", jours[i]);
    }

    return 0;
}
Le mot-clé const indique que les chaînes retournées ne doivent pas être modifiées.
Erreur fréquente — Retourner l’adresse d’un tableau local

Le code suivant est incorrect :

int *creerTableau(void)
{
    int tableau[3] = {1, 2, 3};

    return tableau;
}
Le tableau est détruit à la fin de la fonction. Le pointeur retourné devient invalide et son utilisation produit un comportement indéfini.

Il faut utiliser un tableau statique, une allocation dynamique, une structure ou un tableau fourni par la fonction appelante.

Version robuste — Structure avec pointeur et taille

Pour un projet plus structuré, une structure peut contenir le pointeur vers les données et la taille du tableau.

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

typedef struct
{
    int *valeurs;
    size_t taille;
} Tableau;

Tableau creerTableau(size_t taille)
{
    Tableau resultat = {NULL, 0};

    if (taille == 0)
    {
        return resultat;
    }

    resultat.valeurs = malloc(taille * sizeof(int));

    if (resultat.valeurs == NULL)
    {
        return resultat;
    }

    resultat.taille = taille;

    for (size_t i = 0; i < taille; i++)
    {
        resultat.valeurs[i] = (int)(i + 1);
    }

    return resultat;
}

void libererTableau(Tableau *tableau)
{
    if (tableau == NULL)
    {
        return;
    }

    free(tableau->valeurs);
    tableau->valeurs = NULL;
    tableau->taille = 0;
}

int main(void)
{
    Tableau tableau = creerTableau(5);

    if (tableau.valeurs == NULL)
    {
        fprintf(stderr, "Échec de l'allocation.\n");
        return 1;
    }

    for (size_t i = 0; i < tableau.taille; i++)
    {
        printf("%d ", tableau.valeurs[i]);
    }

    libererTableau(&tableau);

    return 0;
}
Cette solution facilite la gestion des erreurs, de la taille et de la libération de la mémoire.
Tableau comparatif — Quelle méthode choisir ?
SituationMéthode conseilléePoint de vigilance
Tableau de taille connueTableau passé en paramètreTransmettre aussi sa capacité
Petit tableau fixeStructure contenant le tableauTaille définie à la compilation
Taille déterminée pendant l’exécutionAllocation avec mallocTester le pointeur et appeler free
Données partagées entre plusieurs appelsTableau statiqueLe même contenu est réutilisé
Matrice à deux dimensionsPointeur vers un tableauPréciser le nombre de colonnes
Tableau avec taille et informations associéesStructure avec pointeur et tailleDéfinir qui libère la mémoire
error: Content is protected !!