Obligement - L'Amiga au maximum

Mercredi 20 septembre 2017 - 07:44  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Accueil
 · A Propos
 · Articles
 · Galeries
 · Glossaire
 · Hit Parade
 · Liens
 · Liste jeux Amiga
 · Quizz
 · Téléchargements
 · Trucs et astuces


Articles

 · Actualité (récente)
 · Actualité (archive)
 · Comparatifs
 · Dossiers
 · Entrevues
 · Matériel (tests)
 · Matériel (bidouilles)
 · Points de vue
 · En pratique
 · Programmation
 · Reportages
 · Tests de jeux
 · Tests de logiciels
 · Tests de compilations
 · Articles divers

 · Articles in english
 · Articles in other languages


Twitter

Suivez-nous sur Twitter




Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Moteurs de recherche
 · Pages de liens
 · Constructeurs matériels
 · Matériel
 · Autres sites de matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Développeurs logiciels
 · Logiciels
 · Développeurs de jeux
 · Jeux
 · Autres sites de jeux
 · Scène démo
 · Divers
 · Informatique générale


Jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Partenaires

Annuaire Amiga

Amedia Computer

Relec

Hit Parade


Contact

David Brunet

Courriel

 


Programmation : C - questions sur les pointeurs
(Article écrit par Laurent Faillie et extrait d'Amiga News - juillet 1994)


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 :

<type> <nom>

...ce qui donne pour un entier :

int var_entière;

Pour un pointeur, on rajoute une étoile derrière le type :

int * pointeur_sur_entier;

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...) :

10 A=&6F16
20 POKE A,PEEK A OR 128

On le traduirait en C par :

C

Aïe, aïe, aïe, j'y comprends rien ! Mais si, la première ligne ne pose pas de problèmes : on définit "a" comme un pointeur sur un octet non signé (UBYTE) et prend la valeur 6F16 hexa comme nous le prouve le printf final. La suite est plus problématique : l'étoile devant le nom du pointeur indique que nous travaillons sur la case mémoire pointée (nous modifions le contenu de 6F16 en l'occurrence). Supprimez les étoiles : cette fois nous modifions "a" (le printf() affiche 6F96).

Le dernier opérateur spécifique aux pointeurs est le "&". Il retourne l'adresse de son opérande. Ainsi...

printf("Adresse de a = %x\n", &a)

...affichera l'adresse de a.

Petit probleme :

C

Quelle est la valeur de b affichée par le printf() ? Voyons, voyons, "a" contient l'adresse de "b", donc le "*a = 3" qui affecte 3 à la "case mémoire" pointée par "a" donc... on modifie b. Résultat : b = 3.

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 :

char x[256]; // défini un tableau de 256 caractères

On a :

x == &x[0];

Par réciprocité,

char *x = "Salut";  // défini x comme un pointeur sur
                    // de caractère et qui pointe sur la
                    // chaîne 'Salut'.

On a aussi :

x[0] == 'S'; x[1] == 'a';...

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 :

C

"????" est une donnée qui ne nous appartient pas. Elle est écrasée par le nouveau 0. D'autre part, comme vous avez dû le remarquer, fournir directement une chaîne est équivalent à donner son adresse. Ainsi, lors d'un :

puts("Bonjour");

...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 :

C

Sur char chaine[256]; quelles sont les incidences de :

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 :

C

Quelle différence y a-t-il entre &chain[idx] et chain[idx] ?

(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.

char *ch="Salut tout le monde";
puts(&ch[6]);

...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 freeware de DICE du code suivant :

C

Ça marche quelques secondes puis des parasites apparaissent à l'écran, suivi d'une méditation du Guru (généralement sur l'input.device). Les problèmes disparaissent si je teste directement "msg" sans faire de copies ! Problème de cette vieille version de DICE ?

Réponse : je ne pense pas que ce soit causé par DICE car j'ai utilisé cette méthode souvent avec la version freeware 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 :

C

...n'importe quel compilateur devrait crier un "int-ptr conversion" indiquant que Text() demande un char et que vous lui fournissez simplement un char. Tous les prototypes de fonctions système se trouvent dans le répertoire clib des includes Commodore (#include <clib/graphics_protos.h>). Un bon programme doit se compiler sans un seul warning ! Le C traite les chaînes de caractères comme le ferait un langage de bas niveau. Il faut donc faire très attention à ne pas modifier les constantes, à ne pas dépasser les limites des tableaux. C'est le seul point où le BASIC et le Pascal sont supérieurs au C !

Attention aussi aux erreurs du genre :

C

Ce programme ne peut pas marcher car nous ne testons pas si le contenu de mpasse est "salut" mais si mpasse pointe sur l'adresse de la chaîne, ce qui est évidemment toujours faux (il pointe sur son propre tableau de 256 caractères).

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 :

GfxBase = (struct GfxBase *)OpenLibrary(GRAPHICSNAME, 36L);

...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 :

C

Le premier champ est une structure "library" alors que le second est un pointeur sur une structure "View". Le problème pour quelqu'un qui n'est pas habitué à jongler avec ces structures est de savoir si l'on doit utiliser le point (.) ou la flèche (->) pour accéder à un champ. Pour faire ce choix, il faut déterminer si le parent du champ est un pointeur ou non. Ainsi, nous voulons connaître la version de la bibliothèque, il faut faire :

GfxBase->LibNode.lib_Version

"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 !


[Retour en haut] / [Retour aux articles]