Lorsque j'ai appris le langage C il y a longtemps l'un des concepts qui m'a donné le plus de difficulté est celui concernant l'usage des variables pointeurs. Cet article est à l'usage de ceux qui débutent en C est qui auraient la même difficulté.
coup d’œil sur les registres interne d'un MCU
Prenons un MCU quelconque, par exemple un PIC12F1822. Si on regarde le modèle de programmation du cœur de ce MCU on voit ceci.
Les registres FSR0 et FSR1 sont ce qu'on appelle des registres d'index, c'est à dire des variables pointeurs. Supposons qu'on écris un programme en mpasm et qu'on veut initialiser la RAM commune à zéro que fait-on?
;sous-routine pour effacer ram commune 0x70-0x7f
clear_common_ram:
;initialistion du pointeur
clrf FSR0H
movlw H'70'
movwf FSR0
clrf WREG
clear_loop:
movwi, FSR0++ ; adressage indirect avec post-incrément
btfss FSR0,7
bra clr_loop
return ; terminé car FSR0==0x80
On pourrais aussi faire ceci sur un PIC baseline:
;sous-routine pour effacer ram commune 0x70-0x7f
clear_common_ram:
movlw H'70'
movwf FSR0
clear_loop:
clrf FSR0 ; adressage indirect
incf FSR0,F ; incrémente le pointeur
btfss FSR0,7
bra clr_loop
return ; terminé car FSR0==0x80
On a mis 0 dans le registre WREG et on transfert cette valeur dans toutes les localisations RAM entre 0x70 et 0x7F. Pour ce faire on se sert du registre d'index FSR0. FSR0 est une variable pointeur. FSR0 ne contient pas un entier mais une adresse et cette adresse indique où on veut mettre la valeur de WREG. l'instruction movwi FSR0++ effectue 2 opérations. D'abord elle transfert le contenu de WREG qui est zéro à l'adresse RAM indiquée par le contenu de FSR0 et deuxièmement incrémente de 1 la valeur de FSR0 pour pointé à l'adresse suivante. Le programme boucle sur clear_loop tant que le bit 7 de FSR0 est à 0 car si le bit 7 est à 1 c'est que FSR0 pointe sur une adresse après 0x7F.
la même chose en C
répétons le même programme en C
void clear_common_ram(){
char *p;
p=(char*)0x70;
while(((int)p&0x80)==0){
*p++=0;
}
}
variable pointeur
Dans tout langage de programmation une variable est une adresse en mémoire dans laquelle une information est conservée. Dans l'exemple ci-haut p ne fait pas exception, c'est une adresse mémoire dans laquelle on va garder une information mais cette information n'est pas un char mais une adresse qui indique un autre endroit en mémoire ou est conservé une donnée de type char. Ça a peut-être l'air plus compliqué que la version mpasm mais c'est presque la même chose. Lorsqu'on déclare la variable p l'étoile * avant le p informe le compilateur que cette variable va contenir une autre adresse et non un char.
l'instruction *p++=0; indique qu'on veut modifier le contenu de l'adresse pointée par p et non le contenu de p lui-même. Ici l'étoile * n'a pas la même signification que lorsqu'on déclare la variable. Elle signifie plutôt un adressage indirect. Dans de nombreux assembleurs l'adressage indirect (par pointeur) est indiqué par le nom du registre qui sert de pointeur entre crochet comme ceci:
LOAD R0,[R15]
Ce qui signifie met la valeur qui se trouve à l'adresse indiquée par le contenu de R15 dans R0. Donc ici R0 est une variable ordinaire tandis que R15 est une variable pointeur (registre d'indexation ou d'indirection).
Quand le programme compilé va s'exécuter, il va aller chercher la valeur de p et la mettre dans FSR0 ou FSR1. ensuite il va mettre WREG à zéro et effectuée une boucle très semblable à celle d'écrite ci-haut dans la version mpsasm.
J'ai fait un test dans MPLAPX en compilant avec XC8 free (pas d'optimisation) voici le résultat dans le disassembly listing
11: void clear_common_ram(){
12: char *p;
13: p=(char*)0x70;
07ED 3070 MOVLW 0x70
07EE 00F0 MOVWF __pcstackCOMMON
07EF 3000 MOVLW 0x0
07F0 00F1 MOVWF 0x71
14: while(((int)p&0x80)==0){
07F1 1BF0 BTFSC __pcstackCOMMON, 0x7
07F2 0008 RETURN
07FC 2FF1 GOTO 0x7F1
15: *p++=0;
07F3 0870 MOVF __pcstackCOMMON, W
07F4 0086 MOVWF FSR1
07F5 0871 MOVF 0x71, W
07F6 0087 MOVWF FSR1H
07F7 0181 CLRF INDF1
07F8 3001 MOVLW 0x1
07F9 07F0 ADDWF __pcstackCOMMON, F
07FA 3000 MOVLW 0x0
07FB 3DF1 ADDWFC 0x71, F
16: }
17: }
Ça a l'air plus compliqué sans optimisation! __pcstackCOMMON est une pseudo-pile créé par le compilateur. On peut s'en passer et simplifier ça. Il n'est pas nécessaire non plus de stocker le pointeur FSR1 sur la pile __pcstackCOMMON.
clear_common_ram:
; initialisation du pointeur p
; le compilateur a choisi FSR1 comme pointeur
; les FSR du PIC12F1822 sont de 16 bits
movlw 0x70
movwf FSR1L
movlw 0
movwf FSR1H
clear_loop:
btfsc FSR1L,7
return
clrf INDF1
; fsr1 a 16 bits, incrément par addition de 1
movlw 1
addwf FSR1L
movlw 0
addwfc FSR1H
goto clear_loop
Lorsqu'on déclare un tableau en C i.e. char tableau[10];, le compilateur va aussi utiliser un registre d'index pour atteindre les éléments de ce tableau, i.e. b=tableau[2]; se traduis en assembleur par:
;initialise le pointeur
movlw LOW tableau
movwf FSR0L
movlw HIGH tableau
movwf FSR0H
;va cherché la valeur dans le 3ième élément du tableau.
moviw 2[FSR0]; WREG=indirection:[FSR0+2]
movwf b ; assigne cette valeur à b
adresse d'une variable ordinaire
Si l'étoile avant le nom d'un pointeur sert à indiquer une indirection,on dit déréférencer, comment fait on pour obtenir l'adresse mémoire où est stocké le contenu d'une variable ordinaire:
int b=0, *p;
p=&b;
*p=34; // que contient b?
Le symbole & est utilisé pour indiquer l'adresse de la variable et non son contenu. donc dans cette exemple p contient l'adresse de la variable b. Et lorsqu'on déréférence p pour y assigner la valeur 34 et bien la valeur de la variable b devient 34 car p pointe vers b.
Que ferais-je sans toi
En effet comment ferait-on sans pointeurs pour adresser un tableau ou n'importe qu'elle étendu de mémoire? J'espère donc que cette présentation a clarifié la question des pointeurs.
Aucun commentaire:
Publier un commentaire
Remarque : Seuls les membres de ce blogue sont autorisés à publier des commentaires.