Comment forcer une mémoire inutilisée lue en C qui ne sera pas optimisée?

Les microcontrôleurs ont souvent besoin d’un registre pour pouvoir effacer certaines conditions d’état. Existe-t-il un moyen portable en C d’assurer que la lecture n’est pas optimisée si les données ne sont pas utilisées? Suffit-il que le pointeur sur le registre mappé en mémoire soit déclaré volatil? En d’autres termes, les éléments suivants fonctionnent-ils toujours sur des compilateurs conformes aux normes?

void func(void) { volatile unsigned int *REGISTER = (volatile unsigned int *) 0x12345678; *REGISTER; } 

Je comprends que traiter avec une fonctionnalité comme celle-ci se heurte à des problèmes liés au compilateur. Donc, ma définition de portable est un peu vague dans ce cas. Je veux juste dire que cela fonctionnerait aussi largement que possible avec les chaînes d’outils les plus populaires.

Les gens discutent avec acharnement de ce volatile signifie volatile . Je pense que la plupart des gens s’accordent pour dire que le assembly que vous montrez avait pour but de faire ce que vous voulez, mais il n’y a pas d’accord général pour dire que le libellé de la norme C le garantit réellement à partir de C99. (La situation a peut-être été améliorée en C2011; je ne l’ai pas encore lu.)

Une alternative non standard, mais assez largement supscope par les compilateurs intégrés, qui pourrait être plus susceptible de fonctionner est

 void func(void) { asm volatile ("" : : "r" (*(unsigned int *)0x12345678)); } 

(Le terme “volatile” correspond ici à “asm” et signifie “ceci ne peut pas être supprimé même s’il n’a pas d’opérande de sortie. Il n’est pas nécessaire de le placer également sur le pointeur.)

L’inconvénient majeur de cette construction réside dans le fait que vous ne pouvez toujours pas garantir que le compilateur générera une lecture mémoire à une instruction . Avec C2011, utiliser _Atomic unsigned int pourrait suffire, mais en l’absence de cette fonctionnalité, vous devez écrire un vrai assemblage (non vide), insérez-vous vous-même si vous avez besoin de cette garantie.

EDIT: Une autre ride m’est venue à l’esprit ce matin. Si la lecture de l’emplacement de mémoire a pour effet secondaire de modifier la valeur de cet emplacement de mémoire, vous devez

 void func(void) { unsigned int *ptr = (unsigned int *)0x12345678; asm volatile ("" : "=m" (*ptr) : "r" (*ptr)); } 

pour éviter une mauvaise optimisation des autres lectures à partir de cet emplacement. (Pour être clair à 100%, ce changement ne changera pas le langage d’assemblage généré pour func lui-même, mais peut affecter l’optimisation du code environnant, en particulier si func est en ligne.)

Oui, le standard C garantit que le code accédant à une variable volatile ne sera pas optimisé.

C11 5.1.2.3/2

“Accéder à un object volatile,” … “sont tous des effets secondaires”

C11 5.1.2.3/4

“Une implémentation réelle n’a pas besoin d’évaluer une partie d’une expression si elle peut en déduire que sa valeur n’est pas utilisée et qu’aucun effet secondaire requirejs n’est produit (y compris tout effet causé par l’appel d’une fonction ou l’access à un object volatile).”

C11 5.1.2.3/6

“Les exigences minimales pour une implémentation conforme sont:

– Les access aux objects volatils sont évalués ssortingctement selon les règles de la machine abstraite. ”

IIRC, la norme C est un peu vague dans la définition d’utilisation, donc *REGISTER n’est pas nécessairement interprété comme une lecture .

Mais ce qui suit devrait faire:

 int x = *REGISTER; 

C’est-à-dire que le résultat de la référence mémoire doit être utilisé quelque part. Le x n’a pas besoin d’être volatile, cependant.

UPDATE : Pour éviter l’avertissement de la variable _unused, vous pouvez le faire avec une fonction no-op. Une fonction statique et / ou inline doit être optimisée sans pénalité d’exécution:

 static /*inline*/ void no_op(int x) { } no_op(*REGISTER); 

UPDATE 2 : Je viens juste de créer une fonction plus intéressante:

 static unsigned int read(volatile unsigned int *addr) { return *addr; } read(REGISTER); 

Désormais, cette fonction peut être utilisée à la fois en lecture et en utilisation et en lecture et suppression. 😎

Les compilateurs n’optimisent généralement pas les inlines d’assemblage (il est difficile de les parsingr correctement). De plus, cela semble être une solution appropriée: vous voulez un contrôle plus explicite sur les registres et c’est naturel pour l’assemblage.

Étant donné que vous programmez un microcontrôleur, je suppose qu’il existe déjà un assemblage dans votre code, de sorte qu’un assemblage en ligne ne posera pas problème.

Peut-être que les extensions spécifiques à GNU C ne sont pas considérées comme très portables, mais voici une autre alternative.

 #define read1(x) \ ({ \ __typeof(x) * _addr = (volatile __typeof(x) *) &(x); \ *_addr; \ }) 

Cela se traduira par la ligne d’assembleur suivante (compilée avec gcc x86 et optimisée avec -O2): movl SOME_REGISTER(%rip), %eax?

Je reçois le même assembleur de:

 inline read2(volatile uint32_t *addr) { return *addr; }` 

… comme suggéré dans une autre réponse, mais read1() gérera différentes tailles de registre. Même si je ne suis pas sûr que l’utilisation de read2() avec des registres de 8 ou 16 bits soit un problème, il n’y a au moins aucun avertissement sur le type de paramètre.