Allocation dynamique de mémoire. Listes chaînées

Bonjour,
Je continue mes études concernant le langage C et en particulier la partie allocation dynamique de mémoire.
J’ai été faire un tour sur openclassrooms.com afin d’essayer de comprendre les listes chaînées ou liste liées. J’ai parfaitement compris sur le fond comment elles fonctionnaient (du moins c'est ce que je pense), il s’agit tout simplement de structures qui sont reliées entre elles par des pointeurs. Pourquoi par des pointeurs ? Parce que contrairement aux tableaux qui sont mémorisés dans un bloc de mémoire contigüe, les structures peuvent être stockées à des endroits différents dans la mémoire (ce qui présente un confort non négligeable notamment en matière de flexibilité), les pointeurs permettent de situer les structures, de les liées afin de mieux les retrouver. Le dernier élément de la liste pointera vers un pointeur NULL.
J’ai repris en partie le code d’exemple ici sans la suppression d’un élément qui ne pose pas de problème de compréhension :
https://openclassrooms.com/fr/courses/19980-apprenez-a-programmer-en-c/19733-stockez-les-donnees-avec-les-listes-chainees
Sur la forme j’ai compris ce code mais j’ai du l’adapter au niveau de l’allocation dynamique de la mémoire pour les structures :
Dans la fonction initialisation()
au lieu de :

Liste *liste = malloc(sizeof(*liste));
Element *element = malloc(sizeof(*element));

Lire :

Liste *liste; // pointeur vers la structure Liste
Element *element; // pointeur vers la structure Element
  
  liste = (Liste*)malloc(sizeof(*liste));
  element = (Element*)malloc(sizeof(*element));

Dans la fonction insertion () au lieu de :

Element *nouveau = malloc(sizeof(*nouveau));

Lire :

Element *nouveau; // pointeur vers un nouvel "Element"
nouveau = (Element*)malloc(sizeof(*nouveau)); // allocation dynamique de la mémoire

Les structures étant des variables comme les autres, il faut donc caster le résultat du malloc() comme le C++ l’impose (le programme étant écrit en C).

J’ai ajouté la gestion d’un tableau de char, j’ai créé trois structures.
Avant d’aller plus loin avec les listes doublements chaînées, je souhaitais vous soumettre la manière dont j’avais compris la gestion de ces variables. Peut-être que quelque chose m’échappe ?

Voici ma version du code testé sur UNO :

typedef struct Element Element; // Element devient équivalent à struct Element
typedef struct Liste Liste; // Liste devient équivalent à struct Liste

struct Element
{
  char nom[10] ;
  int nombre;
  Element *suivant; // pointeur qui va permettre la liaison entre les éléments de la liste
};

struct Liste //contient l'adresse du premier élément de la liste dans premier
{
  Element *premier;
};

void setup() {
  Serial.begin(115200);
  Liste *maListe = initialisation();
  insertion(maListe, "Bonsoir", 25);
  insertion(maListe, "coucou", 6);
  afficherListe(maListe);

}

void loop() {}

Liste *initialisation() //crée la struture de contrôle et le premier élément de la liste
{
  Liste *liste; // pointeur vers la structure Liste
  Element *element; // pointeur vers la structure Element
  
  liste = (Liste*)malloc(sizeof(*liste));
  element = (Element*)malloc(sizeof(*element));

  if (liste == NULL || element == NULL)
  {
    exit(EXIT_FAILURE);
  }

  strcpy (element->nom , "bonjour");
  element->nombre = 10;
  element->suivant = NULL;
  liste->premier = element;
  return liste;
}

void insertion(Liste *liste, const char *nvNom, int nvNombre)
{
  /* Création du nouvel élément */
  Element *nouveau; // pointeur vers un nouvel "Element"
  nouveau = (Element*)malloc(sizeof(*nouveau)); // allocation dynamique de la mémoire
  if (liste == NULL || nouveau == NULL)
  {
    exit(EXIT_FAILURE);
  }
  nouveau->nombre = nvNombre; // affectation pour int
  strcpy (nouveau->nom , nvNom); // affectation pour le tableau de caractères
  /* Insertion de l'élément au début de la liste */
  nouveau->suivant = liste->premier; /* Insertion en début de liste, du coup le pointeur suivant prend l'adresse de premier
  Le nouvel élément pointe vers son futur successeur qui est l'actuel premier élément de la liste */
  liste->premier = nouveau;  // premier pointe logiquement vers nouveau
}

void afficherListe(Liste *liste)
{
  if (liste == NULL)
  {
    exit(EXIT_FAILURE);
  }

  Element *actuel = liste->premier;

  while (actuel != NULL) // tant qu'on arrive pas au bout de la liste
  {
    Serial.println(actuel->nom);
    Serial.println(actuel->nombre);
    actuel = actuel->suivant;
  }
}

Bonne journée.
Edité une fois

Bonjour,

Plutôt que d'utiliser les fonctions C malloc() et free() et les cast, sizeof, je te conseillerai plutôt d'utiliser les opérateurs C++ new et delete
Ca simplifie l'écriture et la lecture du programme. Par exemple:

  liste = new Liste;
  element = new Element;

Merci @kamill
Je vais y regarder.

Effectivement @kamill, ça fonctionne également et c'est plus simple :

Liste *initialisation() //crée la struture de contrôle et le premier élément de la liste
{
  Liste *liste; // pointeur vers la structure Liste
  Element *element; // pointeur vers la structure Element
  
  liste = new Liste;
  element = new Element;

  if (liste == NULL || element == NULL)
  {
    exit(EXIT_FAILURE);
  }

  strcpy (element->nom , "bonjour");
  element->nombre = 10;
  element->suivant = NULL;
  liste->premier = element;
  return liste;
}

Et en mémoire c'est pareil je suppose ?

Bon question bête, je rentre de courir et j'ai les idées plus claires.
En fait, je dois créer une nouvelle instance de ma structure avec le mot clé new et la supprimer avec delete et le compilateur se charge du reste !

Je conseille à tous les débutants de passer par le C avant d'aller vers le C++ car créer une instance d'un objet c'est bien mais comprendre comment cet objet est architecturé en mémoire c'est mieux. A moins que je raconte encore des bêtises ...
L'avenir me le fera comprendre ou plutôt mes recherches...

Oui, c'est bien d'avoir une idée de ce qui se passe en mémoire, surtout si on programme un microcontrôleur, mais si on instancie une class avec son constructeur il faudra utiliser new pour que le constructeur soit appelé.

Merci @kamill

Bonne journée

Ah, l'apprentissage du C, une merveilleuse étape dans la vie d'un programmeur ! :slight_smile:

Mais ... pourquoi faire cela sur un Arduino ?
C'est très bien l'Arduino, mais pour faire du C, un simple ordi (PC, Mac), que tu as déjà, suffit !
Sur Arduino, tu tombes assez vite sur des problèmes de mémoire (y'en a peu) ou autres limitations spécifiques.
Sur ton ordi, il te suffit d'installer une chaîne de compilation C (gcc est gratuit sur toutes les plateformes).

Et en particulier la fragmentation, comme démontré ici :

Sur ESP8266 ou ESP32 les problèmes sont moins fréquents, bien que ...

en C++ on créera bien sûr une classe pour la liste chaînée et l'utilisation des template rendra cette classe générique (on s'adaptera au type sous jacent de données) :slight_smile:

par exemple lire Les listes chaînées en C++ | Développement Informatique

Une fois j'ai tenté d'utiliser la Standard Template Library ( stl::list<> ) sur une carte Uno ... ça m'a bouffé la mémoire à très grande vitesse.

Bonjour @biggil,
Je ne sais pas si dans le cursus scolaire on passe directement au C++, quoique ce langage hérite du C donc peut-être faut-il en passer obligatoirement par cette étape ... En tout cas l'apprentissage de ce langage me donne l'impression d'avoir monté une marche (une petite en ce qui me concerne, je n'ai pas la prétention d'être un expert !). Je pense qu'il se trouve à mi-chemin entre les langages de bas et de haut niveau. Du coup je suis sur une petite marche mais je me demande si franchir la marche suivante c'est aller vers le C++ que je classe peut-être à tort dans les langages évolués ou plutôt vers l'assembleur par exemple ! Je pense que je vais essayer d'aller dans ces deux directions en commençant par la plus facile, le C++.
Comprendre le C m'a permis de mieux appréhender le fonctionnement des µcontrôleurs et des ordinateurs. Je n'avais pas une perception aussi précise de leur "structure" même si il me reste beaucoup à apprendre encore !
Un grand merci à @J-M-L qui m'a permis d'évoluer plus rapidement. Merci pour sa patience.

J'utilise CodeBlocks qui utilise gcc.

Bonjour @hbachetti, oui je suis passé par là et @J-M-L m'avait déjà bien orienté dans ce sens.

Bonjour @J-M-L ,
Merci pour le lien.

PS : Rassurez-vous, je n'ai pas la grosse tête. Je n'ai pas la prétention de maîtriser parfaitement le C. J'en ai simplement acquis les bases.

le C++ a bien évolué et est différent en certains aspects du C pour la même syntaxe, donc il y a quelques pièges mais pour bien comprendre ce qu'il se passe à bas niveau, maîtriser le C est pas mal.

Cependant le compilateur qu'on utilise pour Arduino est un compilateur C++, qui donc aura des règles plus strictes sur les types de données par exemple et ce que l'on peut/doit faire lors des changements de type par exemple (cast). Donc c'est bien d'étudier aussi le C++

Allez vers l'assembleur est cool aussi, c'est un autre monde et parfois nécessaire quand on veut maîtriser code (et les timings) au plus proche du matériel possible. Pour la majorité des cas cependant les chaînes de compilation moderne sont souvent plus efficaces que le programmeur et c'est plus simple de maintenir du C++ que de l'assembleur !

Merci @J-M-L
Je vais commencer à étudier le C++.
Bonne soirée.

Bonjour à tous,
Outre l'allocation dynamique de mémoire qui présente des inconvénients certains et qui est l'objet de ce fil de discussion. Je pense certainement à tort que le langage C est plus adapté sur Arduino. Malgré tout comme le fait si justement remarqué @J-M-L, l'IDE arduino compile en C++, il faut donc étudier ce langage, mais Arduino avait-il vraiment besoin du C++ ?
Aprés tout ce n'est que l'avis d'une personne qui ne connait pas encore toutes les subtilités du C++.
Cependant je me dis que les c-strings portent bien leur nom, elles viennent du C et sont reprises par le C++. En revanche, la classe String n'existe pas en C.
Voilà il y a très certainement des justifications que je découvrirai au cours de mon apprentissage, peut-être une anticipation de l'évolution des capacités des µcontrôleurs notamment en termes de mémoire, une simplification de la manière de coder...

Bonne journée à tous.

Oui, absolument, si on veut bénéficier pleinement du framework et des bibliothèques arduino.
Le majorité des bibliothèques sont sous forme de class, à commencer par la class Serial.

Maintenant si on est allergique au C++ on peut très bien faire son programme sans (trop) utiliser de particularités de C++, mis à part bien sur l'utilisation des librairies.

Avoir la possibilité de créer des boites noires pour des objets un peu complexes (port série, servo, capteurs, écrans…) et pouvoir obtenir une représentation (instance) de cet objet en une ligne de code permet quand même de bien simplifier l’accès à cet environnement pour les débutants.

Certes on pourrait faire des bibliothèques de fonctions mais on aurait potentiellement des conflits de noms de variables ou fonctions et ce serait beaucoup plus verbeux

Merci @kamill, ça a le mérite d'être clair. Je ne l'avais pas compris. Je ne suis pas allergique au C++ bien au contraire, j'essaye juste de comprendre.
Merci.

Oui c'est clair.
Merci @J-M-L

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.