Pointeurs

 

 

Introduction

Ce tutoriel est ma deuxième tentative pour expliquer ce que sont les pointeurs, principalement en C, puisque ce sera le langage utilisé pour tous les exemples (et plus spécifiquement sur Ti68k), bien que ces notions soient bien sûr adaptables à n'importe quel langage disposant de pointeurs.

Pourquoi un tutoriel "seulement" sur les pointeurs ? Parcequ'il s'agit d'une notion assez difficile à assimiler la première fois que l'on apprend un langage qui en possède, et qu'il m'a semblé qu'une mauvaise compréhension de leur fonctionnement était à l'origine de beaucoup de bloquages.

Autre chose : mon but n'est pas de faire un cours complet, mais un cours compréhensible. Il en dira moins que des documentations spécialisées sur ce sujet, mais j'espère présentera les choses de façon suffisemment simples pour être assimilables par n'importe qui.

Si vous trouvez des erreurs, des imperfections, ou quoi que ce soit d'autre, n'hésitez pas à me le faire savoir : vertyos [at] fr [point] st.

 

 

Avant les pointeurs

Variables simples

Avant de parler des pointeurs, nous allons parler des variables normales. Vous savez déjà déclarer des variables, mais quel en est l'effet exactement ? Admettons que vous créez un programme quelconque, à un moment ou à un autre vous tapez :

unsigned short var;

Qu'est-ce que cela signifie exactement ? En fait, le "unsigned short" renseigne sur la taille de votre variable en mémoire : 2 octets. Il y aura donc "quelque part" dans la mémoire 2 octets qui sont réservés pour votre variable "var", et ce jusqu'à ce qu'elle soit détruite (à la fin de la fonction dans laquelle elle est déclarée).

Vous n'avez pas besoin de vous en occuper, de toutes façons : pour vous, il suffit de manipuler "var" tout simplement, en l'utilisant dans des calculs, en lui affectant des valeurs, etc... Mais comment cela fonctionne-t-il exactement ?

 

Notion d'adresse

Vous savez probablement que ce nom, "var", n'est là que pour vous simplifier la tâche : une fois votre programme compilé, il n'existe plus. Comment est-ce que cette variable est retrouvée, parmis toutes les autres dans la mémoire ? C'est ici que le concept indispensable pour comprendre les pointeurs fait son apparition : les adresses.

Le nom est logique, puisqu'il a la même signification que son sens courant : l'adresse de la variable est un nombre qui permet de savoir exactement à quel endroit elle se trouve dans la mémoire. Imaginez la mémoire comme un énorme tableau, dans l'une (ou plusieurs, suivant la taille) de ses cases se trouve notre variable; l'adresse est tout simplement l'index de cette case.

Pour retrouver notre variable, il nous suffit donc de connaitre l'index de la case où elle se trouve dans le tableau (son adresse dans la mémoire), ainsi que le nombre de "cases" qu'elle y occupe (sa taille), par exemple une seule si c'est un "char", deux si c'est un "short", et quatre si c'est un "long". Notez au passage que l'adresse et la taille n'ont aucun lien, une variable de type "long" n'aura aucune raison d'avoir une adresse plus grande qu'une variable de type "char" : toutes deux peuvent se trouver à n'importe quel endroit de notre tableau, la mémoire.

 

 

Syntaxes à respecter

Récuperer l'adresse

Nous savons donc qu'une variable est repérée dans la mémoire par son adresse. Il est bien sûr possible d'utiliser cette adresse dans des programmes, et c'est d'ailleurs ce qu'il va falloir faire avec les pointeurs. Une adresse est, comme nous l'avons vu, un nombre. Compte tenu de la taille de la mémoire, les "cases du tableau" peuvent avoir des indices assez grands : l'adresse est donc un nombre codé sur 4 octets, ni plus, ni moins.

Voici par exemple quelques manipulations, inutiles, mais qui montrent l'utilisation de l'operateur "&", qui sert à récuperer l'adresse d'une variable. Les adresses sont toujours positives (normal, pour un index de tableau), nous utiliseront donc des "unsigned long" dans cet exemple ("unsigned" pour être toujours positif, et "long" pour avoir effectivement un nombre codé sur 4 octets). Autant prévenir tout de suite : ceci n'est pas la bonne méthode, et vous aurez droit à deux warnings en compilant cet exemple, vous expliquant qu'un "unsigned long" n'est pas fait pour stocker des adresses. Peu importe, ce code n'est ici que pour l'exemple, nous verrons plus tard quelle est la bonne méthode.

char var;
unsigned long addr1, addr2;

// Nous allons avoir deux warnings : des unsigned long ne sont pas fait pour récuperer des adresses...
addr1 = &var; // On récupere l'adresse de "var", et on la stocke dans "addr1"
var = 42;
addr2 = &var; // On récupere l'adresse de "var" à nouveau

if (addr1 == addr2)
   printf("L'adresse ne change jamais");

Quoi qu'il arrive, ce programme affichera toujours "L'adresse ne change jamais", pour la simple et bonne raison que c'est vrai : une fois déclarée, l'adresse d'une variable est fixe et ne sera jamais modifiée, jusqu'à sa destruction. C'est pour cela qu'il est par exemple impossible d'écrire un tel code :

char var;

&var = 42; // Erreur

Ce n'est pas parceque l'adresse est un nombre qu'il est possible de faire tout ce qu'on pourrait faire avec une valeur numerique normale. Mais nous verrons ça plus tard...

 

Les types de pointeurs

Nous allons enfin voir à quoi servent ces pointeurs. Le code précedent, où nous utilisions un "unsigned long" pour stocker l'adresse n'était qu'un affreux bidouillage : c'est justement pour faire ce genre de choses que les pointeurs existent.

Pour déclarer un pointeur, il faut préceder le nom de la variable par un "*" au moment de la déclaration, comme ceci :

char *pointeur_sur_char;
short *pointeur_sur_short;
long *pointeur_sur_long;

Cette fois, il y a beaucoup à dire sur peu de lignes. Premièrement, la notion de pointer "sur" un type : le premier pointeur que nous avons "pointe sur" un "char", le deuxième "sur" un "short", et le dernier "sur" un "long". Ceci n'est rien d'autre que le type de la variable dont le pointeur contient l'adresse.

En clair, notre "pointeur_sur_char" va recevoir l'adresse d'une variable, c'est son rôle. Puisqu'il pointe sur un "char", comme nous l'avons précisé lors de sa déclaration, la variable dont il contient l'adresse devra être un "char". Notez que ceci n'est pas "réellement" indispensable, puisque quel que soit le type de cette variable, notre pointeur fera toujours 4 octets et pourra contenir n'importe quelle adresse, qu'elle soit valide ou non. Mais il est très fortement conseillé de ne pas faire n'importe quoi, et de réserver les pointeurs sur "char" pour pointer sur des "char", les pointeurs sur "short" pour pointer sur des "short", etc.

Ces variables sont donc déclarées, comme nous l'avons vu elles sont pretes à recevoir des nombres codés sur 4 octets. Encore une restriction : ce sont des nombres, mais malgré cela toutes les operations ne sont pas autorisées dessus. Par exemple :

char *ptr1, *ptr2

ptr1 = ptr1 * ptr2; // Erreur

Ceci est une erreur, non pas parceque cela risquerait de provoquer un plantage, mais simplement pour une raison de bon sens : il est totalement illogique de "multiplier" des adresses, cela n'a aucun sens dans un programme. Vous pouvez le faire quand même, en "trichant", mais vous n'en aurez de toutes façons jamais besoin.

En revanche, il est possible d'additionner et de soustraire des adresses, cela pourra même être très utile à certains moments. Cela est bien sûr vrai entre des adresses et des nombres "normaux", vous pouvez ajouter 10 à une adresse par exemple (nous en verrons l'effet tout à l'heure).

 

 

Manipulation basiques

Stocker des adresses

Nous avons vu que stocker des adresses dans des "unsigned long" était à éviter, et qu'il fallait des pointeurs pour cela. Voici l'illustration :

char *ptr_char, var_char;
short *ptr_short, var_short;

ptr_char = &var_char;
ptr_short
= &var_short;

Ce bout de code n'a strictement aucun effet, mais au moins il a le mérite d'être plus propre que le précedent. Nous avons déclaré deux variables, et les pointeurs ont reçu leurs adresses : on dit qu'ils pointent dessus. "ptr_char" pointe sur "var_char", et "ptr_short" pointe sur "var_short".

N'oubliez pas que les pointeurs sont des variables comme des autres. En particulier, eux aussi ont une adresse. Cela signifie qu'on peut faire pointer un pointeur sur ... un pointeur. On appelle ça un pointeur de pointeur, et même si nous allons attendre avant de nous y interesser, sachez que cela peut être très utile (il est également possible de faire des pointeurs de pointeurs de pointeurs, etc...). Voici ce que cela donne :

char *ptr, **ptr_sur_ptr, ***ptr_sur_ptr_sur_ptr, var;

ptr = &var;
ptr_sur_ptr = &ptr;
ptr_sur_ptr_sur_ptr = &ptr_sur_ptr;

Oui, encore un exemple inutile, mais il est parfaitement valide.

 

Déreferencer un pointeur

Nous avons jusqu'à présent stocké des adresses, bien, mais cela n'a pas grand interet si on ne peut pas les utiliser. Nous allons donc maintenant voir à quoi cela sert réellement.

Une fois que nous avons notre pointeur qui pointe sur une variable, il est possible à partir de lui de récuperer le contenu de cette variable, et même de le modifier. Comment ? Il contient l'adresse de la variable, et le type que nous lui avons donné à la déclaration permet de connaitre la taille de cette même variable, nous avons donc les deux données nécessaires : il est possible de faire tout ce que l'on veut dessus.

Cette operation s'appelle "déreferencer" le pointeur. Pour cela, il faut utiliser à nouveau l'operateur "*". Nous allons tout de suite voir dans un exemple simple :

char *ptr, var;

ptr = &var;
*ptr = 42;

printf("var vaut : %d",var);

Vous l'aurez deviné, cet exemple va afficher "var vaut : 42". Pourtant nous n'avons pas modifié "var" directement, nous l'avons fait par l'intermédiaire de "ptr", en le faisant dans un premier temps pointer sur "var", puis en utilisant "*" pour accéder à la variable pointée, qui était "var". Pour résumer, une fois que nous avons stocké l'adresse de "var" dans le pointeur, travailler avec "var" ou bien avec "*ptr" a exactement le même effet.

C'est ici que l'on note l'importance du type du pointeur. Imaginez que ptr ne soit pas de type "char *", mais par exemple "short *" :

char var;
short *ptr;

ptr = &var; // Risqué (ptr n'a pas le bon type), mais pas d'erreurs
*ptr = 42; // Erreur

printf("var vaut : %d",var);

Tout va bien se passer, l'adresse de var va être correctement stockée, mais un gros problème va se poser à la ligne où nous affectons 42 : lors de l'utilisation de "*", on essaie d'acceder au contenu de la mémoire à cette adresse, et comme ptr est de type short, ce contenu est supposé faire 2 octets, comme un short. On va y stocker 42, mais on va le stocker sur 2 octets au lieu d'un seul : un octet de trop a été modifié, et il a de grandes chances de contenir des données importantes, qui vont être effacées, provoquant un bug.

Il est donc important d'utiliser des pointeurs du bon type. Autre petite précision : attention à ne pas déreferencer un pointeur qui n'a pas été défini avant, puisqu'il aura une valeur par défaut completement aléatoire. En déreferençant, vous allez donc tomber n'importe où dans la mémoire, et risquer de lire (ou pire, écrire) à des endroits inconnus.

 

 

Utilisation des pointeurs

Modification de variable par pointeur

Premier exemple d'école, imaginons un programme dans lequel nous voulons effectuer un calcul en fonction de deux variables parmis 3 dont nous disposons : "a", "b" et "c", puis afficher le résultat à l'écran. Ce calcul peut donc utiliser "a" et "b", ou bien "b" et "a" (qui ne donnera pas forcément le même résultat, si le calcul est la division de la 1ere operande par la deuxième par exemple), etc...

La première solution consisterait à écrire les 3*3=9 calculs possibles, ce qui veut dire 9 printf, même 9 fois quasiment le même calcul. Nous allons plutôt utiliser des pointeurs. Admettons que les variables op1 et op2 soient déjà définies, elles servent à savoir quelle variables seront utilisées comme première et deuxième operande (1 pour "a", 2 pour "b", et 3 pour "c").

// Admettons ici que "a", "b", "c", "op1" et "op2" sont déjà utilisables,
// ce sont par exemple les paramètres d'une fonction, elles sont toutes de type char.

char *ptr1, *ptr2;

switch (op1) // On fait pointer ptr1 sur la première operande
{
   case 1: ptr1 = &a; break;
   case 2: ptr1 = &b; break;
   case 3: ptr1 = &c; break;
}

switch (op2) // Et ptr2 sur la seconde
{
   case 1: ptr2 = &a; break;
   case 2: ptr2 = &b; break;
   case 3: ptr2 = &c; break;
}

printf("Résultat : %d",*ptr1 * (*ptr1 - *ptr2));

Bien sûr, ce code est encore un exemple qui a peu de chances de se produire, et qui plus est il n'est pas protégé : si "op1" ou "op2" sont différents de 1, 2, ou 3, les pointeurs ne seront pas définis, et de gros problèmes risquent de se présenter lors de l'utilisation de "*" pour les déreferencer.

 

Echange de deux valeurs

Nous voulons écrire une fonction qui inverse le contenu de deux variables de type short : elle doit mettre la valeur de l'une dans l'autre, et inversement. Quand on la code proprement, cette operation nécessite une troisième variable temporaire, qui servira à mémoriser la première valeur, que l'on va écraser, de façon à la restituer ensuite.

Voici comment la fonction pourrait être codée par quelqun qui ne connait pas les pointeurs :

void Swap(short a, short b)
{
   short temp;

   temp = a;
   a = b;
   b = temp;
}

C'est bien simple : cette fonction n'a aucun effet. En l'appellant, les deux paramètres ne seront absolument pas échangés, une fois la fonction terminée ils auront la même valeur qu'au départ. Pourquoi ? Tout simplement parceque lors de l'appel d'une fonction, les parametres ne sont pas les "vraies" variables que l'on a indiqué, mais des copies, qui sont effacées une fois la fonction terminée.

La solution consiste donc à passer par des pointeurs : au lieu d'indiquer les deux variables en parametre, on donne leurs adresses. Les parametres seront également des copies, mais peu importe : ce n'est plus les variables en elles-même qui nous importent comme dans la version précedente, mais leur valeur (l'adresse), qui elle sera bien sûr identique dans les copies. La fonction va alors modifier les variables en passant par leurs adresses, et c'est donc cette fois les vraies variables qui seront modifiées :

void Swap(short *a, short *b)
{
   short temp;

   temp = *a; // Par *a, on accède à la véritable variable, dans la fonction appellante
   *a = *b; // Idem ici...
   *b = temp; // Et ici
}

Attention à un détail : nous aurions appellé la première fonction de cette façon : "Swap(var1,var2)" pour échanger le contenu de "var1" et de "var2". C'est maintenant les adresses de ces variables qu'il faut indiquer, l'appel ressemble donc à présent à ceci : "Swap(&var1,&var2)".

 

 

Pointeurs et tableaux

Structure d'un tableau

Vous vous doutez peut-être de la nature des tableaux : ce sont également des pointeurs. Quand vous déclarez un tableau de N elements, la place nécessaire pour les stocker est allouée dans la mémoire, et la variable de votre tableau n'est autre qu'un pointeur vers son premier element (celui de rang 0). Par exemple :

unsigned short tableau[16];

Vous avez déclaré un tableau qui peut contenir jusqu'à 16 "unsigned short", chacun d'entre eux prenant 2 octets en mémoire, vous avez donc alloué 32 octets. La variable "tableau" pointe maintenant sur le début de cet espace alloué, c'est à dire le premier "unsigned short", et tous les autres sont à la suite. C'est pour cette raison qu'au lieu d'acceder au premier element par "tableau[0]", qui n'est qu'un raccourci d'écriture, vous pouvez utiliser "*tableau", c'est à dire déreferencer la variable "tableau", ce qui vous donne la valeur de son premier element.

Pour acceder aux autres élements, ce n'est pas plus compliqué, il faut juste se souvenir de ce que nous avons déjà dit et y ajouter un petit complément. Les adresses sont des entiers, et certaines operations mathématiques sont autorisées dessus, en particulier les additions. Puisque nos 16 elements sont stockés à la suite, il suffit d'ajouter "quelque chose" à l'adresse pour trouver le 2eme element, puis le 3eme, et ainsi de suite. Comment savoir combien ajouter ? C'est très simple, le fait d'ajouter 1 à une adresse l'incrémente en fait d'autant d'octets qu'en prend le type sur lequel elle pointe. En clair, si c'est "ptr" est un pointeur sur un "unsigned short", effectuer "ptr + 1" l'incrémente de deux octets. Si c'était un pointeur sur un "unsigned long", cela l'aurait incrémenté de quatre octets.

Pour en revenir à notre tableau, l'operation est donc très simple : puisque "tableau" nous indique l'adresse du premier element, "tableau + 1" est celle du deuxième, "tableau + 2" celle du troisième, et ainsi de suite. Il ne reste plus qu'à déreferencer, et la conclusion est très simple : "tableau[n]" est exactement équivalent à "*(tableau + n)". Ce qui confirme donc le fait que "tableau[0]" et "*tableau" (ou "*(tableau + 0)") sont deux façons d'écrire la même chose.

Une petite note sans grande utilité : cette équivalence est complete, si bien que si vous écrivez "n[tableau]" à la place de "tableau[n]", le résultat sera "*(n + tableau)" au lieu de "*(tableau + n)", ce qui revient au même. Cela dit, même si ce genre d'horreur est possible, évitez-les dans vos programmes sous peine de les rendre illisibles.

 

Décalages de tableaux

Puisque nous avons vu la relation entre pointeurs et tableaux, autant utiliser ça maintenant. Nous allons tout d'abord étudier un cas qui peut éventuellement être utile dans certains programmes : créer un tableau "décalé" par rapport à un autre. Prenons un tableau comme ceci :

unsigned short tableau[8] = {42, 17, 2, 6, 8, 65, 1250, 986};

Considérons maintenant que nous voulons le même tableau, mais sans les deux premières valeurs : un tableau qui ne contienne que la suite 2, 6, 8, 65, 1250 et 986. Cette operation est très simple, il suffit de créer un pointeur qui ne va pas pointer sur le premier element de ce tableau, comme la variable "tableau" le fait déjà, mais vers le troisième. Ceci peut être fait de deux façons, premièrement celle qui semble logique par rapport à ce que nous venons d'expliquer :

unsigned short *tableau_decale = &tableau[2]; // On fait pointer tableau_decale sur le 3eme element de tableau

Mais puisque "tableau[2]" est équivalent à "*(tableau + 2)", alors on obtient "&(*(tableau + 2))", et donc en supprimant les "&" et "*" qui s'annulent :

unsigned short *tableau_decale = tableau + 2;

Ainsi, "tableau_decale[0]" est égal à "tableau[2]", "tableau_decale[1]" à "tableau[3]", etc...

 

Parcours de tableaux

Dernier exemple avec les tableaux, parcourir un tableau est également une chose très facile en utilisant des pointeurs. On peut aisément obtenir un pointeur sur une des "cases" du tableau, l'incrémenter pour passer à la suivante, ou la décrementer pour revenir à la précedente. Il faut juste faire attention de ne pas sortir du tableau.

Nous voulons écrire une fonction qui prend en paramètre un tableau et un entier, qui indique sa taille, et qui retourne la somme de tous ses élements. Au passage, nous allons voir que puisqu'un tableau n'est qu'un pointeur, il est très facile de le passer en parametre : on passe son pointeur, et ainsi toute modification du tableau faite dans la fonction sera réellement effectuée sur le tableau, et les modifications seront toujours valides une fois l'appel de la fonction terminé.

Commençons par la version évidente, qui n'utilise pas de pointeurs (excepté celui que l'on passe en paramètre, pour le tableau) :

unsigned short SumTable (unsigned short *tableau, unsigned short taille)
{
   unsigned short i, somme = 0;

   for (i = 0; i < taille; i++) // Pour tous les elements i (i variant de 0 à taille - 1)
      somme += tableau[i]; // On ajoute la taille de l'element i
}

Notez que cette fonction, si notre tableau s'appelle "tableau" et contient 10 élements, s'appelle avec "SumTable(tableau,10)" : on passe le tableau en tant que pointeur à la fonction, et son utilisation pour y accéder reste exactement la même que dans la fonction principale.

Maintenant, nous allons en écrire une deuxième version, qui n'utilisera pas la variable "i" utilisée pour parcourir le tableau. En y réflechissant, nous savons qu'il y a "taille" elements, donc on peut décrementer la varaible "taille" de 1 et s'arrêter quand elle atteind 0 pour savoir combien d'elements additionner. On va alors utiliser "*tableau" pour accéder à la première valeur, puis incrémenter la variable "tableau" de 1, ce qui a pour effet de la faire pointer sur l'element suivant : nous n'avons plus besoin des précedent qui ont déjà été pris en compte. De cette façon on utilise à nouveau "*tableau++" pour accéder à un element, puis incrémenter la variable, pour qu'à la prochaine boucle l'element suivant soit lu, et ainsi de suite. Cela nous permet d'écrire une version plus efficace de la fonction :

unsigned short SumTable(unsigned short *tableau, unsigned short taille)
{
   unsigned short somme = 0;

   while (taille--) // En décrementant cette variable, la boucle fera exactement "taille" itérations
      somme += *tableau++; // On ajoute la valeur de l'element courant, puis on passe au suivant
}

Cette version produit exactement le même effet que la précedente, mais évite d'utiliser une variable supplémentaire "i", qui est en fin de compte inutile.

 

 

Pointeurs et chaines

Structure d'une chaine

Vous l'aurez deviné, les chaines manipulées sont elles aussi des pointeurs. Plus précisement, ce sont des tableaux de "char", donc chaque "case" du tableau est une lettre, et la dernière vaut 0 pour indiquer la fin de la chaine, sans quoi il serait impossible de savoir où s'arrêter. C'est pourquoi ce genre de code est risqué :

char ma_chaine[5] = "hello";

En effet, il y a 5 octets pour stoquer les 5 lettres, mais aucun octet supplémentaire pour le 0 de fin de chaine, donc dès l'utilisation d'une fonction qui traitera la chaine, comme printf pour l'afficher par exemple, l'affichage va se poursuivre au-dela du "o" de la chaine, jusqu'à ce que la fonction rencontre un 0 quelque part dans la mémoire.

En nous aidant de ceci, nous allons écrire une pseudo-fonction d'affichage de chaine de caractère, en supposant que "PrintChar(x,y,char)" est une fonction qui affiche la lettre "char" à la position "x, y" sur l'écran. Nous avions déjà vu que pour les tableaux, nous pouvions incrémenter le pointeur du tableau de 1 pour passer à la "case" suivante : il en est de même pour les chaines, nous pourrons donc aisément parcourir la chaine de caractères en caractères de cette façon, jusqu'à tomber sur le 0 final. Voici à quoi pourrait ressembler la fonction :

void PrintString(unsigned short x, unsigned short y, char *string)
{
   while (*string) // Tant que l'on a pas atteind le 0 final
   {
      PrintChar(x,y,*string++); // On inscrit le caractère puis on décale le pointeur
      x += 10; // On admettra qu'un caractère fait 10 pixels de large
   }
}

Ainsi, on va afficher un à un tous les caractères, en déplaçant la position x à chaque fois pour inscrire les caractères cote à cote. Une fois que le 0 final sera atteind, la boucle s'arrêtera et la chaine aura été inscrite entièrement.

 

Copie d'une chaine

Pour conclure sur les chaines, nous allons voir un exemple classique mais néanmoins assez joli d'utilisation des pointeurs, afin d'écrire une fonction qui recopie une chaine dans un buffer (exactement comme le fait la fonction "strcpy"). Grâce aux pointeurs, cette fonction va prendre une seule ligne. Voici le code sans commentaires, les explications suivent :

void CopyString(char *buffer, char *string)
{
   while ((*buffer++ = *string++));
}

Détaillons les étapes : les "++" étant évalués en dernier, nous allons les mettre de coté pour l'instant. Il nous reste un "(*buffer = *string)", qui signifie donc que l'on copie le caractère de la chaine "string" dans le buffer, et toute la parenthèse aura donc pour valeur ce caractère copié. Ensuite, les deux pointeurs seront incrémentés, on passe donc au prochain caractère de la chaine et à la prochaine "case" du buffer. Grâce au "while", cette operation est répetée tant que la valeur du caractère copié est différente de 0 : on va donc copier le premier caractère de la chaine dans la première case du buffer, puis le second caractère dans la deuxième case du buffer, et ainsi de suite, jusqu'à avoir copié le 0 final. La fonction s'arrêtera alors, et l'intégralité de la chaine aura été copiée.

Puisque ces arguments sont des pointeurs, le buffer passé en argument aura été réellement modifié (ce n'est pas une copie qui l'a été), la fonction s'utilise donc exactement comme "strcpy", par exemple de cette manière :

char buffer[14];

CopyString(buffer,"Hello, World!"); // Copie des 13 caractère + le zéro final dans le buffer de 14 octets

Voici ce qui pouvait être dit sur les chaines, tout ce qui avait été dit sur les tableaux reste bien sûr valable pour elles également.

 

 

Fin (temporaire ?)

En attendant de continuer ce tutoriel, j'attendrais de savoir si il a servi (à quoi bon perdre du temps inutilement ^^). Si vous l'avez jugé utile, merci de m'envoyer un mail (vertyos [at] fr [point] st) en y ajoutant vos remarques, critiques, commentaires, etc...

 

Vertyos