[RESOLU] Problème avec un tableau de structure

Bonjour,

Pour un projet perso, j’ai besoin de lire un fichier type CSV depuis une carte SD.
Pour la lecture SD pas de problème, mais ou le problème arrive, c’est lorsque je veux enregistrer les données récupérées dans un tableau de structure.

Dans ces données, il y a 7 éléments par ligne :

  • Les 3 premiers sont des entiers (int)
  • Le 4e est une chaine de caractère “char *”.
  • Les 3 derniers sont des “uint16_t”

J’ai créé ci-dessous un script allégé qui reproduit mon erreur.

Son fonctionnement dans cet exemple :

  • Dans le setup, je vais lire le contenu de la carte.
  • à l’aide de “fileSD.read()”, je lit chacune des lignes du fichier dans un “while”. Chacun des caractères est lu, et stockés dans une chaine jusqu’à ce que je rencontre le caractère de fin de ligne (\n).
  • Je dirige la lecture vers la console pour vérification, ma ligne est OK.
  • Avec la fonction strtok(), je décompose mes données, qui sont toutes séparées par un “;”.
  • Au fur et à mesure du découpage, je stock ma donnée dans le tableau de structure.
  • A la fin du traitement, je vérifie chacune des données stockées, tout est OK.
  • Une fois toute les lectures terminées, le programme revient dans le “setup”.
  • Je fait une boucle sur le nombre de ligne lue, et pour chacune de ces lignes, j’affiche le contenu stocké dans le tableau (ce qui devrait être la même chose que la précédente lecture).

Et la c’est le drame :wink:

Les INT et les UINT16_T sont correct, mais la chaine de caractère est du n’importe quoi.

Ce que j’obtient dans la console (le 2 dernières lignes ne sont pas bonne) :

Lecture de La Carte... 
Fichier message.txt ouvert.
7;8;0;Dèbut de démo;2;50;2000
0 -> 7 - 8 - 0 - Dèbut de démo - 2 - 50 - 2000
2;2;1;Scroll_Up;30;30;1000
1 -> 2 - 2 - 1 - Scroll_Up - 30 - 30 - 1000
0 ->> 7 - 8 - 0 - ⸮?⸮⸮?lb 0⸮⸮? - 2 - 50 - 2000
1 ->> 2 - 2 - 1 -  - 30 - 30 - 1000

Ce que j’attend et que je devrais avoir (sauf que je dois me planter quelque part) :

Lecture de La Carte... 
Fichier message.txt ouvert.
7;8;0;Dèbut de démo;2;50;2000
0 -> 7 - 8 - 0 - Dèbut de démo - 2 - 50 - 2000
2;2;1;Scroll_Up;30;30;1000
1 -> 2 - 2 - 1 - Scroll_Up - 30 - 30 - 1000
0 ->> 7 - 8 - 0 - Dèbut de démo - 2 - 50 - 2000
1 ->> 2 - 2 - 1 - Scroll_Up - 30 - 30 - 1000

Voici le contenu de la carte SD :

7;8;0;Dèbut de démo;2;50;2000
2;2;1;Scroll_Up;30;30;1000

Le code Arduino :

#include <SD.h>

// Parametrage SD card
#define SD_CS_PIN 4
#define MESSAGE_FILE "test.csv"

struct sTxtAffiche {
  int           efIn;     
  int           efOut;   
  int           posTxt; 
  char*   txt;  
  uint16_t      vIn;  
  uint16_t      vOut; 
  uint16_t      pause;  
} ;
sTxtAffiche TxtAffiche[] = {};
int nbrLigne = 0;

void setup() {
  Serial.begin(115200);
  // Init SD card
  if (!SD.begin(SD_CS_PIN)) {
    Serial.println(F("Erreur Initialisation de la carte !"));
    Serial.println(F("Verifiez la carte SD et appuyez sur le bouton RESET"));
    while (1) {}
  } else {
    Serial.println("initialization done.");
  }
  
  // EcrireSD();
  LectureSD();

  for(int i = 0; i <= nbrLigne; i++) {
    Serial.print(i); Serial.print(" ->> "); Serial.print(TxtAffiche[i].efIn); Serial.print(" - "); Serial.print(TxtAffiche[i].efOut); Serial.print(" - ");
    Serial.print(TxtAffiche[i].posTxt); Serial.print(" - "); Serial.print(TxtAffiche[i].txt); Serial.print(" - ");
    Serial.print(TxtAffiche[i].vIn); Serial.print(" - "); Serial.print(TxtAffiche[i].vOut); Serial.print(" - "); Serial.println(TxtAffiche[i].pause);
  }

}

void loop() {
  // put your main code here, to run repeatedly:

}


void LectureSD() {
  char fileChar;
  String ligneText;
  
  Serial.println("Lecture de La Carte... ");
    // On ouvre le fichier à lire.
  File fileSD = SD.open(MESSAGE_FILE, FILE_READ);
  if(fileSD){
    int ligneEnCours = 0;
    
    // Fichier ouvert, on exécute le traitement.
    Serial.print("Fichier "); Serial.print(MESSAGE_FILE); Serial.println(" ouvert.");

    while(fileSD.available() > 0) {
      // On lit le caractère courant
      fileChar = fileSD.read(); 
      
      // On vérifie si on est en fin de ligne
      if (fileChar == '\n') { 
        // Nous sommes en Fin de ligne, on traite la ligne courante.
        char str_array[ligneText.length()];                     // On crée un tableau de la longueur de la ligne "ligneText"
        ligneText.toCharArray(str_array, ligneText.length());   // On converti la String "ligneText" en Char, que l'on stock dans notre tableau.
        Serial.println(str_array); 
        TxtAffiche[ligneEnCours].efIn = atoi(strtok(str_array, ";"));
        TxtAffiche[ligneEnCours].efOut = atoi(strtok(0, ";"));
        TxtAffiche[ligneEnCours].posTxt = atoi(strtok(0, ";"));

        // ***** Je pense que le problème vient de cette ligne ci dessous *****
        TxtAffiche[ligneEnCours].txt = strtok(0, ";");

        TxtAffiche[ligneEnCours].vIn = atoi(strtok(0, ";"));
        TxtAffiche[ligneEnCours].vOut = atoi(strtok(0, ";"));
        TxtAffiche[ligneEnCours].pause = atoi(strtok(0, ";"));

        Serial.print(ligneEnCours); Serial.print(" -> "); Serial.print(TxtAffiche[ligneEnCours].efIn); Serial.print(" - "); Serial.print(TxtAffiche[ligneEnCours].efOut); Serial.print(" - ");
        Serial.print(TxtAffiche[ligneEnCours].posTxt); Serial.print(" - "); Serial.print(TxtAffiche[ligneEnCours].txt); Serial.print(" - ");
        Serial.print(TxtAffiche[ligneEnCours].vIn); Serial.print(" - "); Serial.print(TxtAffiche[ligneEnCours].vOut); Serial.print(" - "); Serial.println(TxtAffiche[ligneEnCours].pause);

        ligneEnCours ++ ;   
        ligneText = "" ;
            
      } else {
        // Nous ne sommes pas en fin de ligne.
        ligneText += fileChar;    // On ajoute le caractère Lu à la ligne "ligneText".
      }
    }
    
    fileSD.close();
    nbrLigne = ligneEnCours - 1;
  }
}

void EcrireSD() {
  SD.remove(MESSAGE_FILE);
  
  File myFile = SD.open(MESSAGE_FILE, FILE_WRITE);
  if (myFile) {
    myFile.println("7;8;0;Dèbut de démo;2;50;2000");
    myFile.println("2;2;1;Scroll_Up;30;30;1000");
    myFile.close();
    Serial.println("Ecriture OK.");
  }
}

Je pense que le problème se situe dans l’enregistrement du texte à cette ligne :

TxtAffiche[ligneEnCours].txt = strtok(0, ";");

Car si je met en dur :

TxtAffiche[ligneEnCours].txt = "Toto";

J’ai bien “Toto” de retourné à la fin du programme.
J’ai testé également avec :

String valTemp = (String)strtok(0, ";");
TxtAffiche[ligneEnCours].txt = valTemp.c_str();

Les signes retournés sont différents, mais je n’ai jamais ma chaine de caractères attendue.

Je ne comprend pas pourquoi si je met en dur, cela fonctionne.
Et je ne comprend pas non plus pourquoi, à la première interrogation de mon tableau, la valeur “TxtAffiche[ligneEnCours].txt” est correct, et qu’ensuite elle ne l’est plus à la fin du programme.

Si quelqu’un a un idée du problème, je vous remercie par avance.

Bonjour,

Dans ta structure tu as un char * donc un pointeur sur le texte. Il faut allouer de la mémoire pour le texte.

Soit tu alloues la place max dans ta structure "char txt[20];" mais ça peut être source de mémoire gachée.
Soit tu alloues dynamiquement la mémoire pour le texte à l'exécution avec new.

Vous avez raison sur le commentaire

        // ***** Je pense que le problème vient de cette ligne ci dessous *****
        TxtAffiche[ligneEnCours].txt = strtok(0, ";");

quand vous faites cela, vous mettez dans votre structure un pointeur qui va faire référence à un endroit particulier au sein the str_array. mais comme à chaque ligne str_array change, ensuite tous ces pointeurs pointent n’importe ou.

comme le dit @kamill, une façon simple (mais gourmande) est de réserver assez de place dans la structure pour le texte le plus long

const byte LongeurMaxDuTexte = 20; 
struct sTxtAffiche {
  int           efIn;     
  int           efOut;   
  int           posTxt; 
  char        txt[LongeurMaxDuTexte+1]; // <=== MAXIMUM 20 CARACTERES, +1 pour le null à la fin
  uint16_t      vIn;  
  uint16_t      vOut; 
  uint16_t      pause;  
} ;

et ensuite vous copiez dans cet espace la chaîne temporaire

strncpy(TxtAffiche[ligneEnCours].txt, strtok(0, ";"), LongeurMaxDuTexte); // http://www.cplusplus.com/reference/cstring/strncpy/
TxtAffiche[ligneEnCours].txt[LongeurMaxDuTexte] = '\0'; // par précaution

Voici le solution qui alloue juste la mémoire nécessaire

const char* letexte = ... // ptr sur le texte, obtenu par strtok()
size_t len = strlen ( letexte ) + 1; // taille du texte + le '\0' final
TxtAffiche[ligneEnCours].txt = new char[len]; // allouer la mémoire
strcpy ( TxtAffiche[ligneEnCours].txt, letexte ); // et y copier le texte

Ca suffit si ta structure ne varie jamais plus. Si tu la détruis (par ex. pour en créer une autre), il faut libérer la mémoire. Pour ça, pour ça, il faut faire:

delete[] TxtAffiche[n].txt;

Sinon, la solution la plus simple est d'utiliser un objet String (qui s'occupe tout seul d'allouer/libérer la mémoire). Mais bon, ça va gueuler dans les chaumières... :slight_smile:

biggil:
Sinon, la solution la plus simple est d'utiliser un objet String (qui s'occupe tout seul d'allouer/libérer la mémoire). Mais bon, ça va gueuler dans les chaumières... :slight_smile:

c'est pareil en faisant l'allocation dynamique :slight_smile:

L'allocation dynamique ne pose problème que si on désalloue la mémoire dans n'importe quel ordre.
Si on ne désalloue pas la mémoire ou si on désalloue dans l'ordre inverse ou elle a été allouée, ça ne doit pas poser de problème.

tout à fait

Merci à tous,

Ne sachant pas à l'avance la longueur du texte, qui peut être très court, ou assez long, j'ai utilisé la solution de Biggil.
Ca fonctionne nickel.

Une fois définie, la structure ne change plus, donc c'est parfait.

Merci encore.

Une fois définie, la structure ne change plus, donc c'est parfait.

Oui dans ce cas pas de souci avec l'allocation dynamique et vous avez l'allocation optimale pour votre cas.

Si vous voulez être ceinture/bretelle, il faudrait s'assurer que l'allocation mémoire a fonctionné avant de copier des choses dedans.

Dans le standard actuel du C++, new ne retourne jamais NULL, il raise un std::bad_alloc qu'on n'attrape pas sur un petit arduino car le compilateur par défaut n'a pas le bon flag (-fexceptions).

Dans l'absolu si vous vouliez renvoyer NULL s'il n'y a plus de mémoire, vous devriez l'appeler en le postfixant avec (std::nothrow) du genrechar* textPtr = new (std::nothrow) char[100];mais ça ne va pas fonctionner car nothrow n'est pas défini à cause du flag...

On peut revenir à la bonne vieille méthode du C qui est d'utiliser malloc() qui retournera null s'il n'y plus la place dans le tas et donc vous pouvez tester le pointeur retourné avant de faire le strcpy() au cas ou l'allocation ait foiré

 const char* letexte = strtok(0, ";"); // ptr sur le texte, obtenu par strtok()
size_t len = strlen (letexte) + 1; // taille du texte + le '\0' final
TxtAffiche[ligneEnCours].txt = (char*) malloc(len); // allouer la mémoire
if (TxtAffiche[ligneEnCours].txt != null) strcpy (TxtAffiche[ligneEnCours].txt, letexte );
else Serial.println(F("Attention plus de mémoire"));

pour libérer la mémoire dans ce cas on appelle free() sur le pointeur

un petit code de test:

void setup() {
  Serial.begin(115200);
  char* textPtr = malloc(1000); // essayez avec 2000
  if (textPtr != NULL) Serial.println(F("Mémoire allouée correctement"));
  else  Serial.println(F("Pas assez de Mémoire"));
}

void loop() {}

sur un UNO avec 1000 ça passe, avec 2000 ça vous dira qu'il ne peut pas

Merci à tous, ça fonctionne nickel :wink: