#define vs. enums pour l’adressage de périphériques

Je dois programmer des registres périphériques dans un microcontrôleur basé sur ARM9.

Par exemple, pour l’USART, je stocke les adresses de mémoire pertinentes dans un enum :

 enum USART { US_BASE = (int) 0xFFFC4000, US_BRGR = US_BASE + 0x16, //... }; 

Ensuite, j’utilise des pointeurs dans une fonction pour initialiser les registres:

 void init_usart (void) { vuint* pBRGR = (vuint*) US_BRGR; *pBRGR = 0x030C; //... } 

Mais mon professeur dit que je ferais mieux d’utiliser #define s, tel que:

 #define US_BASE (0xFFFC4000) #define US_BRGR (US_BASE + 0x16) #define pBRGR ((vuint*) US_BRGR) void init_usart (void) { *pBRGR = 0x030C; } 

Comme cela, dit-il, vous n’avez pas l’obligation d’allouer des pointeurs dans la stack.

Personnellement, je n’aime pas beaucoup #define , ni d’autres directives du préprocesseur. La question est donc, dans ce cas particulier, est-ce que #define s vaut vraiment la peine d’être utilisé au lieu d’ enum s et de pointeurs alloués par stack?


Question connexe: Vous souhaitez configurer un registre de périphérique particulier dans une puce basée sur ARM9

L’approche que j’ai toujours privilégiée consiste à définir d’abord une structure reflétant la disposition du registre des périphériques

 typedef volatile unsigned int reg32; // or other appropriate 32-bit integer type typedef struct USART { reg32 pad1; reg32 pad2; reg32 pad3; reg32 pad4; reg32 brgr; // any other registers } USART; USART *p_usart0 = (USART * const) 0xFFFC4000; 

Ensuite, dans le code, je peux simplement utiliser

 p_usart0->brgr = 0x030C; 

Cette approche est beaucoup plus propre lorsque vous avez plusieurs instances du même type de périphérique:

 USART *p_usart1 = (USART * const) 0xFFFC5000; USART *p_usart2 = (USART * const) 0xFFFC6000; 

User sbass a fourni un lien vers une excellente chronique de Dan Saks qui donne beaucoup plus de détails sur cette technique et souligne ses avantages par rapport à d’autres approches.

Si vous êtes assez chanceux pour utiliser C ++, vous pouvez append des méthodes pour toutes les opérations courantes sur le périphérique et bien encapsuler les particularités des périphériques.

Je crains que les enum soient une impasse pour une telle tâche. La norme définit les constantes d’ enum comme étant de type int , elles ne sont donc généralement pas compatibles avec les pointeurs.

Un jour sur une architecture avec des pointeurs 32 bits int et 64 bits, vous pourriez avoir une constante qui ne rentre pas dans un int . Ce qui va arriver n’est pas bien défini.

D’autre part, l’argument selon lequel enum allouerait quelque chose sur la stack n’est pas valide. Ce sont des constantes de temps de compilation et n’ont rien à voir avec la stack de fonctions, ni plus que les constantes que vous spécifiez par le biais de macros.

Dan Saks a écrit un certain nombre de colonnes à ce sujet pour la programmation de systèmes embarqués. Voici un de ses derniers . Il discute de C, C ++, des énumérations, des définitions, des structs, des classes, etc. et des raisons pour lesquelles vous pouvez superposer les uns aux autres. Vaut vraiment la peine d’être lu et toujours de bons conseils

D’après mon expérience, l’une des principales raisons d’utiliser #define pour ce genre de chose est qu’il s’agit davantage de l’idiome standard utilisé dans la communauté intégrée.

Utiliser des énumérations au lieu de #define générera des questions / commentaires de la part des instructeurs (et à l’avenir des collègues), même si l’utilisation d’autres techniques peut présenter d’autres avantages (comme ne pas écraser l’espace de noms d’identificateur global).

Personnellement, j’aime bien utiliser des énumérations pour les constantes numériques, mais vous devez parfois faire ce qui est habituel pour quoi et où vous travaillez.

Cependant, la performance ne devrait pas être un problème.

La réponse est toujours de faire ce que veut l’enseignant et de faire passer le cours à votre propre question, de savoir si ses raisons sont valables et de se faire sa propre opinion. Vous ne pouvez pas gagner contre l’école, n’en vaut pas la peine.

Dans ce cas, il est facile de comstackr ou de désassembler un assembleur pour voir la différence éventuelle entre l’énumération et la définition.

Je recommanderais le définit sur enum, j’ai eu l’inconfort du compilateur avec les énumérations. Je déconseille vivement d’utiliser des pointeurs tels que vous les utilisez. J’ai vu tous les compilateurs ne pas générer avec précision les instructions souhaitées. C’est rare, mais quand cela se produit, vous vous demanderez comment vos dernières décennies de codage ont fonctionné. Les structures de pointage ou autre sont considérablement pires. Je me fais souvent flamber pour cela et je m’attends à ce moment-là. Trop de kilomètres autour du bloc, nous avons corrigé trop de code erroné avec ces problèmes pour ignorer la cause fondamentale.

Je ne dirais pas nécessairement que de toute façon, c’est mieux. C’est juste une préférence personnelle. En ce qui concerne l’argument de votre professeur, c’est vraiment un point discutable. L’allocation de variables sur la stack correspond à une instruction, quel qu’en soit le nombre, généralement sous la forme sub esp, 10h . Donc, si vous avez un local ou 20, c’est toujours une instruction pour allouer l’espace à tous.

Je dirais que l’un des avantages de #include est que si, pour une raison quelconque, vous souhaitez modifier le mode d’access à ce pointeur, il vous suffit de le modifier à un emplacement donné.

J’aurais tendance à utiliser un enum, pour une compatibilité future avec le code C ++. Je dis cela parce que, dans mon travail, nous avons beaucoup de fichiers d’en-tête C partagés entre des projets, dont certains utilisent du code C et d’autres, du C ++. Pour ceux qui utilisent C ++, nous aimerions souvent envelopper les définitions dans un espace de noms pour empêcher le masquage de symboles, mais vous ne pouvez pas affecter de #define à un espace de noms.