structure et memcpy vers un tableau de char

Bonjour toute la communauté française.
Très grand débutant, j'ai du mal à comprendre les résultats sur le moniteur série de mon sketch ci dessous.
Y a t'il un passionné qui puisse l'analyser et me dire où se situent mes erreurs.
le but:
assigner un tableau de char à trois éléments de la structure,
copier directement depuis la mémoire chaque octet puis l'assigner à un tableau de char de même longueur que la structure,
écrire sur le moniteur série ce tableau de char.
Le résultat m'étonne, il semble que seul le premier élément de la structure soit copié.(Voir la copie d'écran)

merci de votre aide.

struct st
{
  char t1[10];   
  char t2[10];
  char t3[10];
}ms;


void setup()
{
  Serial.begin(9600);
// assignation par copie du tab de char dans chaque element de ms
char f[]={'f','r','e','d'}; // sans le zero de fin de string ?
memcpy(ms.t1,f,10);
memcpy(ms.t2,"aline",10);
memcpy(ms.t3,"monique",10);

char st[sizeof(ms)];  // le tableau
memcpy(st,&ms,sizeof(ms));//la copie st est supposé contenir la totalité de la structure

Serial.println(ms.t1);//retourne bien fred
Serial.println(ms.t2);//retourne bien aline
Serial.println(ms.t3);//retourne bien monique

Serial.print("ms: ");Serial.println((char*)&ms);// seulement ms: fred

Serial.print("st: ");Serial.println(st);// ici pareil où sont mes erreurs?

Serial.println(sizeof(st));
Serial.println(sizeof(ms));
}

void loop() {
  

}

es_struct3.ino (835 Bytes)

Capture moniteur.jpg

Bonjour,

Le Serial.print() s'arrête à la fin de la première chaine (\0), c'est donc normal qu'il n'affiche pas la suite.

merci Kamil je m'en doutais.
c'est pourquoi j'ai remplacé le caractère '\0' par " "(espace) et voilà ce que j'obtiens

Serial.println(ms.t2);//retourne bien aline
 Serial.println(ms.t3);//retourne bien monique
 for (int i = 0; i < sizeof(st); i++)
 {
   if (st[i] == '\0') st[i] = " ";//remplacement du zero fin de string par un espace
 }

et sur le moniteur

fred
aline
monique
ms: fred
st: fred4[fralinemonimoniquemsfred
30
30

Cela n'a aucun sens de supprimer le ZERO en fin de chaîne.
La méthode print() suppose qu'il y a un zéro, tout comme toute fonction de manipulation de chaîne.
Le ZERO est obligatoire.

memcpy(ms.t1,f,10);
memcpy(ms.t2,"aline",10);
memcpy(ms.t3,"monique",10);

Pourquoi recopier dans une chaîne x une chaîne y avec une longueur de 10, alors que les chaînes source sont plus courtes ?

Une chaîne se recopie avec strcpy() ou strncpy().

Au lieu de tourner autour du pot avec une solution qui ne marchera pas, si tu nous disais ce que tu veux faire ?

bonjour Henri Baghetti

bien sur c'est absurde d'enlever tous les zéros fin de String, aussi je pense en avoir rajouter un à la fin de st par ce code

  Serial.println(ms.t1);   //retourne bien fred
  Serial.println(ms.t2);   //retourne bien aline
  Serial.println(ms.t3);   //retourne bien monique
  for (int i = 0; i < sizeof(st); i++)
  {
    if (st[i] == '\0') st[i] = " ";     //remplacement de tous les zéros fin de string par un espace
  }                                        // il devrait y en avoir au moins trois
  st[sizeof(st) - 1] = '\0';          // celui ci est indispensable

  Serial.print("ms: "); Serial.println((char*)&ms); // seulement ms: fred

  Serial.print("st: "); Serial.println(st); // ici pareil où sont mes erreurs?

et voici le résultat

fred
aline
monique
ms: fred
st: fred4[fralinemonimoniquem
30
30

29 caractères + 0 fin de String donc les 30 attendus.
Les caractères inattendus semblent être du bourrage pour arriver à 10 caractères par éléments de la structure.
suis je dans le vrai?

Si tu ne dis pas ce que tu veux faire comment dire si tu es dans le vrai ou pas ?

  st[sizeof(st) - 1] = '\0';          // celui ci est indispensable

Faux. Tu mets à zéro le dernier caractère de t3. Si t3 contient "monique" tu places un zéro deux caractères après "monique".

vous allez avoir des chaînes de longueur variable dans votre structure

struct st
{
  char t1[10];   
  char t2[10];
  char t3[10];
}ms;

si vous voulez écrire sur le moniteur série les 3 contenus ils suffit de faire

Serial.print(ms.t1);
Serial.write('\t'); // un séparateur, ici une tabulation
Serial.print(ms.t2);
Serial.write('\t'); // séparateur
Serial.print(ms.t3);

si vous voulez bâtir une nouvelle cString (tableau de char terminé par le caractère NULL) avec le contenu des 3 cString, vous avez besoin d'un tableau assez grand. Vous avez 3 chaines qui contiendront au pire 9 caractères et le '\0'. soit 27 caractères, vous avez besoin de 2 séparateurs sans doute pour l'affichage et un '\0' à la fin. il vous faut donc 30 caractères au min dans le buffer.

ensuite ça se fait soit avec sprintf(), mais cette fonction est très riche et prend plein de mémoire programme, soit par strcat()

char destination[30];

// coût mémoire important car fonction 'riche'
sprintf(destination, "%s\t%s\t%s", ms.t1, ms.t2, ms.t3); 

// coût mémoire moindre
destination[0] = '\0'; // on s'assure que la chaîne est bien vide
strcat(destination, ms.t1);
strcat(destination, "\t");
strcat(destination, ms.t2);
strcat(destination, "\t");
strcat(destination, ms.t3);

on peut jouer aussi à écrire la chaîne au bon endroit avec un strcpy() en trouvant l'octet de fin de chaîne par strlen()

vous pouvez voir d'autres fonctions standard du C and les lib habituelles: stdlib.h et aussi string.h

quel est mon but!

d'abord comprendre sinon je n'irais pas loin. Essentiel pour moi.

ensuite cette transformation pourrait me servir pour former un string et l'envoyer sur un canal quelconque.

J'ai consulté de nombreux forum ou tuto où l'on rencontre des protocoles d'envoi plus sophistiqués et il faut énormément de patience pour en comprendre des bribes.
c'est pourquoi j'essaie des choses qui me paraissent simples.

L'idéal dans ce genre de situation est d'utiliser une classe au lieu d'une struct, et d'ajouter une méthode print() ou send().

dom0834:
ensuite cette transformation pourrait me servir pour former un string et l'envoyer sur un canal quelconque.

le plus souvent le canal quelconque se fiche que vous balanciez les données en une fois ou plusieurs fois... il ne voit qu'un flux... donc faire

monCanal.print(ms.t1);
monCanal.write('\t'); // un séparateur, ici une tabulation
monCanal.print(ms.t2);
monCanal.write('\t'); // séparateur
monCanal.print(ms.t3);

est sans doute la méthode à privilégier car elle ne nécessite pas de buffer supplémentaire et vous êtes sur une architecture avec SRAM limitée bien souvent...

(sinon je vous ai mis un exemple post #7 si vous avez raté ce post)

class myStruct
{
  private:
    char t1[10];
    char t2[10];
    char t3[10];
    
  public:
    myStruct(const char *s1, const char *s2, const char *s3);
    void print(void);
};

myStruct::myStruct(const char *s1, const char *s2, const char *s3)
{
  strcpy(t1, s1);
  strcpy(t2, s2);
  strcpy(t3, s3);
}

void myStruct::print(void)
{
  Serial.print(t1);
  Serial.print(" ");
  Serial.print(t2);
  Serial.print(" ");
  Serial.println(t3);
}

myStruct mystruct("fred", "aline", "monique");

void setup()
{
  Serial.begin(115200);
  mystruct.print();
}

void loop()
{
}

Ce code affiche :

fred aline monique

Ensuite si tu veux écrire sur un canal spécifique ou une SD, il faut modifiee la méthode print(), ou lui ajouter un argument de type Stream (Serial, Serial1, etc.).

juste par sécurité dans le constructeur je ferais

myStruct::myStruct(const char *s1, const char *s2, const char *s3)
{
  strncpy(t1, s1, 9); t1[9] = '\0';
  strncpy(t2, s2, 9); t2[9] = '\0';
  strncpy(t3, s3, 9); t3[9] = '\0';
}

(et au lieu de 9 (et 10) j'aurais un #define ou une constante dans la classe)

merci pour votre réactivité, elle va plus vite que mes questions, et un grand merci pour votre indulgence.

Donc je peux résumer:

il est absurde de vouloir transformer une structure en chaine de caractère afin de l'envoyer.

si besoin je devrais utiliser la méthode de JML, je crois l'avoir comprise.

la méthode de Henri B me branche. Il faut que je l'approfondisse (merci à lui pour ce qu'il met sur son blogue que j'étudie sans parfois tout comprendre. Il faut un début à tout)

juste par sécurité dans le constructeur je ferais

strncpy par sécurité. Absolument.

oui une approche objet va vous permettre d'encapsuler vos 3 données (et d'autres) dans un composant qui offrira des possibilités d'interagir avec lui simplement, sans avoir à regarder comment c'est fichu

dans l'esprit de Henri B j'ai modifié son exemple pour envoyer à la suite trois trames qu'il faudra parser à l'arrivée.

class myStruct
{
  private:
    char dest[2];
    char t1[10];
    char t2[10];
    char t3[10];

  public:
    // le constructeur
    myStruct(const char *dest, const char *s1, const char *s2, const char *s3);
    //la seule methode
    void print(void);
};
/*             le constructeur        */
myStruct::myStruct(const char *desti ,const char *s1, const char *s2, const char *s3)
{
  strncpy(dest, desti, 1);  // destinataire du message
  strncpy(t1, s1, 9); t1[9] = '\0';         // les actions à exécuter
  strncpy(t2, s2, 9); t2[9] = '\0';
  strncpy(t3, s3, 9); t3[9] = '\0';
}
/*         methode pour écrire sur liaison série       */
void myStruct::print(void)
{
  Serial.print("$");      // debut de trame
  Serial.print(",");
  Serial.print(dest);
  Serial.print(",");      // separateur pour parser à l'arrivée
  Serial.print(t1);
  Serial.print(",");
  Serial.print(t2);
  Serial.print(",");
  Serial.print(t3);
  Serial.print(",");
  Serial.println("*");    // fin de trame
}

myStruct mystruct1("A","action1", "ac2", "act3"); 
myStruct mystruct2("B","ordre1", "ord2", "o3");

char dest[2]="C";                              // var globale pour init
char action1[10]="on";
char action2[10]="off";
char action3[10]="nul";

myStruct mystruct3(dest,action1,action2,action3);//plus généraliste
void setup()
{
  Serial.begin(9600);
  mystruct1.print();
  mystruct2.print();
  mystruct3.print();
}

void loop()
{
}

j'obtiens bien 3 envois de taille différente qui à réception feront affaire.

$,A,action1,ac2,act3,*
$,B,ordre1,ord2,o3,*
$,C,on,off,nul,*

merci à tous

attention comme dest est juste un char, ne pas utiliser strncpy

  strncpy(_dest, dest, 1);            // PAS BON POUR UN CHAR

il faut faire simplement

_dest = dest;

et un char se déclare entre simple apostrophe, les guillemets c'est pour les chaines

myStruct mystruct1('A', "action1", "ac2", "act3"); 
myStruct mystruct2('B', "ordre1", "ord2", "o3");

et pas besoin des intermédiaires pour votre structure plus généraliste, au lieu de

char dest='C';                              // var globale pour init
char action1[10]="on";
char action2[10]="off";
char action3[10]="nul";

myStruct mystruct3(dest,action1,action2,action3);//plus généraliste

vous pouvez faire directement

 myStruct mystruct3('C', "on", "off", "nul");//plus généraliste

Et aussi :

myStruct mystruct1("A","action1", "ac2", "act3");
myStruct mystruct2("B","ordre1", "ord2", "o3");
// 
myStruct mystruct1('A',"action1", "ac2", "act3"); 
myStruct mystruct2('B',"ordre1", "ord2", "o3");

Comment le compilo AVR peut-il accepter ça ?

Le compilo ESP, lui, signale une erreur ...

c'est vrai Henri B dans ma précipitation je ne me suis pas aperçu tout de suite de mes embrouilles,

bien sûr je n'avais pas vu qu'il manquait la var dest dans le moniteur.

je n'ai pas l’œil aussi incisif que vous. Aussi je me suis permis de corriger mon post. (Ce que je n'auras pas du faire afin que quelqu'un d'autre puisse suivre mes erreurs et vos rectifications).

je remets mon code modifié

class myStruct
{
  private:
    char dest[2];      // ma modif
    char t1[10];
    char t2[10];
    char t3[10];

  public:
    // le constructeur
    myStruct(const char *dest, const char *s1, const char *s2, const char *s3);
    //la seule methode
    void print(void);
};
/*             le constructeur        */
myStruct::myStruct(const char *desti ,const char *s1, const char *s2, const char *s3)
{
  strncpy(dest, desti, 1); dest[2] =  '\0'; // destinataire du message
  strncpy(t1, s1, 9); t1[9] = '\0';             // les actions à exécuter
  strncpy(t2, s2, 9); t2[9] = '\0';
  strncpy(t3, s3, 9); t3[9] = '\0';
}
/*         methode pour écrire sur liaison série       */
void myStruct::print(void)
{
  Serial.print("$");      // debut de trame
  Serial.print(",");
  Serial.print(dest);
  Serial.print(",");      // separateur pour parser à l'arrivée
  Serial.print(t1);
  Serial.print(",");
  Serial.print(t2);
  Serial.print(",");
  Serial.print(t3);
  Serial.print(",");
  Serial.println("*");    // fin de trame
}

myStruct mystruct1("A","action1", "ac2", "act3"); 
myStruct mystruct2("B","ordre1", "ord2", "o3");

char dest[2]="C";                              // var globale pour init
char action1[10]="on";
char action2[10]="off";
char action3[10]="nul";

myStruct mystruct3(dest,action1,action2,action3);//plus généraliste
void setup()
{
  Serial.begin(9600);
  mystruct1.print();
  mystruct2.print();
  mystruct3.print();
}

void loop()
{
}

et cette fois ci le résultat

$,A,action1,ac2,act3,*
$,B,ordre1,ord2,o3,*
$,C,on,off,nul,*

Est ce mieux?

je serai plus vigilant désormais.
on se fait tancer mais çà entre (dans la tête de quelqu'un né pdt la dernière guerre)