|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Aujourd'hui, je vais répondre aux questions d'un lecteur anonyme concernant les pointeurs, un sujet qui a dû déjà amener de nombreux programmeurs au bord de la crise de nerf. C'est sans doute l'aspect le plus épineux du C et qui peut rendre le pire des plats de nouilles BASIC beaucoup plus lisible que nos sources C... Nous parlerons aussi des chaînes de caractères qui en sont l'application la plus courante. La mémoire d'un ordinateur est composée de cases mémoires qui contiennent chacune un caractère de 8 bits et que l'on appelle "octet". Chaque octet est identifié par une adresse. Comme l'ordinateur a besoin de stocker des informations de plus de 256 valeurs différentes (2^8 = 256), il regroupe ces octets en mots de 16 bits ou en mots long de 32 bits, voire plus... Un pointeur est une variable qui contient l'adresse d'un objet en mémoire. Cet objet est défini par son type (caractère (octet), mot, mot long ou structure diverse...). Notez au passage que tous les types les plus courants sont définis dans le fichier <exec/types.h> des includes systèmes. Pour définir une variable, nous utilisons la syntaxe suivante :
...ce qui donne pour un entier :
Pour un pointeur, on rajoute une étoile derrière le type :
Prenons un exemple simple en BASIC (pour les heureux possesseurs d'un Sharp PC-1350, ce POKE sympathique vous permettra d'afficher les caractères japonais katanas...) :
On le traduirait en C par : ![]() Le dernier opérateur spécifique aux pointeurs est le "&". Il retourne l'adresse de son opérande. Ainsi...
...affichera l'adresse de a. Petit probleme : ![]() Après ce petit préambule concernant les pointeurs et avant d'attaquer des utilisations plus... violentes, il est temps de répondre à notre lecteur. Quelle différence y a-t-il en mémoire entre 'C' et "C" ? La première forme est une valeur qui correspond au caractère C dans la table ASCII. C'est donc un entier dont la valeur est 67. Dans la seconde forme, nous déclarons une chaîne constante composée de deux caractères : 67 ('C') et 0 qui indique la fin de la chaîne. Une chaîne de caractères n'est en fait qu'un tableau où sont stockés les caractères qui la composent. Ainsi, si chaîne contient "Salut", nous avons chaîne[0]='S', chaîne[1]='a',... chaîne[4]='t', chaine[5]=0. C'est le 0 qui indique que la chaîne est finie. L'oublier provoquera le réveil de notre ami le Guru... Attention : ceci ne concerne pas certaines fonctions de la dos.library qui a été écrite en BCPL et où les chaînes ont un autre format. Comme pour tout type de tableau, donner simplement le nom de la chaîne est équivalent à indiquer l'adresse de base du tableau, donc si :
On a :
Par réciprocité,
On a aussi :
Attention : dans ce dernier cas, x pointe sur une chaîne constante, qui ne doit donc pas être modifies sous peine de plantage. Ici, le compilateur a réservé 6 octets pour stocker la chaîne ('S' +'a' + 0). Si l'on tente de rajouter un point d'exclamation par strcat(x,"!"); notre chaîne a maintenant une longueur de 7 octets et écrase donc l'octet qui la suit. Voici le contenu de la mémoire : ![]()
...la fonction ne reçoit pas en paramètre 'B'+'o'+... mais le pointeur sur le début de la chaîne c'est-à-dire 'B'. Quelle différence entre char *chaine[256] et char chaine[256] ? Une très grosse différence : dans le premier cas, nous déclarons un tableau de 256 pointeurs (sur des caractères) alors que dans le second nous déclarons un tableau de 256 caractères qui pourra contenir une chaîne (strcpy(chaîne,"salut")). La première forme permet de créer des tableaux de chaînes constantes : ![]() strcat(chaine,"C"); ? Comme je l'ai dit plus haut, ajoute le caractère C à la chaîne. La partie à ajouter peut faire plusieurs caractères (strcat(chaine,"machin");). Évidemment, il faut que les chaînes se terminent par un 0 sinon on risque le plantage. chaine[lgr++]= 'C' ? Je rappelle que "chaine" est un tableau de caractères dont lgr est ici l'indice. La réponse dépend essentiellement de la valeur de lgr. chaine[lgr] prend la valeur 67 ('C'). Exemples : ![]() (l'auteur de cette lettre donne un exemple avec un affichage utilisant la fonction Text(), mais le résultat est identique avec un simple puts()). Aïe, aïe, aïe, le Guru n'est pas loin ! Explication : je rappelle que chain[idx] renvoie un caractère, c'est-à-dire un entier codé sur 8 bits pouvant donc prendre des valeurs de 0 à 255. On essaie donc d'afficher une chaîne qui se trouverait à une adresse comprise entre 0 et 255. Cette zone mémoire est réservée par le système pour les vecteurs des exceptions, des traps, des interruptions... Résultat : on affiche des caractères bidons. Si on essaie de modifier cette chaîne, c'est le plantage rapidement. Dans le second cas, ça marche sans problème... si "idx" a une valeur correcte comprise entre 0 et la taille du tableau.
...affichera "tout le monde" car le &ch[6] permet de sauter les six premiers caractères de la chaîne. Un texte a été affiché grâce à la fonction Text() Pourquoi n'est-il pas effacé correctement simplement en Text()ant le même texte avec la couleur 0 ? Je ne peux pas vous aider et il doit y a voir une erreur ailleurs car cette méthode fonctionne très bien, le problème provient soit de votre version du Kickstart (peu probable), soit d'une erreur ailleurs dans le source. De toute façon, la graphics.library contient des fonctions plus rapides pour effacer une portion d'une fenêtre comme RectFill(). Problème avec la version gratuicielle de DICE du code suivant : ![]() Réponse : je ne pense pas que ce soit causé par DICE car j'ai utilisé cette méthode souvent avec la version gratuicielle sans aucun problème. Le fait que les plantages disparaissent après une modification n'est pas une preuve que le code est correct car les compilateurs organisent leurs données différemment suivant le code qu'on leur fournit. D'autre part, les symptômes indiqueraient un problème de pointeur fou (pointeur non initialisé, adresse foireuse... ou chaîne plus grande que la mémoire qui lui est réservée es qui détruit un pointeur !). Ici non plus, je ne peux rien faire sans le reste du listing ! Quelques conseils Pour conclure avec les pointeurs, il n'y a qu'une solution pour éviter beaucoup de désagréments : prototyper, prototyper, prototyper ! Ainsi, face à un : ![]() Attention aussi aux erreurs du genre : ![]() Pointeurs et structures Comme promis, passons à des applications plus violentes mettant en scène des pointeurs : les pointeurs associés aux structures. C'est le point le plus complexe du langage mais aussi ce qui fait sa force et c'est très utilisé sur notre machine. En effet, sur les machines 8 bits, toutes les adresses système sont absolues car il n'y a généralement qu'un seul programme en mémoire, sans multitâche. Ainsi on sait que sur un Sharp 1350, &6f01,02 contient l'adresse de début de la mémoire BASIC. Sur Amiga, ce n'est plus possible : la mémoire n'est qu'une ressource comme une autre qui doit être partagée entre toutes les tâches. En plus, les structures système évoluent à chaque version du Kickstart. Rien n'est figé en mémoire, tout est relatif et utilise donc intensément les pointeurs... Prenons l'exemple de la graphics.library. Nous l'ouvrons par un :
...ce qui nous fournit un pointeur sur une structure contenant les informations sur la bibliothèque, définie dans graphics/gfxbase.h et qui commence comme suit : ![]()
"GfxBase" est un pointeur donc la flèche est nécessaire pour accéder à "LibNode". Par contre, c'est le point qui sépare "lib_Version" de LibNode car le parent, "lib_Version", n'est pas un pointeur mais une structure. Petit exercice pour voir si vous avez tout compris : comment connaître le mode de l'écran actif (champ Mode de l'ActiView) ? Dans un prochain article, je parlerais des outils de débogage à notre disposition comme MungWall ou Enforcer qui permettent de localiser pas mal de bogues décrits dans cet article. A la prochaine !
|