je me pose une question peut-être un peu stupide pour le experts en programmation... ce qui n'est pas mon cas, même si je me débrouille
donc, voila : vu que, contrairement à un PC qui va être appelé à faire une multitude de choses différentes, et où la mémoire sera optimisée par l'OS de bien des manières, un arduino est programmé pour une tache précise, donc pourquoi ne pas utiliser que des variables globales ?
je comprends bien que pour la propreté du code, c'est pas idéal, mais en revanche en procédant ainsi cela permettrait de savoir avec certitude à la compilation si notre code dépasse les capacités du micro, non ? puisque à l'issue de la compilation l'IDE donne la capacité mémoire utilisée par les variables globales. En se gardant une marge pour la pile, ça permettrait d'être plus serein, il me semble.
Ca fait déja deux fois que je perds des heures à chercher un bug alors que c'est "juste" un dépassement de la ram...
L'avantage des variables locales à une fonction c'est qu'elles ocupent de l'espace mémoire seulemeny pendant le temps d'exécution de la fonction, sitôt sorti de la fonction la place mémoire est libérée.
Idem pour les compteurs dans une boucle for par exemple qui n'existent plus sorti de la boucle.
Comme d'hab je ne pense pas qu'il y est une règle universelle.
Autre point je me rappelle avoir utilisé des variables globales en multi fichiers.
Le multi fichier permet d'améliorer la clarté et laisse la possibilité de se faire une bibliothèque personnelle.
Le temps d'accès aux variables définies dans un autre fichier (le fichjer ino) est plus long que celui pour accéder aux variables définie dans le fichier de travail (fichier.cpp).
Bonjour,
Je dois avoir moins d’expérience que vous, mais juste pour vous dire que personnellement, je fais un mix:
Les variable globale que j'utilise, sont centrales. C'est à dire qu'elles servent dans la majorité des fonctions.
Les autres sont essentiellement pour créer des boucles ou autre chose que j'utilise localement.
Parfois, j'utilise des variables global 'fourre tout'. Celles ci sont largement utilisé afin de les rentabiliser.
Rien à voir, mais j'utilise beaucoup le débug (Serial.print) avec des string que je place en progmem afin de préserver la RAM.
Si ça peu vous aider, à l'époque j'avais trouvé une fonction qui calculai la mémoire entre les piles. Astucieusement utilisé, elle peu mettre en évidence les points gourmand dans votre code.
// Mémoire dispo entre la stack & la heap
int freeRam ()
{
extern int __heap_start, *__brkval;
int v;
Serial.print(F("mémoire dispo entre la stack & la heap= "));
Serial.println((int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval));
}
c'est aussi ce que je fais, et j'utilise aussi cette fonction (pas exactement la même, j'affiche sur mon LCD, mais le calcul est le même) depuis mes prises de tête inutiles, justement...
68tjs:
L'avantage des variables locales à une fonction c'est qu'elles ocupent de l'espace mémoire seulemeny pendant le temps d'exécution de la fonction, sitôt sorti de la fonction la place mémoire est libérée.
Idem pour les compteurs dans une boucle for par exemple qui n'existent plus sorti de la boucle.
(...)
et oui, mais justement ! quel est l'intéret de libérer cette mémoire dans une application qui fait toujours la même chose ? parceque le résultat c'et qu'il est quasi impossible de savoir facilement avant l'execution du programme quel est son utilisation totale de mémoire... et quand on fais un prog un peu long, ben bug...
Pour ma régul de chauffage j'ai du passer d'une uno à une mega à cause de ça, avec les emmerdes qui vont avec comme la carte SD capricieuse, une plus grande sensibilité aux perturbations électriques etc etc...
ben, ça arrive quand la mémoire est pleine, ou proche d'être pleine. Dès le départ oui, mais, c'est sournois comme bug, le programme se met à avoir un comportement illogique (par exemple la valeur retourné par une fonction n'est pas celle qui est calculée dans la fonction, une variable change de valeur toute seule, etc etc) du au fait que plusieurs variables finissent par être écrites au même endroit en mémoire faute de place.
Je ne suis pas assez caller pour te répondre "avec de la théorie".
Néanmoins je me dis que chaque fois qu'une variable à une durée de vie limité c'est bon à prendre.
Exemple si on déclare globale une variable int i pour les boucles for elle occupera en permanence de la place mémoire.
Alors que si on la déclare uniquement dans la boucle elle n'occupera de la place que pendant le temps de vie de la boucle.
La conséquence sera que cela donnera de la place pour d'autres fonctions qui n'utilisent pas de boucle for mais qui auront aussi besoin de variables temporaires. pepe a donné il y a quelques temps un moyen pour connaître la place disponible en cours de programme.
Tu devrais essayé de tester en différents endroits de ton programme pour voir où cela coince.
J'espère que je vais utiliser les bons mots :
Il y a aussi la possibilité avec les globales de ne pas les transmettre dans les fonctions par valeur mais par adresse.
A mon sens cela doit dispenser de créer des variables locales donc gain de place.
L'avantage de libérer la mémoire (spécialement celle sur la pile, l'allocation dynamique dans le tas est un peu plus compliquée et amène à des soucis comme la classe String) c'est que vous pouvez avoir un code qui tient en mémoire sur votre arduino alors que si tout était statique ça déborderait
imaginez que vous ayez un ruban de 1000 LEDs et que vous avez besoin de buffer intermédiaire pour faire des calculs d'animation en écrivant comme cela
void fonction1()
{
byte animation1[500];
for (int i = 0; i < 500; i++) {
animation1[i] = i & 0xFF;
Serial.println(animation1[i]);
}
}
void fonction2()
{
byte animation2[1000];
for (int i = 0; i < 1000; i++) {
animation2[i] = i & 0xFF;
Serial.println(animation2[i]);
}
}
void fonction3()
{
byte animation3[200];
for (int i = 0; i < 200; i++) {
animation3[i] = i & 0xFF;
Serial.println(animation3[i]);
}
}
void fonction4()
{
byte animation4[800];
for (int i = 0; i < 800; i++) {
animation4[i] = i & 0xFF;
Serial.println(animation4[i]);
}
}
void setup() {
Serial.begin(115200);
}
void loop() {
fonction1();
fonction2();
fonction3();
fonction4();
}
vous n'avez à un instant t que 200 à 1000 octets alloués sur la pile pour votre animation en cours et donc ça tiendrait sur un UNO par exemple. Mais Si vous déclariez les 4 tableaux en global (un peu idiot j'en conviens, un seul avec la plus grande taille suffirait, mais c'est pour l'exemple) alors aucune chance de tourner sur un UNO car il faudrait 2500 octets de RAM rien que pour ces tableaux
Il y a d'autres usages aussi pour des variables locales -> vous avez la notion de fonction ré-entrante. Une fonction qui s'appelle elle même. imaginez que vous voulez parcourir l'arborescence des fichiers sur une carte SD. Vous écrivez une fonction afficheRépertoire() et quand cette fonction trouve un ficher qui est un répertoire, elle affiche son nom puis appelle afficheRépertoire() sur cet élément. Si vous aviez des variables globales pour ce parcours récursif alors vous ne pourriez pas garder un historique sur la pile de l'exploration du tableau par exemple
il y a ensuite des optimisations. Une variable locale étant intrinsèquement à durée de vie limitée, le compilateur peut décider d'utiliser un register du micro-processeur pour cette variable alors que si elle est globale, il va devoir générer du code pour charger depuis la mémoire le registre, puis en fin de fonction ré-écrire le registre dans la mémoire, même si vous n'en avez pas besoin
il y aussi des effets de bord. une variable globale va conserver sa valeur d'appels en appels et le compilateur ne pourra pas vous prévenir si vous avez oublié de l'initialiser dans la fonction
il y aussi les problèmes d'espace de noms. en cachant une variable dans votre fonction, c'est pas grave si il y a une variable du même nom ailleurs dans une autre fonction. elles ne vont pas se télescoper.
etc...
oui, en effet je n'avais pas pensé au cas des fonctions réentrantes et aux optimisations possible par le compilateur. les autres points, c'est que je sous-entendais par la formule "à part pour la propreté du code"
whaou ça c'est de la réponse. Certains points me dépassent : comment éviter la fragmentation du tas si c'est le compilateur qui décide de ce qui s'y trouve en fonction des différents appels de fonctions ?
et du coup ça m'amène à une autre question plus ou moins en rapport : est -il préférable d'utiliser des définitions comme "#define ma_pin 13" ou plutot "const byte ma_pin=13;" ??
j'ai cru comprendre que la seconde manière permet des optimisations par le compilateur ?
Oui pour le sujet mais non pour la forme.
Vu la réaction qu'il y a eu dernièrement à propos du message épinglé sur comment modifier ses post une rédaction plus adaptée me semble indispensable.
la rédaction reste incomplète et pas structurée comme un tuto
A mon sens la recommendation devrait plutôt être du genre:
C++ propose différentes options pour gérer la mémoire. Elles ont toutes leurs avantages et leurs inconvénients. C'est la boîte à outil du programmeur et il n'y a aucune raison de dire "je vais toujours utiliser le marteau"... Un bon programmeur se doit de connaître tous ces outils, comprendre l'impact de leur usage sur la mémoire, où vont être stockés les octets et comment et par qui ils seront gérés (à la main quand on libère des objets alloués dynamiquement jusqu'à automatique pour l'usage de la pile).
il y a déjà de nombreux articles sur internet expliquant l'organisation de la mémoire vive en pile et tas, je ne suis pas sûr de la valeur ajoutée de réinventer la roue
Il pourrait être pertinent cependant de mettre les choses en perspectives sur l'usage de l'allocation dynamique dans le cadre d'un petit micro-contrôleur qui a très peu de mémoire - mais là encore il n'y a pas d'approche globale. Même la classe String que je n'aime pas, si bien utilisée (ie toutes les allocations et libérations dynamique en LIFO) ne posera pas de soucis.