IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Arduino Discussion :

printf limite taille de string


Sujet :

Arduino

  1. #1
    Membre régulier
    Homme Profil pro
    retraité informaticien
    Inscrit en
    Novembre 2008
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : retraité informaticien

    Informations forums :
    Inscription : Novembre 2008
    Messages : 90
    Points : 75
    Points
    75
    Par défaut printf limite taille de string
    Bonjour à tous
    Je ne vois pas où est l'erreur : Serial.printf est incapable de formatter un String d'une longueur supérieure à 10 alors que Serial.print y parvient.

    S'agit-il d'un bug ou d'une erreur de ma part
    Je préfèrerais une erreur de ma part mais laquelle ?
    Merci et bonne journée


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    String toto;
    void setup() {
    	Serial.begin(74880);
    	delay(500);
    	Serial.println();
    	toto = "";
    	for (int i = 0; i< 12; i++) {
    		toto = toto + "a";
    		Serial.println((String) "valeur de toto (longueur = " + toto.length() + ", " + toto);
    		Serial.printf("valeur de toto (longueur = %d), %s\n", toto.length(), toto);
    	}
    }
    void loop() {}
    Le code ci-dessus produit ce résultat :
    valeur de toto (longueur = 1, a
    valeur de toto (longueur = 1), a
    valeur de toto (longueur = 2, aa
    valeur de toto (longueur = 2), aa
    valeur de toto (longueur = 3, aaa
    valeur de toto (longueur = 3), aaa
    valeur de toto (longueur = 4, aaaa
    valeur de toto (longueur = 4), aaaa
    valeur de toto (longueur = 5, aaaaa
    valeur de toto (longueur = 5), aaaaa
    valeur de toto (longueur = 6, aaaaaa
    valeur de toto (longueur = 6), aaaaaa
    valeur de toto (longueur = 7, aaaaaaa
    valeur de toto (longueur = 7), aaaaaaa
    valeur de toto (longueur = 8, aaaaaaaa
    valeur de toto (longueur = 8), aaaaaaaa
    valeur de toto (longueur = 9, aaaaaaaaa
    valeur de toto (longueur = 9), aaaaaaaaa
    valeur de toto (longueur = 10, aaaaaaaaaa
    valeur de toto (longueur = 10), aaaaaaaaaa
    valeur de toto (longueur = 11, aaaaaaaaaaa
    valeur de toto (longueur = 11), ⸮⸮⸮?
    valeur de toto (longueur = 12, aaaaaaaaaaaa
    valeur de toto (longueur = 12), ⸮⸮⸮?

  2. #2
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 775
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 775
    Points : 5 577
    Points
    5 577
    Par défaut
    toto est une String, printf avec %s attend une c-string.... (un tableau de caractères terminé par un caractère nul)

    il faut faire donc

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
     Serial.printf("valeur de toto (longueur = %d), %s\n", toto.length(), toto.c_str());
    votre println() fait de la concaténation de String (ça consomme plein de RAM)

  3. #3
    Membre régulier
    Homme Profil pro
    retraité informaticien
    Inscrit en
    Novembre 2008
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : retraité informaticien

    Informations forums :
    Inscription : Novembre 2008
    Messages : 90
    Points : 75
    Points
    75
    Par défaut arduino printf avec String
    @Jay M
    MERCI!
    J'ai testé dès que j'ai pu et c'était bien le problème.

    Avant cela j'ai cherché partout et testé un bout de programme pour mettre en valeur le problème.
    Ce qui me gêne c'est que je n'ai vu nulle part que printf ne traitait correctement que les c_string.
    Ce qui est surprenant c'est la limitation à 10 caractères ce qui ne m'a pas rendu la tâche facile..
    Je n'ai fait cette concaténation de String que pour mettre en évidence le problème qui semblait lié à la taille du String.
    Dans le programme d'origine je n'ai pratiqué pas cela.

    Une petite question pour finir : la mémoire souffre-t'elle autant lors de concaténations si on travaille avec une taille spécifiée, ex :String toto[14]; et dernière possibilité l'utilisation de la clause reserve()?

    Non je rigolais, ce n'est pas fini; j'aimerais connaitre la structure en mémoire d'un String, pour un C_String je suppose que c'est un tableau de carcatères suivi d'un byte à 0x00?

  4. #4
    Membre expérimenté Avatar de edgarjacobs
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2011
    Messages
    633
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 64
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2011
    Messages : 633
    Points : 1 596
    Points
    1 596
    Par défaut
    Citation Envoyé par jjnoui Voir le message
    pour un C_String je suppose que c'est un tableau de carcatères suivi d'un byte à 0x00?
    Oui, ce qu'on appelle en C une chaîne de caractères (ou c-string) n'est rien d'autre qu'une suite d'octets contigus terminée par le caractère '\0' .
    On écrit "J'ai tort" ; "tord" est la conjugaison du verbre "tordre" à la 3ème personne de l'indicatif présent

  5. #5
    Membre régulier
    Homme Profil pro
    retraité informaticien
    Inscrit en
    Novembre 2008
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : retraité informaticien

    Informations forums :
    Inscription : Novembre 2008
    Messages : 90
    Points : 75
    Points
    75
    Par défaut j'aimerais connaitre la structure en mémoire d'un String
    Citation Envoyé par edgarjacobs Voir le message
    Oui, ce qu'on appelle en C une chaîne de caractères (ou c-string) n'est rien d'autre qu'une suite d'octets contigus terminée par le caractère '\0' .
    Bonjour Edgar Jacobs
    Désolé de répéter ce qui est dans le titre :
    j'aimerais connaitre la structure en mémoire d'un String

  6. #6
    Membre expérimenté Avatar de edgarjacobs
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2011
    Messages
    633
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 64
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2011
    Messages : 633
    Points : 1 596
    Points
    1 596
    Par défaut
    Je réponds suivant mes compétences: je programme en C (que je connais relativement bien), mais ne connais pas le C++ ( et ce bonhomme répond ici ?!? ). Désolé pour la réponse partielle.

    Maintenant, sans faire une recherche approfondie, peut-être là: std::String sur cplusplus.com
    On écrit "J'ai tort" ; "tord" est la conjugaison du verbre "tordre" à la 3ème personne de l'indicatif présent

  7. #7
    Responsable Arduino et Systèmes Embarqués


    Avatar de f-leb
    Homme Profil pro
    Enseignant
    Inscrit en
    Janvier 2009
    Messages
    12 647
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Janvier 2009
    Messages : 12 647
    Points : 56 973
    Points
    56 973
    Billets dans le blog
    40
    Par défaut
    Bonsoir,

    Citation Envoyé par edgarjacobs Voir le message
    [...] peut-être là: std::String sur cplusplus.com
    Pour l'environnement Arduino, ce n'est pas la classe Std::String de la librairie standard qui est utilisée. Arduino a implémenté sa propre classe String pour faciliter les manipulations des chaînes de caractères de ses microcontrôleurs.

    Le souci avec cette classe String façon Arduino, c'est que les emplacements pour stocker les chaînes sont alloués dynamiquement sur le tas. Si par exemple tu rallonges la chaîne, le système va essayer de trouver un emplacement plus grand, et créer un 'trou' dans la mémoire, ce qui fait que la mémoire a la fâcheuse tendance à se fragmenter à force de manipulations.
    C'est pour ça qu'on recommande de ne pas en abuser alors que la ram est limitée sur les microcontrôleurs.

  8. #8
    Membre expérimenté Avatar de edgarjacobs
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2011
    Messages
    633
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 64
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2011
    Messages : 633
    Points : 1 596
    Points
    1 596
    Par défaut
    @f-leb: merci pour cette précision, je n'arrête pas d'apprendre ce monde-là grace au forum, mais ce n'est pas évident de s'y faire.
    On écrit "J'ai tort" ; "tord" est la conjugaison du verbre "tordre" à la 3ème personne de l'indicatif présent

  9. #9
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 775
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 775
    Points : 5 577
    Points
    5 577
    Par défaut
    la fonction printf() avec l'option %s — comme documenté depuis des décennies — attend un pointeur vers une c-string, c'est à dire un tableau de caractères terminé par le caractère nul.
    En passant le pointeur sur toto, vous passiez un pointeur sur quelque chose qui n'était pas une c-string donc ça ne pouvait pas fonctionner et ça n'aurait pas fonctionné non plus avec un std:string C++.

    Pour donner plus d'explications :

    Arduino a créé effectivement sa propre classe String (avec un S majuscule) qui est donc différente de std::string que l'on trouve en C++

    le code source : https://github.com/arduino/ArduinoCo...no/WString.cpp

    une String mode Arduino est un objet qui a 3 variables d'instances

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    protected:
    	char *buffer;	        // the actual char array
    	unsigned int capacity;  // the array length minus one (for the '\0')
    	unsigned int len;       // the String length (not counting the '\0')
    protected:
    donc quand vous instanciez un objet String, vous allouez ces 3 variables et le constructeur (en fonction de ce que vous passez pour initialiser le contenu) va allouer de la mémoire sur le tas pour le buffer (les deux autres variables servent comme leur nom l'indique à stocker la taille allouée (de façon à faire grandir la chaîne si besoin) et la longueur effective utilisée dans le buffer).

    Dans votre malchance vous avez eu l'impression que ça a fonctionné plus ou moins un peu car l'allocation initiale des données a dû tomber à peu près au même endroit que l'instance toto et comme il n'y avait pas d'autres variables, vous réussissez en parcourant la mémoire à retrouver les caractères (il se peut qu'il y ait eu des codes non ascii imprimés mais invisible).

    Cependant, arrivé à un moment quand la chaîne a grandi le bloc mémoire buffer a été réalloué complètement ailleurs et là ça imprimait donc n'importe quoi (la mémoire dans le tas n'ayant pas été effacée vous y retrouviez ce qui avait avant mais ce n'était pas le pointeur vers le buffer.) En passant toto.c_str() comme paramètre on donne bien le buffer sous jacent où qu'il soit en mémoire et ça fonctionne.

    ➜ si on utilise les fonctions des bibliothèques standard comme printf, scanf, etc, il ne faut pas utiliser les String ou std:string mais vraiment se conformer à la spécification et utiliser un tableau de caractères terminé par un caractère nul.

  10. #10
    Membre régulier
    Homme Profil pro
    retraité informaticien
    Inscrit en
    Novembre 2008
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : retraité informaticien

    Informations forums :
    Inscription : Novembre 2008
    Messages : 90
    Points : 75
    Points
    75
    Par défaut printf limite taille de string
    Bonsoir

    J'étais loin d'imaginer la façon dont était construit un String.

    Je supposais une allocation mémoire mais avec seulement un indicateur de longueur et un pointeur.

    En ce qui concerne le gaspillage de la mémoire c'est bien et que je pensais.

    Cela me rappelle le bon vieux Basic interprété, sauf qu'alors il y avait une notion de 'garbage collection'
    (un peu l'équivalent du defrag pour le système de fichier).

    Pour l'instant :
    1 j'ai pu résoudre mes problèmes.
    2 j'ai appris quelque chose.

    Que du positif!

    J'aimerais, par curiosité, savoir ce qu'apportent

    - l'utilisation de reserve() ainsi que
    - l'utilisation d'un String avec indication de taille ( String machaine[12], par exemple)

    Merci à tous les participants.

  11. #11
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 775
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 775
    Points : 5 577
    Points
    5 577
    Par défaut
    Citation Envoyé par jjnoui Voir le message

    J'aimerais, par curiosité, savoir ce qu'apportent

    - l'utilisation de reserve() ainsi que
    - l'utilisation d'un String avec indication de taille ( String machaine[12], par exemple)
    Reserve permet de pré dimensionner le buffer alloué à la String. Ça évite de multiples petites extensions du buffer sous jacent qui pourraient morceler la mémoire.

    Par exemple si vous souhaitez construire une Sting caractère par caractère en lisant le port série ou un flux en faisant
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    String message;
    …
     
    // dans le code 
    while (Serial.available()) message += (char) Serial.read();
    …
    Chaque += va faire grandir et déplacer en mémoire toute la chaîne déjà reçue.

    Un reserve() initial retardera ce moment (et s’il est bien calculé on n’aura pas a ré allouer et déplacer la chaîne)
    ——

    Pour ce qui concerne ce n’est pas lié aux Strings particulièrement, vous déclarez juste un tableau de 12 Strings qui s’appelle machaine (et donc qui est mal nommé, puisqu’il ne s’agit pas que d’une seule chaîne).

  12. #12
    Membre régulier
    Homme Profil pro
    retraité informaticien
    Inscrit en
    Novembre 2008
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : retraité informaticien

    Informations forums :
    Inscription : Novembre 2008
    Messages : 90
    Points : 75
    Points
    75
    Par défaut String machaine[12];
    @ Jay M

    Bon, je vois que j'avais bien compris le comportement de reserve(), mais je cite :
    Pour ce qui concerne

    String machaine[12];

    ce n’est pas lié aux Strings particulièrement, vous déclarez juste un tableau de 12 Strings qui s’appelle machaine (et donc qui est mal nommé, puisqu’il ne s’agit pas que d’une seule chaîne)

    Oh P...., je croyais limiter le phénomène de fragmentation en définissant une taille fixe de machaine alors jusque là j'ai du bouffer de la mémoire à outrance!!!!

    Que le dieu des neuneus me pardonne.

    Il faut dire, pour ma défense, que je sévis surtout sur les PC en Delphi

    Je découvre avec HORREUR l'étendue des dégâts.

    Je comprends bien le mécanisme d'allocation mémoire en cas d'accroissement de la taille d'un String.

    je suppose que le même phénomène se produit au sein d'une procédure, mais que la mémoire utilisée est le stack.
    En quittant la fonction on libère donc la mémoire liée à ce String et l'effet de fragmentation disparait.

    En conclusion : je vais devoir réviser tous les programmes et transformer mes String en char dimensionnés et,
    par là même, revoir toutes les instructions d'affectation et de comparaison.

    Bonne journée à tous.
    MERCI

  13. #13
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 775
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 775
    Points : 5 577
    Points
    5 577
    Par défaut
    Citation Envoyé par jjnoui Voir le message
    Je comprends bien le mécanisme d'allocation mémoire en cas d'accroissement de la taille d'un String.

    je suppose que le même phénomène se produit au sein d'une procédure, mais que la mémoire utilisée est le stack.
    En quittant la fonction on libère donc la mémoire liée à ce String et l'effet de fragmentation disparait.
    C'est peut être un peu plus compliqué que vous ne le pensez.

    Quand vous déclarez une String,
    String toto;
    ---

    Comme dit précédemment seulement trois variables sont allouées.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    char *buffer;	        // the actual char array
    unsigned int capacity;  // the array length minus one (for the '\0')
    unsigned int len;       // the String length (not counting the '\0')
    Ces 3 variables sont soit dans une zone réservée de la RAM pour les variables globales si vous avez défini votre String en variable globale, soit dans la pile si votre String est une variable locale.

    Lorsque vous affectez un contenu à votre variable
    String toto = "Bonjour";le constructeur de la classe String est appelé avec un pointeur vers une zone mémoire contenant une cString (un tableau de caractères terminé par null qui contient les codes ascii de Bonjour).
    Le constructeur regarde la longueur de cette cString (ici 7 caractères plus le caractère null donc 8 octets) et compare cette taille demandée à la taille disponible dans la String (connue par la variable d'instance capacity). Au début il n'y a pas de mémoire allouée, donc capacity vaut 0. Comme on veut 8 octets le constructeur va appeler la fonction realloc() pour changer la zone pointée par le buffer et obtenir 8 octets puis le texte va être copié dans cette zone et les variables capacity et len sont mises à jour. Cette allocation dynamique se fait dans le tas ==> Notre instance utilise donc toujours la mémoire pour ses 3 variables qui n'ont pas changé de place mais utilise aussi ailleurs (dans le tas) les 8 octets.

    Si ensuite vous faites
    toto += " tout le monde";vous appelez l'opérateur surchargé += de la classe String. Cet opérateur sait qu'il faut faire une concaténation du contenu actuel de toto avec ce qui est à droite du signe de l'opérateur +=
    le code de la fonction regarde donc la taille supplémentaire nécessaire pour rajouter le texte " tout le monde" (14 octets) et regarde si entre la longueur actuelle (len) et la capacité actuelle (capacity) il y a la place pour rajouter ce texte. Ici ce n'est pas le cas et donc la fonction doit rappeler realloc() pour obtenir un buffer plus grand, déplacer le contenu existant de toto dans ce nouveau buffer puis rajouter le nouveau texte à la fin et mettre à jour les variables et libérer l'espace occupé par l'ancien buffer. Notez que pendant ce processus on a en mémoire l'ancien pointeur buffer car on a besoin de son contenu, et un nouvel espace plus grand pour la concaténation et aussi le texte à rajouter...

    C'est là que les soucis avec la classe String peuvent commencer.
    Imaginez que l'on soit dans la boucle déjà décrite
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
     while (Serial.available()) message += (char) Serial.read();
    et que message contienne déjà 99 caractères. Vous voulez en rajouter un seul ==> votre arduino doit trouver un bloc de 100 octets de libres pour recopier les 99 octets existants dedans puis rajouter le caractère suivant. Il se peut très bien que votre arduino ait la place pour les 100 octets en tout mais temporairement il n'a pas la place pour 99 + 100 octets et l'opération d'ajout ne va pas se faire (la classe String refuse simplement de faire l'opération mais ne vous le dit pas).

    C'est là qu'avoir utilisé reserve() avant de remplir message est pertinent. Si vous aviez fait un reserve pour 100 octets, l'opérateur += n'a pas à trouver une nouvelle zone plus grande pour copier tout le texte et donc si vous ne dépassez jamais ces 100 octets alors aucune nouvelle allocation dynamique et déplacement ne se produit.

    Enfin, pour être complet, si la variable toto est locale à une fonction, lorsque l'on quitte la fonction le destructeur de la String est appelé. Ce destructeur se charge de faire le ménage et d'appeler free() pour libérer la mémoire dans le tas qui avait été allouée au buffer. Les 3 variables qui étaient sur la pile disparaissent d'elles même avec le fonctionnement standard d'empilement/dépilement des variables de la pile par simple modification du pointeur de pile par le compilateur lorsqu'il quitte la fonction.

    ==> la classe String est sympa pour simplifier les opérations sur le texte mais elle cache énormément de pièges en terme de performance et risque sur la mémoire. Si vous développez un code et que vous êtes juste en mémoire, faites attention, l'usage des String peut vous jouer des tours.

    C'est pour cela que souvent lors du développement pour petits micro-contrôleurs on préfère revenir au cStrings d'origine et fonctions C associées car on gère un buffer de taille fixe sans risque (et sans la facilité) de la dynamicité. Il est essentiel dans ce cas de tester les limites et s'assurer que l'on n'écrit pas au delà de la zone mémoire réservée à notre cString. On préfèrera utiliser les fonctions avec le 'n' comme strncat() snprintf(), ou mieux en 'l' comme strlcat() strlcpy() etc au lieu des vieilles fonctions strcat() strcpy() ou sprintf() qui ne tiennent pas compte de la taille du buffer dispo.

  14. #14
    Membre régulier
    Homme Profil pro
    retraité informaticien
    Inscrit en
    Novembre 2008
    Messages
    90
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : retraité informaticien

    Informations forums :
    Inscription : Novembre 2008
    Messages : 90
    Points : 75
    Points
    75
    Par défaut réponse lumineuse for bien documentée avec citation des références
    @Jay M
    Tout est dans le titre, il ne me reste plus qu'à changer mes habitudes.
    Pour la correction de l'existant ça sera un long voyage pas tranquille.
    MERCI pour vos réponses patientes et brillantes.

  15. #15
    Responsable Arduino et Systèmes Embarqués


    Avatar de f-leb
    Homme Profil pro
    Enseignant
    Inscrit en
    Janvier 2009
    Messages
    12 647
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Janvier 2009
    Messages : 12 647
    Points : 56 973
    Points
    56 973
    Billets dans le blog
    40
    Par défaut
    En effet, réponse brillante de @Jay M

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 2
    Dernier message: 19/11/2022, 09h26
  2. Taille limite d'une String ?
    Par mike76 dans le forum Général JavaScript
    Réponses: 3
    Dernier message: 16/12/2015, 09h56
  3. Réponses: 8
    Dernier message: 15/07/2010, 17h41
  4. Réponses: 2
    Dernier message: 21/10/2004, 12h29
  5. Limiter taille fichier joint à un mail
    Par fdthierry dans le forum Applications et environnements graphiques
    Réponses: 2
    Dernier message: 27/08/2004, 12h12

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo