Fonctionnement du compilateur, vérification de l’espace mémoire disponible.

Bonjour,

J’aimerais vous présenter deux bouts de code:
le premier fonctionne normalement mais le deuxième bug.

Le premier code consiste à déclarer un tableau de 1001 caractères puis à le remplir (ici avec es “1”).
Enfin on vérifie que le résultat correspond à celui escompté.

unsigned char tab[1001];
int i;

void setup(){
  Serial.begin(9600);
  pinMode(2,INPUT_PULLUP);
  for (i = 0; i < 1001; i++)
    tab[i] = digitalRead(2); 
  Serial.print("tab[0]: ");
  Serial.println(tab[0]); 
  Serial.print("tab[1000]: ");
  Serial.println(tab[1000]); 
}
void loop() {
}

Le deuxième code est identique, à la différence près qu’on a remplacé le premier tableau par un autre plus grand de 3001 caractères.

unsigned char tab[3001];
int i;

void setup(){
  Serial.begin(9600);
  pinMode(2,INPUT_PULLUP);
  for (i = 0; i < 3001; i++)
    tab[i] = digitalRead(2); 
  Serial.print("tab[0]: ");
  Serial.println(tab[0]); 
  Serial.print("tab[3000]: ");
  Serial.println(tab[3000]); 
}
void loop() {
}

Le programme compile normalement et est transféré vers la carte.
Au lieu d’afficher le résultat attendu lors de l’ouverture de la console, il affiche une page blanche.

Pourquoi?
Dans le second programme on a initialisé un tableau de 3001 caractères. Hors le micro n’a que 2048 octets de SRAM.
Partant de là il était logique de se retrouver face à un problème. :smiley:

Mais la question:
pourquoi le compilateur a-t-il accepté de réserver cet espace mémoire sachant qu’il n’était pas disponible?
Lorsque je développe en assembleur je fais cette vérification manuellement.
Le compilateur ne devrait-t-il pas faire de même?

Ca peut déboucher sur des bugs assez insidieux car dans un programme plus complexe, je n’ai aucun moyen de savoir combien de mémoire va utiliser le compilateur lors de la compilation.

Bonjour,

Tu utilises quel compilateur ? Avec l'ide arduino 1.6.5 j'ai le message d'erreur:

Les variables globales utilisent 3 223 octets (157%) de mémoire dynamique, ce qui laisse -1 175 octets pour les variables locales. Le maximum est de 2 048 octets.

processing.app.debug.RunnerException: Mémore insuffisante ; consulter la page http://www.arduino.cc/en/Guide/Troubleshooting#size pour obtenir des astuces sur comment le réduire.

kamill: Bonjour,

Tu utilises quel compilateur ?

J'utilise la version 1.0.6 de l'ide Arduino. Donc c'est corrigé sur les nouvelles versions d'après ce que je vois. :)

ChristopheFr: Dans le second programme on a initialisé un tableau de 3001 caractères. Hors le micro n’a que 2048 octets de SRAM. Partant de là il était logique de se retrouver face à un problème. :D

C'est aussi le travail du développeur de vérifier qu'il n'utilise pas trop de ressources.

Depuis un bon moment, le message du compilateur concernant l'occupation mémoire est remontée par l'IDE en fin de compilation comme le signale kamill. Mais il faut être prudent quand même car le compilateur ne remonte que la mémoire occupée par les variables globales déclarées explicitement. Il ne voit pas les instances d'objets, la mémoire allouée par des créations de pointeurs enfin tout ce qui concerne la création de données en dynamique.

fdufnews: C'est aussi le travail du développeur de vérifier qu'il n'utilise pas trop de ressources.

Oui certainement. Mais il y une partie cachée impossible à contrôler par le développeur, lorsqu’on importe une librairie par exemple.

On pourrait par exemple compter l’espace total alloué par les variables locales et afficher un warnig lorsque la sommes des variables globales plus locales dépassent les 2Ko. «dépassement potentiel de l’espace mémoire disponible».

J’ai pris l’habitude d’utiliser des variables locales, il faudrait peut-être revoir la stratégie de codage pour Arduino. Tout déclarer en global.

Il est impossible d’évaluer à la compilation la taille des variables locales car elles sont allouées dynamiquement dans la pile.

Si une fonction en appelle une autre la taille des variables locales de chacune s’additionne, mais si elles sont appelées l’une après l’autre la taille des variables locales sera celle des variables de la fonction en cours.

ChristopheFr: J’ai pris l’habitude d’utiliser des variables locales, il faudrait peut-être revoir la stratégie de codage pour Arduino. Tout déclarer en global.

Je suis pas d'accord, la portée des variables c'est primordial ...

ChristopheFr: On pourrait par exemple compter l’espace total alloué par les variables locales et afficher un warnig lorsque la sommes des variables globales plus locales dépassent les 2Ko. «dépassement potentiel de l’espace mémoire disponible».

C'est pas aussi simple que ça, exemples :

void loop() {

fonction1();
fonction2();

}

void fonction1() {

byte tab[1900];

}

void fonction2() {

byte tab[1900];

}

Ne posera aucun problème. Par contre :

void loop() {

fonction1();

}

void fonction1() {

byte tab[1900];
fonction2();

}

void fonction2() {

byte tab[200];

}

plantera. Et encore ce sont des exemples très simplistes, rajoute les objets, leurs instances et les méthodes qui vont avec et ça devient très fun :)

B@tto: Je suis pas d'accord, la portée des variables c'est primordial ...

Je suis 100% d'accord avec ça. Les variables ne doivent être visibles que si c'est nécessaire. Ça améliore la lisibilité du programme et surtout ça évite les régressions qui ne manqueront pas de se produire quand on ajoutera une fonction qui utilisera les mêmes variables globales.

Et ça permet de faire ce que j'ai fait avant dans l'exemple : utiliser des grosses variables allouée dynamiquement et impossible à faire si c'était en global

B@tto: Et ça permet de faire ce que j'ai fait avant dans l'exemple : utiliser des grosses variables allouée dynamiquement et impossible à faire si c'était en global

Oui mais ça présente un risque potentiel, c’est pour ça que j’ai parlé de warning et non de message d’erreur et que j’ai utilisé l’expression «dépassement potentiel de l'espace mémoire disponible» Dans ton premier exemple le compilateur afficherait le warning parce que 1900+1900=3800. Ensuite c’est à toi de vérifier le programme.

Est ce que quelqu'un sait comment est gérée la pile? Est ce qu'elle peut s'étendre sur toute la mémoire ou est ce qu'il y a une partie de la mémoire qui lui est allouée?

Voir là : http://www.nongnu.org/avr-libc/user-manual/malloc.html

Merci. Document très intéressant.

Perso j'utilise cette fonction pour vérifier la mémoire dispo :

int freeRam () 
{
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}