Les variables globales sont-elles mauvaises dans Arduino?

24

Je suis relativement nouveau en programmation et bon nombre des meilleures pratiques de codage que je lis indiquent effectivement qu'il y a très peu de bonnes raisons d'utiliser une variable globale (ou que le meilleur code n'a pas de globaux du tout).

J'ai fait de mon mieux pour garder cela à l'esprit, lors de l'écriture d'un logiciel pour créer une interface Arduino avec une carte SD, parler à un ordinateur et exécuter un contrôleur de moteur.

J'ai actuellement 46 globales pour environ 1100 lignes de code "niveau débutant" (aucune ligne ayant plus d'une action). Est-ce un bon ratio ou devrais-je envisager de le réduire davantage? Quelles pratiques puis-je utiliser pour réduire davantage le nombre de personnes dans le monde?

Je pose cette question ici parce que je suis spécifiquement préoccupé par les meilleures pratiques de codage sur les produits Arduino plutôt que par la programmation informatique en général.

ATE-ENGE
la source
2
Dans Arduino, vous ne pouvez pas éviter les variables globales. Chaque déclaration de variable en dehors de la portée d'une fonction / méthode est globale. Donc, si vous devez partager des valeurs entre des fonctions, elles doivent être globales, sauf si vous voulez passer chaque valeur en argument.
16
@LookAlterno Err, ne pouvez-vous pas écrire des classes dans Ardunio, car c'est juste du C ++ avec des macros et des bibliothèques étranges? Si c'est le cas, toutes les variables ne sont pas globales. Et même en C, il est généralement considéré comme la meilleure pratique de préférer passer des variables (peut-être à l'intérieur de structures) dans des fonctions plutôt que d'avoir des variables globales. Cela peut être moins pratique pour un petit programme, mais cela est généralement payant car le programme devient plus grand et plus complexe.
Muzer
11
@LookAlterno: "J'évite" et "tu ne peux pas" sont des choses très différentes.
Courses de légèreté avec Monica
2
En fait, certains programmeurs intégrés interdisent les variables locales et nécessitent à la place des variables globales (ou des variables statiques à portée de fonction). Lorsque les programmes sont petits, cela peut faciliter leur analyse comme une simple machine à états; parce que les variables ne se remplacent jamais (c'est-à-dire: comme elles empilent et tasent les variables allouées).
Rob
1
Les variables statiques et globales offrent l'avantage de connaître la consommation de mémoire au moment de la compilation. Avec un arduino disposant d'une mémoire très limitée, cela peut être un avantage. Il est assez facile pour un novice d'épuiser la mémoire disponible et de rencontrer des échecs introuvables.
antipattern

Réponses:

33

Ils ne sont pas mauvais en soi, mais ils ont tendance à être surutilisés là où il n'y a aucune bonne raison de les utiliser.

Il y a de nombreuses fois où les variables globales sont avantageuses. D'autant plus que la programmation d'un Arduino est, sous le capot, très différente de la programmation d'un PC.

Le plus grand avantage des variables globales est l'allocation statique. Surtout avec des variables grandes et complexes telles que les instances de classe. L'allocation dynamique (l'utilisation de newetc.) est mal vue en raison du manque de ressources.

De plus, vous n'obtenez pas une seule arborescence d'appels comme vous le faites dans un programme C normal (une seule main()fonction appelant d'autres fonctions) - au lieu de cela, vous obtenez effectivement deux arborescences distinctes ( setup()appelant des fonctions, puis loop()appelant des fonctions), ce qui signifie que parfois les variables globales sont les seule façon d'atteindre votre objectif ( par exemple, si vous voulez l' utiliser à la fois setup()et loop()).

Donc non, ils ne sont pas mauvais, et sur un Arduino, ils ont plus et de meilleures utilisations que sur un PC.

Majenko
la source
Ok, et si c'est quelque chose que j'utilise uniquement loop()(ou dans plusieurs fonctions appelées loop())? serait-il préférable de les mettre en place différemment que de les définir au départ?
ATE-ENGE
1
Dans ce cas, je les définirais probablement dans loop () (peut-être comme staticsi j'avais besoin d'eux pour conserver leur valeur à travers les itérations) et les passerais par les paramètres de fonction le long de la chaîne d'appel.
Majenko
2
C'est une façon, ou passez-la comme référence: void foo(int &var) { var = 4; }et foo(n);- a nmaintenant 4 ans.
Majenko
1
Les classes peuvent être allouées en pile. Certes, il n'est pas bon de mettre de grandes instances sur la pile, mais quand même.
JAB
1
@JAB Tout peut être alloué par pile.
Majenko
18

Il est très difficile de donner une réponse définitive sans voir votre code réel.

Les variables globales ne sont pas mauvaises, et elles ont souvent un sens dans un environnement intégré où vous faites généralement beaucoup d'accès matériel. Vous n'avez que quatre UARTS, un seul port I2C, etc. Il est donc logique d'utiliser des variables globales pour des variables liées à des ressources matérielles spécifiques. Et en effet, la bibliothèque de base Arduino fait que: Serial, Serial1, etc. sont des variables globales. En outre, une variable représentant l'état global du programme est généralement un global.

J'ai actuellement 46 globales pour environ 1100 lignes de [code]. Est-ce un bon ratio [...]

Ne concerne pas les chiffres. La bonne question que vous devriez vous poser est, pour chacun de ces globaux, s'il est logique de l'avoir à l'échelle mondiale.

Pourtant, 46 global me semble un peu élevé. Si certains d'entre eux contiennent des valeurs constantes, qualifiez-les comme const: le compilateur optimise généralement leur stockage. Si l'une de ces variables n'est utilisée que dans une seule fonction, rendez-la locale. Si vous souhaitez que sa valeur persiste entre les appels à la fonction, qualifiez-la comme static. Vous pouvez également réduire le nombre de globales «visibles» en regroupant les variables dans une classe et en ayant une instance globale de cette classe. Mais ne le faites que lorsque cela a du sens de rassembler des choses. Faire une grande GlobalStuffclasse pour avoir une seule variable globale n'aidera pas à rendre votre code plus clair.

Edgar Bonet
la source
1
D'accord! Je ne savais pas staticsi j'avais une variable qui n'est utilisée que dans une fonction, mais j'obtiens une nouvelle valeur chaque fois qu'une fonction est appelée (comme var=millis()) dois-je faire cela static?
ATE-ENGE
3
Non, staticc'est uniquement lorsque vous devez conserver la valeur. Si la première chose que vous faites dans la fonction est de définir la valeur de la variable à l'heure actuelle, il n'est pas nécessaire de conserver l'ancienne valeur. Tout ce que statique fait dans cette situation, c'est perdre de la mémoire.
Andrew
C'est une réponse très complète, exprimant à la fois des points en faveur et des scepticismes à l'égard des mondiaux. +1
underscore_d
6

Le principal problème avec les variables globales est la maintenance du code. Lors de la lecture d'une ligne de code, il est facile de trouver la déclaration des variables passées en paramètre ou déclarées localement. Il n'est pas si facile de trouver une déclaration de variables globales (souvent cela nécessite un IDE).

Lorsque vous avez plusieurs variables globales (40 c'est déjà beaucoup), il devient difficile d'avoir un nom explicite qui n'est pas trop long. L'utilisation de l'espace de noms est un moyen de clarifier le rôle des variables globales.

Une mauvaise façon d'imiter les espaces de noms en C est:

static struct {
    int motor1, motor2;
    bool sensor;
} arm;

Sur un processeur Intel ou Arm, l'accès aux variables globales est plus lent que les autres variables. C'est probablement le contraire sur Arduino.

BOC
la source
2
Sur AVR, les globaux sont sur RAM, tandis que la plupart des locaux non statiques sont alloués aux registres CPU, ce qui rend leur accès plus rapide.
Edgar Bonet
1
Le problème n'est pas vraiment de trouver la déclaration; être capable de comprendre rapidement le code est important mais finalement secondaire par rapport à ce qu'il fait - et là le vrai problème est de trouver quelle varmint dans quelle partie inconnue de votre code est capable d' utiliser la déclaration globale pour faire des choses étranges avec un objet que vous avez laissé de côté à l'air libre pour que chacun fasse ce qu'il veut.
underscore_d
5

Bien que je ne les utilise pas lors de la programmation pour un PC, pour l'Arduino, ils présentent certains avantages. Surtout si cela a déjà été dit:

  • Pas d'utilisation de mémoire dynamique (créant des lacunes dans l'espace de stockage limité d'un Arduino)
  • Disponible partout, donc pas besoin de les passer comme arguments (ce qui coûte de l'espace de pile)

De plus, dans certains cas, en particulier en termes de performances, il peut être bon d'utiliser des variables globales, au lieu de créer des éléments en cas de besoin:

  • Pour réduire les écarts dans l'espace de tas
  • L'allocation dynamique et / ou la libération de mémoire peut prendre un temps considérable
  • Les variables peuvent être «réutilisées», comme une liste d'éléments d'une liste globale qui sont utilisés pour plusieurs raisons, par exemple une fois comme tampon pour le SD, et plus tard comme tampon de chaîne temporaire.
Michel Keijzers
la source
5

Comme pour tout (à part les gotos qui sont vraiment mauvais), les globaux ont leur place.

Par exemple, si vous avez un port série de débogage ou un fichier journal sur lequel vous devez pouvoir écrire de partout, il est souvent judicieux de le rendre global. De même, si vous avez des informations critiques sur l'état du système, la rendre globale est souvent la solution la plus simple. Il est inutile d'avoir une valeur que vous devez transmettre à chaque fonction du programme.

Comme d'autres l'ont dit, 46 semble beaucoup pour seulement un peu plus de 1000 lignes de code, mais sans connaître les détails de ce que vous faites, il est difficile de dire si vous les utilisez trop ou non.

Cependant, pour chaque global, posez-vous quelques questions importantes:

Le nom est-il clair et précis pour que je n'essaye pas accidentellement d'utiliser le même nom ailleurs? Sinon changez le nom.

Cela doit-il jamais changer? Sinon, envisagez d'en faire un const.

Est-ce que cela doit être visible partout ou est-ce seulement global pour que la valeur soit maintenue entre les appels de fonction? Envisagez donc de le rendre local à la fonction et d'utiliser le mot-clé static.

Est-ce que les choses vont vraiment mal se passer si cela est modifié par un morceau de code lorsque je ne fais pas attention? Par exemple, si vous avez deux variables liées, par exemple le nom et le numéro d'identification, qui sont globales (voir la note précédente sur l'utilisation de global lorsque vous avez besoin d'informations un peu partout), alors si l'une d'entre elles est modifiée sans que les autres choses désagréables puissent se produire. Oui, vous pouvez simplement être prudent, mais il est parfois bon d'appliquer un peu la prudence. par exemple, placez-les dans un fichier .c différent, puis définissez les fonctions qui les définissent en même temps et vous permettent de les lire. Vous n'incluez alors que les fonctions dans le fichier d'en-tête associé, de cette façon, le reste de votre code ne peut accéder qu'aux variables via les fonctions définies et ne peut donc pas gâcher les choses.

- mise à jour - Je viens de réaliser que vous aviez posé des questions sur les meilleures pratiques spécifiques à Arduino plutôt que sur le codage général et il s'agit plus d'une réponse de codage général. Mais honnêtement, il n'y a pas beaucoup de différence, une bonne pratique est une bonne pratique. La startup()et la loop()structure des moyens Arduino que vous devez utiliser GLOBALS un peu plus que d' autres plates - formes dans certaines situations , mais cela ne change pas vraiment beaucoup, vous finissez toujours viser le meilleur que vous pouvez faire dans les limites de la plate - forme , peu importe ce que la plate-forme est.

Andrew
la source
Je ne connais rien à Arduinos mais je fais beaucoup de développement de bureau et de serveur. Il y a une utilisation acceptable (IMHO) pour les gotos et c'est de sortir des boucles imbriquées, c'est beaucoup plus propre et plus facile à comprendre que les alternatives.
Persistance
Si vous pensez que gotoc'est mal, consultez le code Linux. On peut dire qu'ils sont tout aussi mauvais que des try...catchblocs lorsqu'ils sont utilisés comme tels.
Dmitry Grigoryev
5

Sont-ils mauvais? Peut être. Le problème avec les globaux est qu'ils peuvent être consultés et modifiés à tout moment par n'importe quelle fonction ou morceau de code en cours d'exécution, sans restrictions. Cela peut conduire à des situations qui sont, disons, difficiles à retracer et à expliquer. Il est donc souhaitable de minimiser la quantité de globaux, si possible de ramener la quantité à zéro.

Peut-on les éviter? Presque toujours oui. Le problème avec Arduino est qu'ils vous forcent dans cette approche à deux fonctions dans laquelle ils vous supposent setup()et vous loop(). Dans ce cas particulier, vous n'avez pas accès à l'étendue de la fonction appelant de ces deux fonctions (probablement main()). Si vous l'aviez fait, vous seriez en mesure de vous débarrasser de tous les globaux et d'utiliser plutôt des locaux.

Imaginez ce qui suit:

int main() {
  setup();

  while (true) {
    loop();
  }
  return 0;
}

C'est probablement plus ou moins à quoi ressemble la fonction principale d'un programme Arduino. Les variables dont vous avez besoin à la fois dans setup()la loop()fonction et dans la fonction seraient alors de préférence déclarées à l'intérieur de la portée de la main()fonction plutôt que de la portée globale. Ils pourraient alors être rendus accessibles aux deux autres fonctions en les passant comme arguments (en utilisant des pointeurs si nécessaire).

Par exemple:

int main() {
  int myVariable = 0;
  setup(&myVariable);

  while (true) {
    loop(&myVariable);
  }
  return 0;
}

Notez que dans ce cas, vous devez également modifier la signature des deux fonctions.

Comme cela pourrait ne pas être faisable ni souhaitable, je ne vois vraiment qu'une seule façon de supprimer la plupart des globaux d'un programme Arduino sans modifier la structure du programme forcé.

Si je me souviens bien, vous êtes parfaitement capable d'utiliser C ++ lors de la programmation pour Arduino, plutôt que C. Si vous n'êtes pas (encore) familier avec OOP (Object Oriented Programming) ou C ++, cela pourrait prendre un certain temps pour s'y habituer et d'autres en train de lire.

Ma proposition serait de créer une classe Program et de créer une seule instance globale de cette classe. Une classe doit être considérée comme le modèle des objets.

Considérez l'exemple de programme suivant:

class Program {
public:      
  Program();

  void setup();
  void loop();

private:
  int myFirstSampleVariable;
  int mySecondSampleVariable;
};

Program::Program() :
  myFirstSampleVariable(0),
  mySecondSampleVariable(0)
{

}

void Program::setup() {
  // your setup code goes here
}

void Program::loop() {
  // your loop code goes here
}

Program program; // your single global

void setup() {
  program.setup();
}

void loop() {
  program.loop();
}

Voilà, nous nous sommes débarrassés de presque tous les mondiaux. Les fonctions dans lesquelles vous commenceriez à ajouter votre logique d'application seraient les fonctions Program::setup()et Program::loop(). Ces fonctions ont accès aux variables membres spécifiques à l'instance myFirstSampleVariableet mySecondSampleVariablealors que les fonctions traditionnelles setup()et loop()n'ont pas accès car ces variables ont été marquées class private. Ce concept est appelé encapsulation ou masquage de données.

Vous enseigner la POO et / ou le C ++ est un peu hors de portée de la réponse à cette question, je vais donc m'arrêter ici.

Pour résumer: les globaux doivent être évités et il est presque toujours possible de réduire considérablement le nombre de globaux. Aussi lorsque vous programmez pour Arduino.

Plus important encore, j'espère que ma réponse vous sera quelque peu utile :)

Arjen
la source
Vous pouvez définir votre propre main () dans l'esquisse si vous préférez. Voici à quoi ressemble le stock: github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/…
per1234
Un singleton est effectivement un global, juste habillé d'une manière très confuse. Il a les mêmes inconvénients.
patstew
@patstew Cela vous dérange de m'expliquer comment vous pensez qu'il a les mêmes inconvénients? À mon avis, ce n'est pas le cas, car vous pouvez utiliser l'encapsulation des données à votre avantage.
Arjen
@ per1234 Merci! Je ne suis certainement pas un expert Arduino, mais je suppose que ma première suggestion pourrait également fonctionner.
Arjen
2
Eh bien, c'est toujours un état global qui peut être consulté n'importe où dans le programme, vous y accédez simplement à la Program::instance().setup()place de globalProgram.setup(). Placer des variables globales liées dans une classe / structure / espace de noms peut être bénéfique, surtout si elles ne sont nécessaires que par quelques fonctions liées, mais le modèle singleton n'ajoute rien. En d'autres termes, static Program p;dispose d'un stockage global et static Program& instance()d'un accès global, ce qui revient au même que simplement Program globalProgram;.
patstew
4

Les variables globales ne sont jamais mauvaises . Une règle générale contre eux est juste une béquille pour vous permettre de survivre assez longtemps pour acquérir de l'expérience et prendre de meilleures décisions.

Qu'est-ce qu'une variable globale, c'est une supposition inhérente qu'il n'y a qu'une seule chose (peu importe si nous parlons d'un tableau ou d'une carte globale qui pourrait contenir plusieurs choses, qui contient toujours l'hypothèse qu'il n'y a que une telle liste ou mappage, et non plusieurs indépendants).

Alors avant d'utiliser un global, vous voulez vous demander: est-il concevable que je veuille jamais utiliser plus d'un de ces trucs? S'il s'avère vrai sur toute la ligne, vous devrez modifier le code pour dé-globaliser cette chose, et vous découvrirez probablement en cours de route que d'autres parties de votre code dépendent de cette hypothèse d'unicité, de sorte que vous 'devra également les corriger, et le processus devient fastidieux et sujet aux erreurs. "N'utilisez pas les globaux" est enseigné parce que généralement c'est un coût assez faible pour éviter les globaux dès le début, et cela évite le potentiel d'avoir à payer un coût élevé plus tard.

Mais les hypothèses simplificatrices permises par les globaux rendent également votre code plus petit, plus rapide et utilisent moins de mémoire, car il n'a pas à faire circuler la notion de ce qu'il utilise, n'a pas à faire d'indirection, n'a pas à envisagez la possibilité que la chose désirée n'existe peut-être pas, etc. Sur l'embarqué, vous êtes plus susceptible d'être contraint pour la taille du code et / ou le temps CPU et / ou la mémoire que sur un PC, donc ces économies peuvent avoir de l'importance. Et de nombreuses applications embarquées ont également plus de rigidité dans les exigences - vous savez que votre puce n'a qu'un périphérique particulier, l'utilisateur ne peut pas simplement en brancher une autre sur un port USB ou quelque chose.

Une autre raison courante de vouloir plus d'une chose qui semble unique est le test - tester l'interaction entre deux composants est plus facile lorsque vous pouvez simplement passer une instance de test d'un composant à une fonction, alors que tenter de modifier le comportement d'un composant global est une proposition plus délicate. Mais les tests dans le monde intégré ont tendance à être très différents des autres, donc cela peut ne pas s'appliquer à vous. Pour autant que je sache, Arduino n'a aucune culture de test.

Alors allez-y et utilisez les globaux quand cela semble utile. La police du code ne viendra pas vous chercher. Sachez simplement que le mauvais choix pourrait entraîner beaucoup plus de travail pour vous sur la route, donc si vous n'êtes pas sûr ...

Hobbs
la source
0

Les variables globales sont-elles mauvaises en Arduino?

rien n'est intrinsèquement mauvais, y compris les variables globales. Je le qualifierais de "mal nécessaire" - cela peut vous faciliter la vie mais il faut l'aborder avec prudence.

Quelles pratiques puis-je utiliser pour réduire davantage le nombre de personnes dans le monde?

utilisez des fonctions wrapper pour accéder à vos variables globales. vous le gérez donc au moins du point de vue de la portée.

dannyf
la source
3
Si vous utilisez des fonctions wrapper pour accéder aux variables globales, vous pouvez tout aussi bien placer vos variables dans ces fonctions.
Dmitry Grigoryev