Un #define est une opération du préprocesseur qui est exécuté avant la compilation.
Le préprocesseur est une opération purement textuelle qui ne connait rien du C/C++ mais qui ne sait que
- évaluer des expressions (#if)
- insérer des fichiers (#include)
- faire des remplacement de caractères (#define)
Quand tu écris
#define PIN_LED 13
digitalWrite( PIN_LED, HIGH );
Le préprocesseur intervient avant le compilateur de manière à ce le compilateur compile effectivement:
digitalWrite( 13, 1 );
Comme indiqué plus haut, OCR5A est déjà un define équivalent à
#define OCR5A (*(uint16_t*)0x128)
Si tu rajoutes un define sur FrequenceBroche46
#define FrequenceBroche46 OCR5A
Quand tu écriras
FrequenceBroche46 = 5;
Le compilateur recevra comme ligne à compiler:
(*(uint16_t*)0x128) = 5;
Pour ce qui est d'une constante, quelles sont donc les différences entre les 2 lignes ci-dessous ?
#define CONTROLE_MOTEUR_1_ENA 14
const byte CONTROLE_MOTEUR_1_ENA = 14;
Tout d'abord, la différence fondamentale :
- la première agit au niveau du préprocesseur pour un remplacement de chaine de caractère. Elle ne définit rien au niveau du langage C/C++.
- la première est au contraire une définition de variable avec initialisation.
C'est bien à 100% une définition de variable de la même manière que si le mot clé 'const' n'était pas là. Sauf que cette variable est affublé du modificateur 'const' qui fait qu'elle n'est plus variable justement mais que sa valeur ne peut pas être changée.
En théorie, à l'issue de la phase de compilation, il y a bien création d'une variable, avec son emplacement mémoire. Emplacement mémoire qui est initialisé avec la valeur 14.
Le mot clé 'const' empêche seulement d'utiliser cette variable sur la gauche d'une affectation et l'écriture de 'CONTROLE_MOTEUR_1_ENA = 0;' retournera une erreur de compilation.
Dans le 1er cas, l'écriture de 'digitalWrite( CONTROLE_MOTEUR_1_ENA, HIGH );' sera dans toujours traitée par le préprocesseur pour fournir au compilateur la forme :
digitalWrite( 14, 1 );
Car les 2 chaînes de caractères sont replacés par le préprocesseur par leur valeur substituée correspondante.
Dans le 2nd cas, la même écriture fournit au comiplateur la forme:
digitalWrite( CONTROLE_MOTEUR_1_ENA, 1 );
Et seule le HIGH est une macro préprocesseur remplacée alors par sa chaîne de caractère équivalente.
Vous avez suivit jusque là ?
On continue.
Donc le compilateur est sensé générer du code qui va aller lire en mémoire la valeur de la variable CONTROLE_MOTEUR_1_ENA afin de pouvoir passer cette valeur à la fonction digitalWrite().
Ce qui donc a priori utilise plus de code que l'utilisation du define puisque dans le cas du define, la valeur est immédiatement connue et donc le code assembleur généra devrait être plus simple ....
.....
Sauf que .....
Il existe encore une 3me phase, après le pré-processeur et après le compilateur qui est l'optimiseur. Et l'optimiseur, il est pas con lui.
Il a bien vu que la variable c'est une constante qui va toujours valoir la même valeur.
Donc, ni une, ni deux, il supprime l'accès à la variable et il utilise directement la valeur.
Et puis si à la fin personne n'a besoin d'aller chercher de valeur dans la case mémoire, il la supprime aussi de la map et donc pas d'allocation mémoire inutile.
En résultat, dans la plupart des cas le code généré est donc strictement identique pour l'une ou l'autre des écritures.
Quelle forme choisir alors ?
C'est une question philosophique.
Historiquement, on a tendance à favoriser l'écriture à base de #define car au début les compilateurs n'étaient pas toujours capable de pratiquer cette optimisation.
Maintenant on a plutôt tendance à recommander l'écriture sous la forme de variable 'const' qui présente en plus l'avantage de typer la valeur est donc un meilleur contrôle du code C.
Il reste toutefois des choses qui ne sont possible que sous la forme de #define.
Par exemple le define de OCR5A permet de l'utiliser comme une pseudo variable : OCR5A = 5;
est parfaitement légal.
En C, il n'est pas possible de faire cela autrement.
L'écriture la plus proche en C serait comme on à vu:
volatile unit16_t * const PTR_OCR5A = (volatile uint16_t *)0x128;
ce qui impose l'écriture :
*PTR_OCR5A = 5;
Notez l'emplacement du mot clé const qui indique de c'est bien PTR_OCR5A qui est une constante (le pointeur) et non pas la valeur pointée!!!!
j'ai vérifié et cette écriture génère bien le même code court sans déréférencement d'une variable pointeur.
Comme indiqué, en C++ on a le droit de passer par une référence ce qui donne:
volatile uint16_t &OCR5A = *(volatile uint16_t *)0x128;
Mais dans ce cas, il est impossible de spécifier qu'il s'agit d'une référence 'const' ce qui implique que le compilateur générera toujours un déréférencement de pointeur(lecture de la variable référence OCR5A pour connaitre l'adresse réelle).
Au fait. J'ai écris plus haut :
En résultat, dans la plupart des cas le code généré est donc strictement identique pour l'une ou l'autre des écritures.
En effet, il existe des cas où l'écriture
const byte CONTROLE_MOTEUR_1_ENA = 14;
peut se traduire exactement comme une variable avec lecture de la case mémoire concernée.
En effet, si vous utilisez plusieurs fichiers. Si vous placez dans le fichier Module1.cpp la définition de constante :
const byte CONTROLE_MOTEUR_1_ENA = 14;
Et que ensuite pour pouvoir utiliser cette constante vous mettez dans le fichier d'interface Module1.h la déclaration :
extern const byte CONTROLE_MOTEUR_1_ENA;
De façon à pouvoir écrire dans Module2.cpp :
#include "Module1.h"
digitalWrite( CONTROLE_MOTEUR_1_ENA; HIGH );
Alors dans ce cas, CONTROLE_MOTEUR_1_ENA sera traitée dans Module2 comme une variable externe et aucune optimisation dans Module2 ne sera effectuée (mais toujorus effectuée dans Module1).
La solution étant donc de mettre la définition de la constante dans le fichier Module1.h et non pas dans le corps du Module1.cpp.
Bon.
J'espère avoir été clair car ce n'est pas forcément immédiat quand on vient d'un langage tel que le Pascal.
Le Pascal est un langage très formalisé et contrôlé, on est assez loin de la machine. Les pointeurs sont avant tout un raccourcit d'écriture qui ne permettent pas d'erreur (on ne peut pas en Pascal avoir un pointeur qui ne pointe pas vers quelque chose de connu). En C/C++, aucun contrôle n'est effectué sur la validité d'un pointeur (comme sur un index de tableau d'ailleurs). Ce qui est la cause de bien des problèmes avec les débutants qui ne comprennent pas que derrière un pointeur il faut qu'il y ait une case mémoire réelle allouée par quelqu'un.
A+