mardi 4 août 2015

comprendre les pointeurs

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:

Enregistrer un commentaire