Pointeurs et adresses mémoire en C

Apprends comment un programme C dispose ses variables en mémoire et comment les pointeurs permettent de manipuler directement les adresses mémoire. Obtiens des adresses avec &, lis et modifie des valeurs à travers des pointeurs et passe des variables par adresse à des fonctions. Parcours des tableaux et des chaînes à l’aide de l’arithmétique des pointeurs, utilise des pointeurs de pointeurs pour les arguments de ligne de commande et pour les fonctions qui déplacent un pointeur de la fonction appelante, et appelle des fonctions à travers des pointeurs de fonctions, notamment des fonctions de comparaison pour la fonction standard qsort.

DébutantUnité 4 sur 8 de la formation Formation : Langage C : pointeurs, mémoire et structures

Ce que tu vas apprendre

  1. Représente-toi comment les variables d’un programme C occupent des octets en mémoire, stocke des adresses dans des variables de type pointeur et utilise ces pointeurs pour lire et modifier les variables qu’ils désignent.
    • Se représenter la disposition des variables en mémoire
      Décris la mémoire comme une suite d’octets numérotés, trouve la taille et l’adresse des variables et des éléments de tableaux, et explique la durée de vie de chaque type de variable.
    • Déclarer des pointeurs et obtenir des adresses avec &
      Déclare des variables de type pointeur adaptées, stocke l’adresse d’une variable avec l’opérateur d’adressage et affiche, redirige ou réinitialise un pointeur en toute sécurité.
    • Lire et modifier des valeurs à travers des pointeurs
      Utilise l’opérateur de déréférencement pour lire et modifier l’objet désigné par un pointeur, suis l’exécution du code avec des schémas mémoire et évite de déréférencer des pointeurs invalides.
  2. Écris des fonctions qui modifient les variables de leur fonction appelante à travers des paramètres pointeurs, protège les données en lecture seule avec const et renvoie des pointeurs qui restent valides.
    • Passer des variables par adresse à l’aide de paramètres pointeurs
      Explique pourquoi les fonctions C reçoivent des copies de leurs arguments et utilise des paramètres pointeurs pour modifier les variables d’une fonction appelante, échanger des valeurs, renvoyer plusieurs résultats et lire des données avec scanf.
    • Protéger les données avec const et renvoyer des pointeurs valides
      Indique les données en lecture seule avec const, distingue un pointeur vers un objet constant d’un pointeur constant et renvoie des pointeurs qui restent valides après la fin de la fonction.
  3. Déplace des pointeurs dans des tableaux et des chaînes, explique les liens entre les noms de tableaux et les pointeurs, et garde chaque pointeur dans le tableau auquel il appartient.
    • Effectuer des opérations arithmétiques sur les pointeurs
      Ajoute des entiers à des pointeurs, soustrais et compare des pointeurs vers un même tableau, et détermine quels résultats sont valides.
    • Expliquer les liens entre tableaux et pointeurs
      Utilise la conversion d’un nom de tableau en pointeur et l’identité entre a[i] et *(a + i), et distingue un tableau d’un pointeur.
    • Parcourir des tableaux et des chaînes avec des pointeurs
      Écris des boucles qui parcourent des tableaux et des chaînes terminées par un caractère nul à l’aide de pointeurs, utilise correctement des expressions comme *p++ et respecte les bornes.
  4. Suis plusieurs niveaux d’indirection, permets à des fonctions de modifier un pointeur de leur fonction appelante et utilise des tableaux de pointeurs comme les arguments de ligne de commande.
    • Utiliser des pointeurs de pointeurs
      Déclare et déréférence un pointeur de pointeur, suis-le dans un schéma mémoire et utilise-le pour permettre à une fonction de modifier un pointeur de sa fonction appelante.
    • Utiliser des tableaux de pointeurs et les arguments de ligne de commande
      Construis des tableaux de pointeurs vers des chaînes, traite argc et argv comme des pointeurs et distingue un tableau de pointeurs d’un tableau à deux dimensions.
  5. Stocke des pointeurs de fonctions et appelle les fonctions pointées, passe des fonctions à d’autres fonctions et trie des données de tout type avec la fonction standard qsort et une fonction de rappel de comparaison.
    • Déclarer des pointeurs de fonctions, leur affecter des valeurs et appeler les fonctions pointées
      Écris et lis des déclarations de pointeurs de fonctions, appelle des fonctions à travers ces pointeurs et construis des tables de fonctions qui choisissent un comportement pendant l’exécution du programme.
    • Écrire des fonctions de rappel et trier avec qsort
      Passe des fonctions comme arguments, utilise des pointeurs void pour manipuler des données de tout type et écris des fonctions de comparaison correctes pour qsort.

Pointeurs en C : cours

5 chapitres

1

Mémoire, adresses et valeurs pointées

Une variable possède une valeur, occupe un espace de stockage et a une durée de vie. Un pointeur permet d’atteindre cette variable par son adresse : lire le code consiste donc à suivre à la fois l’adresse contenue dans un pointeur et la valeur qui s’y trouve. Commence par la disposition des octets, puis suis les effets de l’obtention d’une adresse, de la redirection d’un pointeur et d’une écriture à travers celui-ci. De petits schémas mémoire rendent ces opérations visibles avant qu’elles apparaissent dans des fonctions et des boucles.

1.1 Octets, tailles et durées de vie

On peut se représenter la mémoire comme une suite d’octets numérotés. Chaque numéro est une adresse. Un objet comme un int occupe des octets consécutifs, et son adresse désigne son premier octet. Le nom utilisé dans le code source permet au compilateur de trouver cet objet ; le langage n’exige pas qu’une étiquette portant ce nom soit stockée à côté de sa valeur.

Mesurer le stockage avec sizeof

sizeof indique un nombre d’octets et produit une valeur size_t. Utilise %zu pour l’afficher :

int scores[4] = {12, 18, 15, 21};
printf("%zu %zu\n", sizeof scores, sizeof scores[0]);

Avec des int de 4 octets, ce code affiche 16 4. Le premier résultat mesure les quatre éléments ; le second en mesure un seul. sizeof(char) vaut toujours 1. Une disposition courante sur un ordinateur de bureau 64 bits utilise des int de 4 octets, des double de 8 octets et des pointeurs d’objets de 8 octets. D’autres plateformes peuvent être différentes. Même parmi les systèmes 64 bits, long occupe généralement 8 octets sous Linux et macOS, mais 4 sous Windows.

La taille du stockage et la longueur du texte répondent à des questions différentes. Pour char word[20] = "hi";, sizeof word vaut 20, tandis que strlen(word) vaut 2. Le tableau contient toujours 20 éléments, même si sa chaîne n’utilise que deux lettres et un caractère nul de fin de chaîne. Sauf pour les tableaux de longueur variable, ces tailles d’objets sont fixées à la compilation.

Suivre les adresses d’un tableau

Les adresses hexadécimales utilisent les chiffres de 0 à 9, puis de a à f. Le préfixe 0x indique la base 16. Pour un tableau donné à titre d’illustration et commençant à 0x1000, quatre éléments de 4 octets commencent aux adresses suivantes :

ÉlémentPremier octetDernier octet
scores[0]0x10000x1003
scores[1]0x10040x1007
scores[2]0x10080x100B
scores[3]0x100C0x100F

0x100C vaut 4 108 en décimal, douze octets après 0x1000. Les éléments d’un tableau ne comportent aucun espace entre eux. L’ordre et l’espacement des variables déclarées séparément ne sont pas garantis ; les adresses réelles affichées peuvent aussi changer d’une exécution à l’autre. Un compilateur peut garder une valeur dans un registre du processeur lorsqu’aucun stockage adressable n’est nécessaire.

Alignement et ordre des octets

L’alignement restreint les adresses auxquelles les objets d’un type peuvent commencer. Une disposition courante place les int de 4 octets à des adresses multiples de 4 et les double de 8 octets à des adresses multiples de 8. Le compilateur assure un alignement adapté aux objets déclarés.

Le boutisme est l’ordre des octets dans une valeur occupant plusieurs octets. Pour un unsigned int de 4 octets contenant 0x12345678, le stockage petit-boutiste donne les octets 78 56 34 12 des adresses les plus basses aux plus hautes ; le stockage gros-boutiste donne 12 34 56 78. Un unsigned char * peut examiner la représentation d’un objet un octet à la fois. L’ordre des octets ne change pas l’ordre des indices du tableau.

Veiller à la durée de vie de l’objet

Une variable locale ordinaire a une durée de stockage automatique : sa durée de vie couvre l’exécution de son bloc. Les implémentations typiques conservent ces objets dans le cadre de pile d’un appel et réutilisent ensuite cet espace de stockage. Les variables globales, les variables static et les littéraux de chaîne existent pendant toute l’exécution du programme. Enregistrer une adresse ne prolonge pas la durée de vie de son objet.

Points clés

  • sizeof mesure des octets ; strlen compte les caractères qui précèdent le caractère nul de fin de chaîne.
  • Les éléments d’un tableau occupent des emplacements consécutifs dans l’ordre des indices.
  • Les adresses relatives de variables distinctes ne sont pas garanties.
  • L’alignement détermine les adresses de départ adaptées ; le boutisme détermine l’ordre des octets dans une valeur.
  • Une adresse enregistrée ne reste utilisable que tant que son objet existe.

1.2 Stocker et afficher une adresse

Un pointeur stocke une adresse et possède un type qui décrit l’objet auquel il peut accéder. Déclarer un int * réserve un espace pour un pointeur ; cela ne crée pas un int que ce pointeur pourra utiliser. Donne au pointeur une valeur connue dès sa déclaration.

Obtenir l’adresse d’un objet
int count = 42;
int *p = &count;

L’opérateur d’adressage unaire & obtient l’adresse de count. Si count commence à l’adresse fictive 0x1000, p contient 0x1000, tandis que count contient 42. Le pointeur est lui aussi un objet : &p est l’adresse de la zone de stockage distincte qui contient ce pointeur.

Tu peux obtenir l’adresse d’un élément de tableau, comme &scores[2]. Une constante numérique ou le résultat d’un calcul n’est pas un objet stocké dont tu peux obtenir l’adresse de cette façon : &42 et &(count + 1) sont invalides. L’expression binaire a & b utilise le même symbole pour le ET bit à bit ; le nombre d’opérandes indique de quelle opération il s’agit.

Lire chaque déclarateur
DéclarationSignification
int *p, q;Un pointeur et un int ordinaire
int *p, *q;Deux pointeurs vers des int
double *r;Un pointeur vers un double

Les caractères blancs ne changent aucun de ces types. int* p et int *p sont équivalents. Déclarer un pointeur par ligne rend son étoile plus facile à repérer. Sur les ordinateurs de bureau 64 bits courants, un pointeur d’objet occupe 8 octets, mais sa propre taille n’indique pas celle de l’objet qu’il désigne.

Le type pointé compte. Pour double price = 9.5;, utilise double *r = &price;. Initialiser un int * avec &price produit un diagnostic de pointeurs incompatibles. Une conversion explicite qui masque le diagnostic ne rend pas valide un accès à ce double comme un int : le type détermine la manière d’accéder aux octets stockés.

Rediriger un pointeur
int a = 5, b = 8;
int *p = &a;
p = &b;

La dernière affectation remplace l’adresse dans p. Elle ne déplace aucun des deux entiers et ne copie pas la valeur d’un entier dans l’autre. Maintenant, p pointe vers b ; a vaut toujours 5 et b vaut toujours 8. Plusieurs variables de type pointeur peuvent aussi stocker la même adresse.

Utiliser un état vide connu

Un pointeur local non initialisé possède une valeur indéterminée ; on parle parfois de pointeur sauvage. Ne l’examine pas et ne le déréférence pas. Initialise-le avec l’adresse d’un objet valide ou avec NULL, disponible dans des fichiers d’en-tête comme <stddef.h> et <stdio.h> :

int *pending = NULL;
if (pending == NULL) {
    pending = &count;
}

Un pointeur nul ne désigne aucun objet. !pending est un autre test de nullité. Ces tests n’ont de sens que pour des pointeurs initialisés ; ils ne valident ni une adresse quelconque ni un pointeur dont l’objet n’existe plus.

Afficher un pointeur d’objet
printf("%p\n", (void *)p);

%p attend un void * : effectue donc la conversion explicite lorsque tu utilises un pointeur d’objet typé. Sa représentation à l’affichage est choisie par l’implémentation et est généralement hexadécimale. %d et %x attendent des arguments entiers de types précis et ne peuvent pas remplacer %p.

Points clés

  • Initialise un pointeur avec une adresse valide ou NULL.
  • &count est une adresse ; count est l’entier stocké.
  • Dans une déclaration, chaque nom de pointeur a besoin de son propre *.
  • Les types des pointeurs doivent convenir aux objets auxquels ils donnent accès.
  • p = &b modifie le pointeur sans toucher aux entiers.
  • Affiche un pointeur d’objet avec %p et un argument void *.

1.3 Suivre un pointeur pour lire ou écrire

L’opérateur de déréférencement unaire * suit un pointeur jusqu’à son objet. Si p pointe vers x, *p désigne x lui-même. Lire *p lit cet objet ; effectuer une affectation à *p le modifie.

Suivre une écriture à travers une adresse
int x = 10;
int *p = &x;
*p = *p + 5;
int y = *p * 2;
printf("%d %d %d\n", x, *p, y);

D’abord, p stocke l’adresse de x. À droite de l’affectation, *p lit 10. Ajouter 5 donne 15, et le *p de gauche stocke 15 dans x. La ligne suivante multiplie la valeur actualisée par 2 : y devient donc 30. La sortie est 15 15 30.

Le contexte distingue les étoiles : int *p déclare un pointeur, l’opérateur unaire dans *p accède à un objet, et *p * 2 contient aussi une multiplication binaire. Pour un objet valide x, *&x désigne x ; pour un pointeur vers un objet valide, &*p redonne la valeur du pointeur.

Distinguer les adresses des contenus

Commence chaque trace avec deux lignes d’information : l’adresse contenue dans chaque pointeur et la valeur contenue dans chaque entier. Supposons que p pointe vers a, qui contient 3, et que q pointe vers b, qui contient 8.

Instruction considérée à partir de cet état initialEffet
*p = *q;Copie 8 dans a ; les pointeurs gardent leurs cibles
p = q;Fait pointer p vers b ; les entiers gardent leurs valeurs

Un schéma mémoire utilise une case pour chaque objet. Indique le nom et l’adresse de l’objet sur sa case, place sa valeur actuelle à l’intérieur et trace une flèche du pointeur vers sa cible. Une affectation d’adresse déplace une flèche ; une affectation à l’objet pointé modifie le contenu de la case cible.

Après p = q, les pointeurs sont des alias de b. Si tu exécutes ensuite *p = 20;, lire *q ou b donne 20. Copier une adresse crée un autre chemin vers le même objet, et non un autre entier.

Vérifier la validité d’un déréférencement

Un pointeur utilisé pour accéder à un objet doit désigner un objet dont la durée de vie n’est pas terminée et d’un type adapté ; pour écrire, l’objet doit aussi être modifiable. Les pointeurs nuls ne désignent aucun objet. Les pointeurs non initialisés n’ont aucune cible connue utilisable. Un pointeur devient pendant lorsque la durée de vie de sa cible prend fin.

Un accès à travers un pointeur invalide est un comportement indéfini : C n’impose aucune exigence sur le résultat. Le programme peut s’arrêter avec une erreur de segmentation, corrompre des données ou sembler fonctionner normalement. Une exécution apparemment réussie ne permet pas d’établir que l’accès était valide.

Pour un pointeur qui peut légitimement être nul, effectue un test avant d’y accéder :

if (p != NULL) {
    *p = 0;
}

Affecter NULL à un pointeur dont tu n’as plus besoin peut éviter une utilisation accidentelle ultérieure de cette variable. Cela ne réinitialise pas les autres alias. Suis séparément la durée de vie de l’objet.

Mettre des parenthèses autour de la valeur visée

L’opérateur postfixé ++ est prioritaire sur l’opérateur unaire *. Ainsi, *p++ signifie *(p++) : il utilise l’ancienne valeur du pointeur pour atteindre une valeur et avance le pointeur. (*p)++ incrémente la valeur pointée et laisse le pointeur en place. *p + 1 lit la valeur et ajoute 1 sans stocker le résultat. Ce sont trois actions différentes.

Points clés

  • *p désigne l’objet atteint à travers p.
  • Copier une adresse crée des alias ; copier *p copie une valeur.
  • Un déréférencement exige un objet dont la durée de vie n’est pas terminée et un type d’accès adapté.
  • Un comportement indéfini ne garantit aucun résultat, pas même un plantage.
  • *p++ avance le pointeur ; (*p)++ incrémente l’objet pointé.
2

Permettre aux fonctions de manipuler les données de leur fonction appelante

Une fonction reçoit une copie de chaque argument, y compris d’un argument pointeur. Cette copie de l’adresse peut néanmoins atteindre un objet appartenant à la fonction appelante, ce qui rend possibles les échanges de valeurs, les fonctions de lecture et les résultats multiples. L’interface de la fonction doit aussi indiquer si elle modifie les données. Suis l’adresse dans l’appel, distingue les modifications locales des écritures effectuées à travers cette adresse et vérifie quels objets existeront encore lors de l’utilisation d’un pointeur renvoyé.

2.1 Modifier les variables de la fonction appelante à travers des paramètres

En C, chaque argument est passé par valeur. Un paramètre est une variable locale initialisée à partir de l’argument. Passer une adresse ne change pas cette règle : la fonction reçoit une copie de l’adresse et cette copie permet d’atteindre l’objet de la fonction appelante.

Comparer deux fonctions de remise à zéro
void reset_copy(int n) {
    n = 0;
}

void reset(int *n) {
    *n = 0;
}

Pour int score = 12;, reset_copy(score) ne modifie que sa variable locale n. score reste égal à 12. L’appel reset(&score) copie l’adresse de score dans le paramètre pointeur n ; l’affectation à *n donne ensuite la valeur 0 à score. Cette technique est souvent appelée passage par adresse, même si le langage continue de passer les arguments par valeur.

L’adresse dans l’appel et le déréférencement dans le corps de la fonction ont des rôles différents. &score indique à la fonction où se trouve l’objet. *n = 0 lui indique ce qu’elle doit y stocker. L’appel reset(score) fournit un entier là où un pointeur est requis et déclenche un diagnostic du compilateur.

Suivre un échange de valeurs
void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}

/* Inside a calling function: */
int x = 3, y = 8;
swap(&x, &y);
  1. a pointe vers x et b pointe vers y.
  2. t conserve la valeur initiale de x, soit 3.
  3. *a = *b écrit 8 dans x.
  4. *b = t écrit la valeur 3 conservée dans y.

Les deux variables de la fonction appelante changent. Une version qui prend de simples paramètres int a, int b n’échange que des copies. Une version avec des pointeurs qui exécute int *t = a; a = b; b = t; échoue elle aussi à échanger les entiers : elle ne redirige que les paramètres pointeurs locaux.

Fournir plusieurs résultats

Une instruction de retour fournit une valeur, mais des paramètres pointeurs peuvent transmettre des résultats supplémentaires :

void divide(int total, int size, int *q, int *r) {
    *q = total / size;
    *r = total % size;
}

/* size must be positive for these example inputs. */
int q, r;
divide(17, 5, &q, &r);

La division entière donne 3 à q et 2 à r. La fonction appelante fournit deux objets distincts dans lesquels l’écriture est possible. Une autre interface utile renvoie un code indiquant le succès ou l’échec et n’écrit un résultat qu’en cas de succès ; les fonctions appelantes vérifient ce code avant d’utiliser le résultat.

Reconnaître le même schéma dans les appels de bibliothèque

scanf("%d", &age) a besoin de l’adresse de age, car elle y stocke l’entier analysé. Passer age fournit une valeur au lieu du int * requis, ce qui produit un comportement indéfini. Vérifie que scanf renvoie 1 avant d’utiliser la valeur qui vient d’être lue.

Pour le texte, char name[20]; scanf("%19s", name); passe un pointeur vers le premier caractère du tableau. La largeur autorise au maximum 19 caractères en entrée et laisse de la place pour '\0'. L’expression du tableau fournit déjà le pointeur vers un caractère requis.

Un paramètre tableau reçoit de même une adresse, et non une copie du bloc d’éléments. Dans void zero(int *a, size_t n), les affectations à a[i] modifient le tableau de la fonction appelante. Passer l’adresse ne copie qu’un pointeur, ce qui permet aussi aux fonctions en lecture seule d’examiner de grands tableaux sans copier chaque élément.

Points clés

  • C copie chaque argument, y compris les adresses.
  • Une écriture à travers un paramètre pointeur peut modifier l’objet de la fonction appelante.
  • Une fonction qui échange correctement deux entiers échange *a et *b à l’aide d’une valeur conservée.
  • Les paramètres de sortie nécessitent des objets valides et modifiables fournis par la fonction appelante.
  • scanf reçoit des adresses et indique le nombre de conversions réussies.
  • Les paramètres tableaux donnent accès aux éléments appartenant à la fonction appelante.

2.2 Accès avec const et valeurs de retour valides

Une interface utilisant un pointeur doit préciser deux choses : si la fonction peut écrire à travers ce pointeur et combien de temps la zone de stockage pointée reste valide. const répond à la première question. La durée de vie de l’objet répond à la seconde.

Repérer la restriction imposée par const
DéclarationPeut-on rediriger p ?Peut-on affecter une valeur à travers *p ?
const int *p = &x;OuiNon
int *const p = &x;NonOui
const int *const p = &x;NonNon

Un pointeur vers un objet constant limite ce chemin d’accès. Si x a été défini comme un simple int x = 5;, x = 6; reste autorisé même lorsqu’un const int * pointe vers cet objet. Si l’objet lui-même a été défini avec const, tenter de le modifier à travers n’importe quel pointeur constitue un comportement indéfini.

Un pointeur constant conserve une adresse. Il doit être initialisé et son adresse ne peut pas être remplacée ensuite. L’objet situé à cette adresse peut toujours changer si son type le permet. Lis la déclaration à partir du nom en allant vers l’extérieur : int *const p rend p constant ; const int *p protège le int atteint à travers p.

Utilise un paramètre en lecture seule comme const char *s lorsqu’une fonction ne fait qu’examiner une chaîne. Un simple char * peut être passé à ce paramètre sans conversion explicite. La fonction peut avancer son s local, mais une affectation à travers *s est rejetée. Pour revenir à un type qui permet l’écriture, il ne suffit pas de supprimer un avertissement du compilateur : l’objet sous-jacent doit réellement être modifiable.

Renvoyer une position dans la zone de stockage de la fonction appelante
int *find(int *a, size_t n, int target) {
    for (size_t i = 0; i < n; i++) {
        if (a[i] == target) return &a[i];
    }
    return NULL;
}

Pour int v[] = {4, 9, 2, 9};, find(v, 4, 9) renvoie &v[1]. La fonction se termine, mais v appartient à la fonction appelante et y existe toujours. Après avoir vérifié que le résultat diffère de NULL, la fonction appelante peut calculer found - v pour obtenir l’indice 1 ou effectuer l’affectation *found = 0 pour modifier l’élément. Une recherche de 7 renvoie NULL, qui ne doit pas être déréférencé. strchr et strstr utilisent la même convention : un pointeur nul signifie que l’élément n’a pas été trouvé.

Rejeter les adresses de variables locales dont la durée de vie est terminée
int *bad(void) {
    int result = 42;
    return &result;
}

result a une durée de stockage automatique. Sa durée de vie prend fin lorsque l’appel se termine, laissant à la fonction appelante un pointeur pendant. Le compilateur peut émettre un avertissement et tout accès ultérieur à travers ce pointeur constitue un comportement indéfini. Le fait que les anciens octets soient visibles lors d’une exécution ne prolonge pas la durée de vie de l’objet.

Le même problème peut se produire à l’accolade fermante d’un bloc intérieur. Un pointeur extérieur ne peut pas prolonger la durée de vie d’une variable intérieure dont il a enregistré l’adresse.

Choisir un stockage qui dure suffisamment longtemps

Renvoie une valeur int lorsque la fonction appelante n’a besoin que d’un résultat entier. Pour plusieurs résultats, laisse la fonction appelante fournir une zone de stockage à remplir. Un pointeur renvoyé peut aussi désigner le tableau de la fonction appelante, un littéral de chaîne accessible par un const char * ou un objet static.

Une variable locale statique est initialisée une fois et existe pendant toute l’exécution du programme, mais tous les appels la partagent. Si un appel ultérieur y stocke une nouvelle valeur, tous les pointeurs renvoyés précédemment voient cette modification. Le stockage statique résout le problème de durée de vie sans donner à chaque appel son propre objet résultat indépendant.

Points clés

  • const int * restreint les écritures à travers ce pointeur, tandis que int *const fixe l’adresse.
  • Un objet défini avec const ne peut pas être modifié de façon valide.
  • Les pointeurs vers le tableau de la fonction appelante peuvent rester valides après le retour d’une fonction si le tableau existe toujours.
  • Vérifie un résultat de recherche qui peut être nul avant tout accès ou toute soustraction.
  • Renvoyer l’adresse d’une variable locale automatique laisse un pointeur pendant.
  • Un objet résultat statique est partagé par tous les appels.
3

Parcourir des tableaux et des chaînes

L’arithmétique des pointeurs relie l’adresse d’un élément à sa position dans un tableau. Le type pointé fournit la taille du pas, tandis que le tableau fournit la plage autorisée. Utilise ce modèle pour lire les expressions avec indice, passer une tranche à une fonction et construire des boucles qui s’arrêtent avant un accès invalide. Les chaînes ajoutent une seconde règle d’arrêt : leur caractère nul de fin marque la fin du texte, même lorsque la zone de stockage contient davantage d’octets.

3.1 Déplacements par élément et bornes du tableau

Ajouter un entier à un pointeur le déplace par éléments du type pointé. Si p pointe vers l’élément i d’un tableau, p + n pointe vers l’élément i + n, à condition que la position obtenue soit dans le tableau ou juste après celui-ci.

Appliquer une seule fois le facteur de taille à l’adresse

À partir de l’adresse fictive 0x1000, ajouter 3 donne les résultats suivants :

Type de pointeurTaille d’un élémentAdresse après p + 3
int *4 octets0x100C
double *8 octets0x1018
char *1 octet0x1003

Le compilateur applique le facteur de taille. Écrire p + 3 * sizeof *p l’applique deux fois et mène généralement au mauvais élément. Utilise un unsigned char * lorsque le pas souhaité correspond réellement à un octet de la représentation d’un objet.

Pour int a[6] = {10, 20, 30, 40, 50, 60}; et int *p = &a[1];, *(p + 2) lit a[3], qui vaut 40. L’affectation p += 3; déplace le pointeur lui-même vers a[4], qui contient 50. De là, *(p - 1) lit 40. p++, p--, p += n et p -= n se déplacent tous par éléments.

Soustraire des positions pour compter les éléments

&a[5] - &a[1] vaut 4 et inverser les opérandes donne -4. Le résultat compte déjà les éléments ; ne le divise pas par sizeof(int). Son type signé est ptrdiff_t, déclaré dans <stddef.h>, et %td permet de l’afficher.

Ne soustrais que des pointeurs vers un même tableau, y compris sa position juste après le dernier élément, avec un résultat représentable dans ptrdiff_t. Cette opération décrit des positions dans ce tableau. Soustraire les adresses de variables sans lien constitue un comportement indéfini, même si les nombres affichés sont proches par hasard.

Utiliser la position juste après le dernier élément comme borne

Pour six éléments, a + 6 est un pointeur juste après le dernier élément valide. Il peut servir de borne et intervenir dans une soustraction valide entre pointeurs d’un même tableau, mais il ne peut pas être déréférencé. a + 7 et a - 1 constituent déjà un comportement indéfini dès leur formation ; éviter un déréférencement ultérieur ne corrige pas le problème.

for (int *p = a; p < a + 6; p++) {
    printf("%d\n", *p);
}

Le corps s’exécute pour les indices de 0 à 5. Après la dernière incrémentation, p est égal à a + 6 et le test suivant arrête la boucle. Utiliser <= permettrait au corps d’accéder à cette position qui ne correspond pas à un élément.

Pour les règles arithmétiques, un objet seul se comporte comme un tableau d’un élément. Tu peux former &x + 1 ; tu ne peux pas l’utiliser pour lire une prétendue variable suivante.

Ne comparer que des positions qui ont un sens

Les opérateurs d’ordre comme < comparent des positions dans un même tableau. Ne compare pas l’ordre de pointeurs vers des objets sans lien. Les opérateurs d’égalité et d’inégalité permettent de comparer des pointeurs de types compatibles et de tester un pointeur par rapport à un pointeur nul.

Tu ne peux pas additionner deux pointeurs, multiplier des pointeurs ou effectuer des opérations arithmétiques en C standard sur un void * ou un pointeur de fonction. Pour trouver le milieu d’une plage [p, q) située dans un même tableau, utilise p + (q - p) / 2 : soustrais les positions pour obtenir un nombre d’éléments, divise ce nombre, puis avance à partir de p.

Points clés

  • Ajouter un entier déplace un pointeur par éléments de son type pointé.
  • Une différence de pointeurs vers un même tableau compte déjà les éléments et a le type ptrdiff_t.
  • Les pointeurs juste après le dernier élément peuvent marquer une borne, mais ne peuvent pas être déréférencés.
  • Former un pointeur avant le tableau ou au-delà de la position juste après son dernier élément est invalide.
  • Pour les comparaisons d’ordre, utilise des positions dans un même tableau.
  • Soustrais les positions avant de calculer un milieu.

3.2 Expressions de tableau et paramètres pointeurs

Un tableau est un bloc d’éléments. Un pointeur est un objet distinct qui contient une adresse. Leur lien étroit provient d’une conversion automatique qui se produit dans la plupart des expressions : ce ne sont pas deux objets de même nature.

Convertir une expression de tableau
int a[5] = {3, 1, 4, 1, 5};
int *p = a;

Ici, a est converti en pointeur vers a[0]. Cette conversion de tableau en pointeur rend l’initialiseur équivalent à &a[0]. Lire *p ou a[0] donne 3 dans les deux cas.

C définit l’indexation par l’arithmétique des pointeurs : a[i] signifie *(a + i). Par conséquent, p[3] fonctionne aussi et donne 1. &a[i] et a + i désignent le même élément. Même 3[a] a le même sens défini que a[3], bien que l’écriture habituelle soit beaucoup plus claire.

Savoir quand le tableau entier reste un tableau

La conversion ne se produit pas pour les opérandes de sizeof ou de l’opérateur unaire &. Elle ne se produit pas non plus pour typeof et typeof_unqual en C23, ni lorsqu’un littéral de chaîne initialise un tableau de caractères.

Expression, avec des int de 4 octets et un pointeur de 8 octetsRésultat
sizeof a20 octets pour les cinq éléments
sizeof p8 octets pour l’objet pointeur
sizeof a / sizeof a[0]5 éléments

Tu ne peux pas affecter une nouvelle adresse à un tableau ni incrémenter son nom : a = p; et a++; sont invalides. Tu peux rediriger p, par exemple avec p = a + 2;. Copie le contenu du tableau avec une boucle sur ses éléments ou un appel adapté à memcpy.

Suivre le type ajusté du paramètre
int sum(const int *values, size_t n) {
    int total = 0;
    for (size_t i = 0; i < n; i++) total += values[i];
    return total;
}

La fonction appelante fournit à la fois une adresse et un nombre d’éléments. Pour le tableau précédent, sum(a + 2, 3) parcourt les indices initiaux 2, 3 et 4, et additionne 4, 1 et 5 pour renvoyer 10. Dans la fonction, l’indice 0 est relatif au pointeur de départ fourni.

Dans une liste de paramètres de fonction, int a[], int a[10] et int *a déclarent tous un paramètre pointeur. L’écriture simple [10] ne fournit aucune borne vérifiée et ne transmet aucune longueur. Dans une telle fonction, sizeof a mesure un pointeur. L’expression sizeof a / sizeof a[0] donnerait 2 sur un système avec un pointeur de 8 octets et un int de 4 octets, quelle que soit la longueur réelle du tableau de la fonction appelante.

Distinguer un pointeur vers un élément d’un pointeur vers un tableau

a est converti en int *, tandis que &a a le type int (*)[5], un pointeur vers le tableau entier de cinq éléments. Les deux désignent le début de la même zone de stockage, mais l’arithmétique suit leurs types différents. a + 1 avance d’un int, soit 4 octets dans cette disposition. &a + 1 avance d’un tableau entier, soit 20 octets, jusqu’à la position juste après ce tableau.

Une adresse de départ affichée ne peut pas révéler cette différence de type. Lis la déclaration et garde le type pointé à côté du pointeur lorsque tu suis une expression.

Points clés

  • La plupart des expressions de tableau sont converties en pointeur vers le premier élément.
  • a[i] est défini comme *(a + i).
  • sizeof a ne mesure le tableau entier que là où a est réellement un tableau.
  • Un paramètre tableau est ajusté en pointeur ; passe sa longueur utilisable séparément.
  • &a pointe vers un tableau entier : son arithmétique utilise donc un pas plus grand.

3.3 Boucles avec des pointeurs sur des nombres et du texte

Une boucle avec un pointeur a besoin d’une position courante et d’une règle d’arrêt. Un tableau parcouru selon un nombre d’éléments utilise un pointeur de fin ; une chaîne utilise son '\0' final. Choisis la forme qui permet de voir le plus facilement les éléments parcourus.

Parcourir une plage de longueur connue
int a[] = {3, 1, 4, 1, 5};
int total = 0;
for (const int *p = a; p < a + 5; p++) {
    total += *p;
}

Les totaux successifs sont 3, 4, 8, 9 et 14. Une boucle à indice qui ajoute a[i] pour les indices inférieurs à 5 donne le même résultat. Les compilateurs optimisants modernes produisent souvent un code similaire pour ces formes ; la syntaxe des pointeurs ne garantit à elle seule aucun gain de vitesse.

Lire avant ou après le déplacement

Avec p sur le 3 dans {3, 1, 4}, int v = *p++; affecte 3 à v et laisse p sur le 1. L’incrémentation postfixée fournit l’ancienne valeur du pointeur à * et met le pointeur à jour. *++p se déplace d’abord vers l’élément suivant, puis le lit. (*p)++ modifie l’élément courant et ne déplace pas le pointeur.

S’arrêter au caractère nul de fin de chaîne
size_t my_strlen(const char *s) {
    const char *p = s;
    while (*p != '\0') p++;
    return (size_t)(p - s);
}

Pour "pointer", p s’arrête sur le caractère nul de fin de chaîne, à sept positions du début. Le résultat exclut ce caractère. Pour compter des caractères, on effectue le même parcours : parcourir "mississippi" et incrémenter un compteur chaque fois que *p == 's' compte quatre occurrences parmi onze lettres.

Un pointeur vers le milieu d’une chaîne donne accès à son suffixe. Avec const char *msg = "hello world";, puts(msg + 6) affiche "world". Les caractères précédents restent à leur place.

Copier aussi le caractère nul de fin de chaîne
char destination[4];
const char *src = "cat";
char *dst = destination;
while ((*dst++ = *src++) != '\0') {
}

Chaque itération affecte un caractère, teste la valeur affectée et avance les deux pointeurs. La dernière itération copie '\0' : la destination est donc une chaîne complète. Les deux pointeurs se trouvent alors juste après leurs caractères nuls de fin de chaîne. La zone de stockage doit contenir la longueur du texte plus un octet ; omettre cet octet supplémentaire provoque un débordement de tampon. Ce schéma de copie exige aussi que les plages source et destination ne se chevauchent pas.

Inverser depuis les deux extrémités

Pour une chaîne modifiable, obtiens d’abord sa longueur. Si la longueur est nulle, arrête-toi avant de former un pointeur vers le dernier caractère. Sinon, affecte left = s et right = s + length - 1. Tant que left < right, échange les caractères, incrémente left et décrémente right. Pour "stressed", quatre échanges produisent "desserts" ; le caractère nul de fin reste à sa place initiale.

La vérification de la chaîne vide est importante, car soustraire 1 à son pointeur de départ formerait un pointeur situé avant le tableau.

Choisir une zone de stockage de caractères modifiable

char word[] = "hello"; crée un tableau modifiable de six octets, caractère nul de fin compris. const char *word = "hello"; pointe vers un littéral de chaîne à durée de stockage statique. Modifier un littéral de chaîne constitue un comportement indéfini, même si une déclaration utilise char * sans const. Utilise const char * pour que les écritures accidentelles provoquent une erreur de compilation.

Une boucle sur un tableau doit s’arrêter avant la position juste après le dernier élément, et un parcours de chaîne doit effectivement rencontrer un caractère nul de fin dans la zone de stockage accessible. Un pointeur ne contient ni longueur ni garantie que les octets forment une chaîne.

Points clés

  • Une plage de longueur connue se termine à un pointeur juste après le dernier élément ; une chaîne se termine à son caractère nul.
  • *p++ utilise l’ancienne position et avance le pointeur.
  • La copie d’une chaîne inclut le caractère nul de fin et nécessite sa longueur plus un octet.
  • Vérifie si une chaîne est vide avant de former le pointeur vers son dernier caractère.
  • Les tableaux de caractères initialisés à partir de texte sont modifiables ; les littéraux de chaîne ne le sont pas.
  • Un pointeur seul ne contient aucune longueur ni garantie de terminaison.
4

Suivre et mettre à jour un autre pointeur

Un pointeur peut lui-même être l’objet atteint par une adresse. Suivre deux niveaux permet à une fonction de remplacer le pointeur de sa fonction appelante et à un programme de parcourir un tableau d’adresses de chaînes. Garde visible le type de chaque niveau : un pointeur vers un entier, un pointeur vers ce pointeur et un pointeur vers une ligne entière représentent des dispositions différentes. Suis l’objet atteint par chaque déréférencement avant de déterminer ce qu’une affectation modifiera.

4.1 Deux niveaux d’indirection

Un pointeur est un objet stocké : il possède donc une adresse qu’un autre pointeur peut contenir. Un pointeur de pointeur ajoute une étape entre l’expression et les données finales.

Suivre les flèches une par une
int x = 7;
int *p = &x;
int **pp = &p;

Utilisons des adresses fictives : x, à l’adresse 0x2000, contient 7 ; p, à l’adresse 0x3000, contient 0x2000 ; pp, à l’adresse 0x4000, contient 0x3000. Évaluer *pp permet d’atteindre p, dont la valeur est de type int *. Évaluer **pp suit ce pointeur jusqu’à x, un int.

ExpressionTypeObjet ou valeur atteint
ppint **Adresse de p
*ppint *Pointeur stocké dans p
**ppintEntier stocké dans x

&p possède un niveau de pointeur de plus que p ; chaque déréférencement retire un niveau. Initialiser int **pp avec &x crée une incompatibilité, car &x est de type int *.

Choisir le niveau de l’affectation

Avec un autre entier y dont la durée de vie n’est pas terminée, **pp = 9; modifie x. L’affectation *pp = &y; modifie le pointeur p : les utilisations ultérieures de **pp atteignent donc y. Elle laisse x à sa valeur actuelle. Si q est un autre int *, pp = &q; ne modifie que le pointeur extérieur : sa première flèche atteint alors q au lieu de p.

Ne te contente pas de compter les étoiles : vérifie aussi l’objet à chaque niveau. Avant d’évaluer **pp, le chemin vers l’objet pointeur et celui qui mène de cet objet à l’entier doivent tous deux être valides.

Déplacer un pointeur de chaîne de la fonction appelante
void skip_spaces(const char **s) {
    while (**s == ' ') {
        (*s)++;
    }
}

/* In the caller: */
const char *text = "   hi";
skip_spaces(&text);
puts(text);

La fonction appelante passe l’adresse de sa variable de type pointeur. *s désigne donc text ; **s lit le caractère courant. Chaque (*s)++ avance text d’un caractère. Après trois itérations, le pointeur désigne 'h' et puts affiche "hi". La fonction nécessite un pointeur valide vers un pointeur qui désigne une chaîne terminée par un caractère nul.

Une version qui prend const char *s et incrémente s ne modifie qu’une copie locale. Les parenthèses de (*s)++ sont elles aussi indispensables : *s++ incrémente le pointeur local extérieur au lieu du pointeur de caractères de la fonction appelante. Dans const char **s, les caractères sont accessibles en lecture seule par ce chemin, mais le pointeur de la fonction appelante peut être déplacé.

Lire un pointeur de sortie d’une fonction de bibliothèque

strtol de <stdlib.h> utilise un paramètre de sortie de type char ** :

char *end;
long number = strtol("42abc", &end, 10);

La fonction renvoie 42 et stocke dans end un pointeur vers le premier caractère non utilisé, qui désigne maintenant "abc". Si le pointeur de fin est égal au pointeur de départ, aucun chiffre n’a été converti. Une analyse numérique entièrement vérifiée traite aussi les erreurs de dépassement de plage ; ici, le rôle du pointeur est d’indiquer la position d’arrêt.

Points clés

  • int **pp peut contenir l’adresse d’un objet int *.
  • Chaque déréférencement suit un niveau de pointeur.
  • **pp modifie l’entier final ; *pp peut modifier le pointeur intermédiaire.
  • Pour mettre à jour un pointeur de la fonction appelante, il faut disposer de son adresse.
  • strtol utilise char ** pour renvoyer la position du premier caractère non utilisé.

4.2 Tableaux de pointeurs vers des chaînes et tableaux rectangulaires

Un tableau peut stocker des pointeurs comme éléments. Un tableau à deux dimensions stocke quant à lui des lignes complètes comme éléments. Les deux acceptent deux indices, mais leur stockage et les types de pointeurs obtenus après conversion diffèrent.

Stocker les adresses de chaînes distinctes
const char *days[] = {"Mon", "Tue", "Wed"};

days contient trois pointeurs de caractères. Chacun pointe vers un littéral de chaîne distinct. days[1] est le pointeur vers "Tue", et days[1][0] lit 'T'. Avec des pointeurs d’objets de 8 octets, sizeof days vaut 24, quelles que soient les longueurs des chaînes. Les chaînes elles-mêmes occupent une zone de stockage ailleurs.

Les longueurs peuvent différer, ce qui forme une collection irrégulière. Pour réordonner la liste, on peut échanger les éléments pointeurs sans copier le texte. L’affectation days[0] = "Thursday"; est autorisée : l’élément pointeur peut changer même si les caractères qu’il désigne sont accessibles en lecture seule.

Dans la plupart des expressions, days est converti en pointeur vers son premier élément. Comme un élément est un const char *, le type obtenu est const char ** :

for (const char **d = days; d < days + 3; d++) {
    puts(*d);
}

La boucle avance d’un élément pointeur par itération. *d récupère le pointeur de caractères dont puts a besoin.

Lire les chaînes de la ligne de commande

int main(int argc, char *argv[]) reçoit le même type de paramètre que char **argv. argc est positif ou nul et compte les chaînes disponibles. Lorsqu’il est positif, argv[0] représente le nom du programme ou une chaîne vide si ce nom n’est pas disponible. Les arguments de l’utilisateur occupent les indices de 1 à argc - 1, et argv[argc] est un pointeur nul.

Pour une invocation typique ./greet Ada 42, argc vaut 3, argv[1] pointe vers "Ada" et argv[2] pointe vers "42". Le dernier argument est du texte jusqu’à ce qu’une conversion comme strtol le lise. argv[1][0] vaut 'A'. Vérifie argc > 2 avant d’utiliser argv[2].

Une boucle qui affiche les arguments de l’utilisateur peut utiliser for (int i = 1; i < argc; i++) puts(argv[i]);. Pour parcourir les arguments à partir de argv + 1 jusqu’à un pointeur nul, vérifie d’abord argc > 0 ; si argc vaut zéro, aucune plage d’arguments utilisateur ne permet de commencer ainsi. Les chaînes des arguments sont modifiables, contrairement aux littéraux de chaîne.

Suivre des lignes complètes

int grid[3][4] contient douze entiers rangés par lignes : toute la ligne 0, puis la ligne 1 et enfin la ligne 2. Avec des int de 4 octets, chaque ligne occupe 16 octets. grid[2][1] se trouve 36 octets après grid[0][0] : les deux lignes complètes représentent 32 octets et la colonne en ajoute 4.

Déclaration ou expressionSignification
int (*rows)[4]Pointeur vers une ligne de quatre entiers
int *ptrs[4]Tableau de quatre pointeurs vers des entiers
grid après conversionint (*)[4]

grid + 1 avance d’une ligne. Une fonction qui accepte ce tableau peut utiliser int g[][4] ou int (*g)[4], avec un nombre de lignes distinct. Elle ne peut pas utiliser int ** : la grille ne contient aucun pointeur de ligne à déréférencer. Une conversion explicite ne peut pas créer ces pointeurs manquants.

De même, char names[3][6] possède dix-huit emplacements de caractères dans des lignes de taille fixe. const char *names[3] possède trois emplacements de pointeurs qui peuvent désigner des chaînes de longueurs différentes.

Points clés

  • Un tableau de pointeurs vers des chaînes stocke des adresses ; le texte se trouve ailleurs.
  • Un tableau de pointeurs est converti en pointeur vers son premier élément pointeur.
  • argv[argc] est nul ; vérifie le nombre d’arguments avant de lire une chaîne précise.
  • Un tableau à deux dimensions stocke des lignes contiguës et ne contient aucun objet pointeur de ligne.
  • int (*)[4] et int ** décrivent des dispositions de stockage différentes.
  • La seule présence de deux indices n’établit pas que deux objets ont le même type.
5

Choisir des fonctions et fournir des fonctions de rappel

Un pointeur de fonction permet à un programme de choisir une opération pendant son exécution. Son type indique l’appel autorisé, et un paramètre de fonction de rappel permet à une autre fonction de demander cette opération au besoin. Utilise d’abord ces idées dans une petite table de fonctions arithmétiques, puis avec qsort, où la fonction de rappel doit interpréter correctement les adresses des éléments et renvoyer un ordre cohérent. Le tri de pointeurs vers des chaînes réunit les pointeurs de fonctions et deux niveaux de pointeurs d’objets.

5.1 Déclarations et appels de pointeurs de fonctions

Un pointeur de fonction stocke une référence à une fonction qui pourra être appelée plus tard. Son type comprend le type de retour et les types des paramètres. Ces types doivent correspondre à la fonction utilisée à travers le pointeur.

Mettre le nom du pointeur entre parenthèses
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

int (*op)(int, int) = add;

Pars de op : c’est un pointeur ; l’élément pointé est une fonction qui prend deux int et renvoie un int. Sans les parenthèses, int *op(int, int); déclare une fonction qui renvoie un pointeur vers un entier. Les parenthèses changent le sens de la déclaration.

Dans cet initialiseur, un nom de fonction est converti en pointeur de fonction. op = &add; équivaut à op = add;. op(6, 3) et (*op)(6, 3) appellent tous deux add et renvoient 9. L’affectation op = sub; fait renvoyer 3 au même appel. Dans l’expression d’appel, rien n’a changé à part la fonction stockée dans le pointeur.

Garder une signature compatible

Un pointeur de type int (*)(int, int) ne peut pas appeler en toute sécurité une fonction qui prend et renvoie un double. Par exemple, lui affecter sqrt produit un diagnostic de types incompatibles. Convertir explicitement le pointeur de fonction pour supprimer un diagnostic ne rend pas valide un appel incompatible ; le comportement est indéfini.

Le C standard traite séparément les pointeurs d’objets et les pointeurs de fonctions. void * est le type pointeur générique d’objet ; il ne permet pas de stocker un pointeur de fonction de façon portable. Les pointeurs de fonctions acceptent les tests d’égalité et d’inégalité, mais pas l’arithmétique des pointeurs.

Un pointeur de fonction nul signifie qu’aucune fonction ne peut être appelée. Lorsqu’une interface autorise cet état, vérifie-le :

if (op != NULL) {
    int result = op(6, 3);
    printf("%d\n", result);
}

Appeler une fonction à travers un pointeur de fonction nul constitue un comportement indéfini.

Nommer le type une fois
typedef int (*binop)(int, int);
binop ops[] = {add, sub, mul};

Le typedef donne un nom à un type pointeur. binop selected = mul; déclare un pointeur ; binop pick(char symbol); déclare une fonction qui en renvoie un. Sans le typedef, cette déclaration de retour serait int (*pick(char symbol))(int, int);.

Un tableau de pointeurs de fonctions est une table de dispatch. ops[2](6, 3) sélectionne mul et renvoie 18. Pour un indice de sélection signé i, vérifie i >= 0 && i < 3 avant d’évaluer ops[i]. Accéder à un indice situé hors de la table est invalide, même si toutes les fonctions stockées peuvent par ailleurs être appelées.

Lire les déclarations plus longues à partir du nom

Commence par le nom et résous les parenthèses et les parties postfixées avant de continuer vers l’extérieur. Dans int (*table[3])(int, int), table[3] forme un tableau de trois éléments, * fait de chaque élément un pointeur et (int, int) fait de sa cible une fonction qui renvoie un int.

Dans char *(*f)(const char *), f est un pointeur vers une fonction qui prend un const char * ; cette fonction renvoie un char *. Distingue les exigences de durée de vie du pointeur renvoyé des exigences de compatibilité d’appel du pointeur de fonction.

Points clés

  • Les parenthèses distinguent un pointeur de fonction d’une fonction qui renvoie un pointeur.
  • Un pointeur de fonction indique le type de retour et les types des paramètres des appels valides.
  • Un nom de fonction peut initialiser un pointeur de fonction compatible sans &.
  • Un typedef peut faciliter la lecture des tables de pointeurs de fonctions et des types de retour.
  • Valide l’indice de la table et vérifie tout pointeur facultatif qui peut être nul avant l’appel.

5.2 Fonctions de rappel et comparaisons correctes avec qsort

Une fonction de rappel est une fonction fournie à une autre fonction, qui l’appelle dans le cadre de son travail. Cela sépare l’opération générale de parcours ou de tri de l’action ou de l’ordre particulier que tu choisis.

Passer une opération sans l’appeler
void twice(int *x) { *x *= 2; }

void for_each(int *a, size_t n, void (*fn)(int *)) {
    for (size_t i = 0; i < n; i++) fn(&a[i]);
}

Pour int v[] = {1, 2, 3};, for_each(v, 3, twice); produit {2, 4, 6}. Le nom de la fonction fournit un pointeur. Écrire twice() tenterait de l’appeler immédiatement, sans l’argument requis, au lieu de fournir une opération pour des appels ultérieurs.

Retrouver un type à partir d’un pointeur générique d’objet

Un void * peut contenir un pointeur d’objet et être reconverti dans le type d’origine. Il ne peut pas être déréférencé ni utilisé dans l’arithmétique des pointeurs en C standard, car il ne fournit aucun type d’élément. Préserve les qualificatifs : les données en lecture seule passent par const void *, puis par un pointeur adapté vers un objet constant.

qsort de <stdlib.h> prend une adresse de base, un nombre d’éléments, une taille d’élément en octets et une fonction de rappel de comparaison. Celle-ci est de type int (*)(const void *, const void *). Chaque argument pointe vers un élément du tableau, et le signe de la valeur renvoyée indique où placer le premier élément :

Signe de la valeur renvoyéeOrdre
NégatifPremier avant le second
ZéroÉquivalents pour cet ordre
PositifPremier après le second
Trier des entiers sans débordement
int cmp_int(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);
}

/* In the caller: */
int v[] = {42, -7, 19, 0, 3};
qsort(v, 5, sizeof v[0], cmp_int);

Le tableau devient {-7, 0, 3, 19, 42}. Les comparaisons produisent 0 ou 1 : l’expression de retour ne produit donc que -1, 0 ou 1. Inverser l’expression d’ordre en (y > x) - (y < x) trie par ordre décroissant.

N’utilise pas return x - y; pour des entiers signés quelconques. Pour un int de 32 bits, soustraire -2 000 000 000 de 2 000 000 000 dépasse INT_MAX. Le débordement d’un entier signé est un comportement indéfini : aucun résultat après rebouclage ni signe de comparaison fiable n’est donc garanti.

Trier des pointeurs vers des chaînes
int cmp_str(const void *a, const void *b) {
    return strcmp(*(const char *const *)a,
                  *(const char *const *)b);
}

const char *names[] = {"Lin", "Ada", "Grace"};
qsort(names, 3, sizeof names[0], cmp_str);

Inclus <string.h> pour strcmp. Ici, un élément du tableau est lui-même un pointeur. La fonction de rappel reçoit l’adresse de cet élément pointeur : le déréférencer une fois permet donc de récupérer l’adresse de la chaîne. Le const intérieur ajouté empêche de modifier l’élément par ce chemin d’accès. Après le tri, l’ordre est "Ada", "Grace", "Lin".

strcmp(a, b) lirait les octets des emplacements de pointeurs comme du texte. Utilise aussi sizeof names[0], la taille d’un élément pointeur, et non sizeof(char) ou la longueur d’un nom.

Les fonctions de comparaison doivent laisser les éléments inchangés et répondre de façon cohérente, notamment en respectant la transitivité de l’ordre. qsort ne garantit pas de conserver l’ordre des éléments considérés comme égaux. bsearch accepte le même type de fonction de comparaison pour rechercher un élément dans un tableau déjà trié selon cet ordre.

Points clés

  • Passe un nom de fonction pour fournir une fonction de rappel ; les parenthèses appellent immédiatement une fonction.
  • Retrouve le type d’élément à partir de chaque argument générique de comparaison avant l’accès.
  • qsort nécessite un nombre d’éléments, la taille d’un élément et une fonction de comparaison compatible.
  • Utilise les signes des comparaisons sans effectuer de soustraction susceptible de déborder.
  • Les fonctions de comparaison de pointeurs vers des chaînes reçoivent les adresses des emplacements de pointeurs.
  • Des résultats de comparaison égaux ne garantissent pas un ordre stable après qsort.

Pointeurs en C : exemples

5 Leçons Interactives

1

Suivre une adresse et la valeur qu’elle permet d’atteindre

13 étapes
Construis un schéma mémoire, redirige des pointeurs et prévois quel objet chaque affectation modifie.
  1. 1
    IntroductionDistinguer trois éléments

    Un entier stocké possède une valeur, un emplacement et une durée de vie. Un pointeur ajoute un autre objet dont la valeur est une adresse. Suis un petit programme en notant les deux types de valeurs : des entiers comme 10 et des adresses comme 0x1000.

    À chaque ligne, pose-toi une question concrète : cette instruction modifie-t-elle une adresse, modifie-t-elle l’objet atteint par cette adresse ou lit-elle simplement une valeur ?

  2. 2
    ExplicationDisposer quatre entiers en mémoire

    Imagine int scores[4] commençant à 0x1000 sur un système dont les entiers occupent 4 octets. Le premier élément utilise les octets de 0x1000 à 0x1003. Le suivant commence à 0x1004, puis viennent 0x1008 et 0x100C.

    Les éléments occupent des emplacements consécutifs. Cette règle de disposition concerne le tableau ; elle ne garantit aucun ordre pour deux entiers déclarés séparément.

  3. 3
    ExempleMesurer le tableau, puis son contenu

    Pour char word[20] = "hi";, deux mesures donnent des résultats différents :

    • sizeof word correspond aux 20 octets de stockage du tableau.
    • strlen(word) correspond aux 2 caractères qui précèdent '\0'.

    Le caractère nul de fin occupe un autre élément du tableau, et les éléments restants appartiennent toujours au tableau. sizeof renvoie une valeur size_t, affichée avec %zu ; il ne compte pas le nombre d’éléments actuellement utilisés par le programme.

  4. 4
    LienDistinguer la disposition de la durée de vie

    L’alignement détermine les adresses de départ adaptées, tandis que le boutisme détermine l’ordre des octets dans une valeur. Aucun des deux n’indique combien de temps un objet existe.

    Une variable locale ordinaire existe pendant l’exécution de son bloc. Une variable locale statique ou une variable globale existe pendant toute l’exécution du programme. Enregistrer une adresse conserve un chemin vers une zone de stockage ; cela ne peut pas prolonger la durée de vie d’une variable locale automatique après la fin du bloc.

  5. 5
    DéfiTrouver l’adresse d’un élément

    Un tableau de six entiers commence à 0x1800, avec 4 octets par élément. Où commence l’élément 3 et quelle est la taille du tableau entier ?

    ---reveal---

    L’élément 3 commence douze octets après le début, à 0x180C. Six éléments de 4 octets occupent 24 octets. L’indice donne un décalage en nombre d’éléments ; le multiplier par la taille d’un élément donne le décalage en octets.

  6. 6
    ExplicationDonner une cible au pointeur

    Considère int count = 42; int *p = &count;. L’opérateur & obtient l’adresse de count, et p stocke cette adresse. Si count se trouve à l’adresse fictive 0x2000, la valeur du pointeur est 0x2000 ; la valeur de l’entier reste 42.

    Le pointeur occupe lui aussi sa propre zone de stockage. Son adresse est &p, une information différente de l’adresse contenue dans p.

  7. 7
    ConseilLire l’étoile à côté de chaque nom

    int *p, q; déclare un pointeur et un entier ordinaire. Pour déclarer deux pointeurs, utilise int *p, *q; ou place les déclarations sur des lignes distinctes.

    Initialise un pointeur avec l’adresse d’un objet adapté ou avec NULL. Un pointeur nul est un état vide connu ; un pointeur local non initialisé a une valeur indéterminée et ne peut pas être testé en toute sécurité comme s’il était déjà nul.

  8. 8
    DéfiRediriger sans modifier les données

    Pars de int a = 9, b = 16; int *p = &a;. Exécute ensuite p = &b;. Quelles valeurs entières changent et que contient maintenant p ?

    ---reveal---

    Aucun des deux entiers ne change : a reste égal à 9 et b reste égal à 16. Seul p change et stocke l’adresse de b. Une affectation de pointeur remplace une adresse ; elle ne copie pas le contenu de l’ancienne ou de la nouvelle cible.

  9. 9
    ExempleLire, additionner et écrire à travers p

    Continue avec int x = 10; int *p = &x; et exécute :

    *p = *p + 5;
    

    Le *p de droite lit 10 dans x. Ajouter 5 donne 15. Le *p de gauche désigne le même objet : l’affectation écrit donc 15 dans x. L’adresse dans p n’a pas changé. Ensuite, int y = *p * 2; lit 15 et crée un entier distinct y contenant 30.

  10. 10
    Point cléUne copie d’adresse crée un autre chemin

    Pour int n = 6; int *p = &n; int *q = p;, les deux pointeurs atteignent n. L’affectation a copié une adresse, et non l’objet entier.

    Après *q = 14;, lire n, *p ou *q donne 14. Dessine une case pour l’entier et deux flèches qui y mènent. Dessiner deux cases d’entiers suggérerait à tort des données indépendantes.

  11. 11
    ConseilVérifier la cible avant l’accès

    Pour accéder à un objet, il faut un pointeur vers un objet de type adapté dont la durée de vie n’est pas terminée. Un pointeur nul ne désigne aucun objet et un pointeur pendant a perdu sa cible. Un test de nullité traite l’état vide facultatif d’un pointeur initialisé ; il n’établit pas qu’une cible non nulle existe toujours.

    Un accès invalide constitue un comportement indéfini. C ne garantit ni une valeur particulière ni un plantage, même lorsqu’une exécution semble normale.

  12. 12
    DéfiSuivre une adresse copiée

    Quelles sont les valeurs finales de a et b ?

    int a = 6, b = 19;
    int *p = &a, *q = p;
    p = &b;
    *q = *p + 1;
    

    ---reveal---

    a devient 20 et b reste égal à 19. Rediriger p laisse q pointant vers a. La dernière ligne lit b à travers p et écrit la somme dans a à travers q.

  13. 13
    RécapitulatifSuivre l’objet désigné à gauche

    Lis le côté gauche avant de prévoir une modification. Une affectation à p modifie une adresse ; une affectation à *p modifie sa cible. Garde les pointeurs copiés sous forme de flèches distinctes, vérifie la durée de vie de la cible et utilise le type pointé pour interpréter chaque accès.

2

Faire modifier les données de la fonction appelante par une fonction

11 étapes
Suis un échange de valeurs, utilise des paramètres de sortie et détermine si les pointeurs renvoyés restent valides.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionSuivre l’adresse dans un appel
  2. 2
    ExplicationUne adresse copiée atteint toujours la fonction appelante
  3. 3
    ExempleConserver la valeur avant de la remplacer
  4. 4
    ExempleÉcrire la seconde valeur dans le premier emplacement
  5. 5
    ExempleTerminer l’échange
  6. 6
    DéfiFournir deux résultats
  7. 7
    ExplicationPlacer const au niveau voulu
  8. 8
    ExempleRenvoyer une position encore valide
  9. 9
    DéfiExpliquer le problème de durée de vie
  10. 10
    DéfiChoisir une interface de recherche permettant l’écriture
  11. 11
    RécapitulatifVérifier l’écriture et la durée de vie
3

Parcourir un tableau sans dépasser ses bornes

15 étapes
Relie l’arithmétique, la conversion de tableau et les boucles sur les chaînes en suivant chaque position visitée.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionRepérer le départ, le pas et l’arrêt
  2. 2
    ExplicationSe déplacer par éléments
  3. 3
    ExempleSoustraire deux positions
  4. 4
    Point cléUtiliser une borne qui n’est pas un élément
  5. 5
    DéfiTrouver le milieu
  6. 6
    ExplicationUne expression de tableau fournit un pointeur de départ
  7. 7
    ExempleUtiliser sizeof au bon endroit
  8. 8
    DéfiPasser une plage plus petite
  9. 9
    ExempleLire puis avancer, ou avancer puis lire
  10. 10
    ExplicationTrouver la fin d’une chaîne
  11. 11
    ExempleCopier un caractère à la fois
  12. 12
    ConseilRéserver l’emplacement du caractère nul de fin
  13. 13
    DéfiCorriger la dernière visite de la boucle
  14. 14
    DéfiPréparer une inversion en toute sécurité
  15. 15
    RécapitulatifRendre explicite chaque position visitée
4

Modifier un pointeur et lire un tableau de pointeurs

12 étapes
Suis deux niveaux d’indirection, puis distingue les emplacements de pointeurs vers des chaînes des lignes complètes d’un tableau.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionFaire du pointeur lui-même une cible
  2. 2
    ExempleDessiner deux flèches
  3. 3
    ExplicationChoisir le niveau à modifier
  4. 4
    ExemplePasser par adresse la position de la fonction appelante
  5. 5
    DéfiPrévoir une modification du seul pointeur
  6. 6
    LienLire la position d’arrêt d’une conversion
  7. 7
    ExplicationUn tableau peut posséder des emplacements de pointeurs
  8. 8
    ExempleAvancer parmi les adresses
  9. 9
    ConseilVérifier le nombre d’arguments avant d’accéder aux chaînes
  10. 10
    DéfiLire le type d’une ligne entière
  11. 11
    DéfiAdapter une fonction à la zone de stockage fournie
  12. 12
    RécapitulatifNommer l’objet à chaque niveau
5

Choisir une opération et trier avec une fonction de rappel

15 étapes
Lis les types des pointeurs de fonctions, sélectionne une opération dans une table et construis des fonctions de comparaison pour les entiers et les pointeurs vers des chaînes.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionDistinguer une tâche générale de l’opération choisie
  2. 2
    ExplicationLire le type de fonction appelable
  3. 3
    ExempleSélectionner une autre fonction
  4. 4
    ConseilNommer un type pointeur pour une table
  5. 5
    DéfiLire une déclaration apparentée
  6. 6
    ExplicationFournir une fonction de rappel pour une action
  7. 7
    ExempleFournir les quatre arguments du tri
  8. 8
    ExplicationRetrouver le type entier des éléments
  9. 9
    ExempleRenvoyer le signe correspondant à l’ordre
  10. 10
    DéfiÉviter un raccourci qui provoque un débordement
  11. 11
    LienTrier des pointeurs vers des chaînes, pas des lignes de caractères
  12. 12
    DéfiChoisir la bonne taille pour le tri
  13. 13
    Point cléL’égalité ne garantit pas la stabilité
  14. 14
    DéfiSuivre une fonction de rappel de comparaison sélectionnée
  15. 15
    RécapitulatifFaire correspondre l’appel, la disposition et l’ordre

Pointeurs en C : QCM

40 Questions Pratiques en 4 séries

Série 1 : Suivre les adresses et les appels de fonctions10 Questions Pratiques

Question 1 sur 10

Un int occupe 4 octets. Un tableau int levels[7] commence à l’adresse fictive 0x2400. À quelle adresse commence levels[4] ?
  1. 0x2404
  2. 0x2410
  3. 0x2414
  4. 0x241C
Afficher la réponseMasquer la réponse
Réponse : B
L’indice 4 se trouve quatre éléments après le début. Chacun occupe 4 octets : le décalage est donc de 16 octets, écrits 0x10, et l’adresse est 0x2410. Ajouter seulement 4 à l’adresse reviendrait à traiter l’indice comme un nombre d’octets et atteindrait le deuxième élément.

Question 2 sur 10

Étant donné int items = 16;, quelle déclaration crée un pointeur vers items ?
  1. int *p = items;
  2. int p = &items;
  3. int *p = *items;
  4. int *p = &items;
Afficher la réponseMasquer la réponse
Réponse : D
&items obtient l’adresse de l’entier, et int *p déclare le type pointeur correspondant. Initialiser le pointeur avec items fournirait l’entier stocké 16 au lieu de l’adresse d’un objet entier. Obtenir une adresse laisse la valeur de items inchangée.

Question 3 sur 10

Quelle valeur n contient-il après ce code ?

int n = 14;
int *p = &n;
*p = 23;
  1. 23
  2. 14
  3. 37
  4. 0
Afficher la réponseMasquer la réponse
Réponse : A
p contient l’adresse de n : *p désigne donc ce même entier. Affecter 23 à travers *p remplace la valeur dans n. Conserver 14 reviendrait à confondre une écriture à travers le pointeur avec une modification de l’adresse stockée dans le pointeur lui-même.

Question 4 sur 10

Que vaut score après le retour de clear(score) ?

void clear(int value) { value = 0; }
/* In the caller: */
int score = 27;
clear(score);
  1. 0
  2. 1
  3. 27
  4. Comportement indéfini
Afficher la réponseMasquer la réponse
Réponse : C
La fonction reçoit une copie locale de l’argument entier. Affecter zéro à value modifie ce paramètre : score reste donc égal à 27 dans la fonction appelante. Pour obtenir zéro, il faudrait une fonction qui reçoit &score par un pointeur vers un entier et écrit dans l’objet pointé.

Question 5 sur 10

Sur un système dont les pointeurs d’objets occupent 8 octets, qu’affiche ce code ?

char label[12] = "oak";
char *p = label;
printf("%zu %zu
", sizeof label, sizeof p);
  1. 4 8
  2. 12 8
  3. 12 12
  4. 3 8
Afficher la réponseMasquer la réponse
Réponse : B
label est un tableau de douze caractères : sizeof label vaut donc 12, même si le texte stocké ne contient que trois lettres. p est un objet pointeur distinct et occupe 8 octets sur ce système. Compter le caractère nul de fin donne la partie occupée par la chaîne, et non la taille déclarée du tableau.

Question 6 sur 10

Que valent left et right après l’appel ?

void exchange(int *a, int *b) {
    int *t = a;
    a = b;
    b = t;
}
/* In the caller: */
int left = 11, right = 29;
exchange(&left, &right);
  1. 29, 11
  2. 29, 29
  3. 11, 11
  4. 11, 29
Afficher la réponseMasquer la réponse
Réponse : D
La fonction échange ses copies locales des pointeurs sans effectuer d’affectation à *a ni à *b. Aucun objet entier n’est modifié : left reste donc égal à 11 et right à 29. S’attendre à un échange des valeurs revient à confondre le déplacement de valeurs d’adresses locales avec la copie des entiers atteints par ces adresses.

Question 7 sur 10

Une fonction est déclarée int read_count(int *out);. Elle renvoie 1 en cas de succès et n’écrit le nombre qu’en cas de succès. Quel code de la fonction appelante affiche en toute sécurité un nombre qui vient d’être lu ?
  1. int n; if (read_count(&n)) printf("%d", n);
  2. int n; read_count(&n); printf("%d", n);
  3. int n; if (read_count(n)) printf("%d", n);
  4. int *n; if (read_count(n)) printf("%d", *n);
Afficher la réponseMasquer la réponse
Réponse : A
La fonction appelante fournit l’adresse d’un objet entier et n’utilise sa valeur qu’après avoir vérifié le code de succès. Un affichage inconditionnel pourrait lire un n non initialisé si la fonction échoue sans écrire. Un pointeur non initialisé ne fournit aucun objet de sortie valide que la fonction puisse remplir.

Question 8 sur 10

Étant donné int x = 6, y = 17; const int *p = &x;, quelle affectation est autorisée ?
  1. *p = y;
  2. (*p)++;
  3. p = &y;
  4. *p += 1;
Afficher la réponseMasquer la réponse
Réponse : C
p est un pointeur vers des données entières constantes : il peut donc être redirigé vers y. Une affectation à travers *p est rejetée, car ce chemin d’accès est en lecture seule. La restriction concerne l’accès à travers ce pointeur ; x lui-même a été défini sans const et peut toujours être modifié par son propre nom.

Question 9 sur 10

Quelles sont les valeurs finales de a et b ?

int a = 4, b = 13;
int *p = &a, *q = p;
*p += 3;
p = &b;
*q = *p - 2;
*p = *q + 5;
  1. 7, 12
  2. 11, 13
  3. 7, 18
  4. 11, 16
Afficher la réponseMasquer la réponse
Réponse : D
La première écriture donne la valeur 7 à a, et la redirection de p laisse q pointant vers a. L’affectation suivante donne la valeur 11 à a à partir de la valeur 13 de b, puis la dernière écriture donne la valeur 16 à b. Garder 7 pour a revient à déplacer à tort q alors que seul p a été redirigé.

Question 10 sur 10

Quel couple de valeurs la dernière ligne affiche-t-elle ?

int *next_value(void) {
    static int n = 20;
    n += 3;
    return &n;
}
/* In the caller: */
int *first = next_value();
int saved = *first;
int *second = next_value();
printf("%d %d
", saved, *first + *second);
  1. 23 49
  2. 23 52
  3. 26 52
  4. 23 46
Afficher la réponseMasquer la réponse
Réponse : B
Le premier appel stocke 23, qui est copié dans saved. Le second appel donne la valeur 26 au même entier statique, et les deux pointeurs renvoyés l’atteignent : la somme de leurs valeurs pointées est donc 52. Le résultat 49 suppose que le premier pointeur conserve l’ancienne valeur, mais seul saved contient une copie distincte de l’entier.
Série 2 : Raisonner sur les plages de tableaux et les chaînes10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Pour double values[8];, que vaut &values[6] - &values[2] ?
  1. 32
  2. 8
  3. 4
  4. 2
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Qu’affiche puts(message + 5) pour const char *message = "blue sky"; ?
  1. sky
  2. sky
  3. blue
  4. ky
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Quelle est la valeur de *(p - 2) après ces instructions ?

int bins[] = {6, 12, 18, 24, 30, 36, 42};
int *p = bins + 1;
p += 4;
  1. 18
  2. 36
  3. 12
  4. 24
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Que renvoie total(data + 1, 3) ?

int total(const int *p, size_t n) {
    int s = 0;
    for (size_t i = 0; i < n; i++) s += p[i];
    return s;
}
/* In the caller: */
int data[] = {9, 4, 7, 2, 8};
  1. 20
  2. 13
  3. 17
  4. 21
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Sur un système avec des pointeurs de 8 octets et des int de 4 octets, que renvoie count(values) ?

size_t count(int a[9]) {
    return sizeof a / sizeof a[0];
}
/* In the caller: */
int values[9] = {0};
  1. 2
  2. 9
  3. 8
  4. 4
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Que valent taken et a[0] ensuite ?

int a[] = {8, 15, 22};
int *p = a;
int taken = *p++;
(*p)++;
  1. 15, 9
  2. 8, 9
  3. 8, 8
  4. 9, 8
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Après ce code, quel est le plus grand entier positif ou nul k pour lequel p + k peut être formé dans la plage autorisée pour le pointeur ?

int a[8];
int *p = a + 1, *end = a + 8;
p += (end - p) / 2;
end -= 2;
p += end - p;
  1. 3
  2. 2
  3. 1
  4. 4
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Une chaîne modifiable terminée par un caractère nul est inversée avec cette préparation. Quelle entrée révèle un calcul de pointeur invalide avant le début de la boucle ?

size_t n = strlen(s);
char *left = s;
char *right = s + n - 1;
while (left < right) {
    char t = *left; *left++ = *right; *right-- = t;
}
  1. Une chaîne de deux caractères
  2. Une chaîne d’un caractère
  3. Une chaîne de longueur impaire
  4. Une chaîne vide
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Un tableau int a[6] commence à l’adresse fictive 0x3000 et chaque entier occupe 4 octets. Quelles adresses a + 1 et &a + 1 représentent-ils ?
  1. 0x3004 et 0x3018
  2. 0x3004 et 0x3004
  3. 0x3018 et 0x3018
  4. 0x3001 et 0x3006
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Que valent count et p - text après cette boucle ?

char text[] = "abaca";
char *p = text;
int count = 0;
while (*p != '\0') {
    if (*p++ == 'a') count++;
}
  1. 3, 6
  2. 2, 5
  3. 3, 5
  4. 2, 6
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 3 : Suivre les niveaux de pointeurs et les dispositions de stockage10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Après int n = 31; int *p = &n; int **pp = &p;, quel est le type de *pp ?
  1. int
  2. int **
  3. int ***
  4. int *
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Avec des pointeurs d’objets de 8 octets, que vaut sizeof words ?

const char *words[] = {"red", "violet", "gold", "teal"};
  1. 20
  2. 32
  3. 24
  4. 8
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Que vaut *p ensuite ?

int a = 18, b = 25;
int *p = &a;
int **pp = &p;
*pp = &b;
**pp -= 4;
  1. 21
  2. 14
  3. 25
  4. 18
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Pour char *end; long n = strtol("57kg", &end, 10);, vers quoi end pointe-t-il après la conversion ?
  1. Le premier caractère de "57kg"
  2. Le caractère 7 dans "57kg"
  3. Le caractère k dans "57kg"
  4. Le caractère nul de fin après "57kg"
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Un programme reçoit la ligne de commande ./paint amber 6 dans l’organisation habituelle d’un environnement hébergé. Que valent argc et argv[2][0] ?
  1. 2 et 6
  2. 3 et 6
  3. 3 et '6'
  4. 2 et '6'
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Quel type de paramètre correspond à int board[2][5] de la fonction appelante après sa conversion habituelle en pointeur ?
  1. int **board
  2. int (*board)[5]
  3. int *board[5]
  4. int (*board)[2]
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Que valent a, b et *p après l’appel ?

void shift(int **slot, int *next) {
    **slot += 2;
    *slot = next;
    **slot += 3;
}
/* In the caller: */
int a = 5, b = 12;
int *p = &a;
shift(&p, &b);
  1. 7, 15, 15
  2. 10, 12, 12
  3. 7, 15, 7
  4. 5, 15, 15
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Quel est le décalage en octets de map[2][3] par rapport à map[0][0], et quel est le type de map + 1, pour int map[4][6] avec des entiers de 4 octets ?
  1. 36 octets et int **
  2. 60 octets et int **
  3. 36 octets et int (*)[6]
  4. 60 octets et int (*)[6]
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Que vaut word et quel est l’indice de p ensuite ?

char word[] = "abcd";
char *p = word;
char saved = *p++;
*p = saved;
p += 2;
*p = 'z';
  1. "aacz", indice 3
  2. "bbcz", indice 3
  3. "aazd", indice 2
  4. "aacd", indice 4
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Une fonction auxiliaire doit renvoyer un pointeur vers le plus grand élément du tableau non vide de la fonction appelante, en permettant à celle-ci de le modifier. Quelle solution satisfait ces deux exigences ?
  1. Copier la plus grande valeur dans un int local et renvoyer son adresse.
  2. Stocker la plus grande valeur dans un int statique et renvoyer son adresse.
  3. Conserver l’indice du plus grand élément et renvoyer &a[index].
  4. Conserver la plus grande valeur et la renvoyer à travers un const int *.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 4 : Choisir des opérations et construire des fonctions de rappel pour le tri10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Que déclare int (*filter)(int); ?
  1. Une fonction qui prend un int et renvoie un int *.
  2. Un pointeur vers une fonction qui prend un int et renvoie un int.
  3. Une fonction qui prend un int * et renvoie un int.
  4. Un pointeur vers une fonction qui prend un int * et renvoie un int.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Une fonction a pour signature void visit(int *a, size_t n, void (*action)(int *));, et void mark(int *x); est disponible. Comment une fonction appelante fournit-elle mark pour un tableau data de cinq éléments ?
  1. visit(data, 5, mark());
  2. visit(data, 5, *mark(data));
  3. visit(data, 5, mark(data));
  4. visit(data, 5, mark);
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Quelle valeur est affectée à result ?

int difference(int a, int b) { return a - b; }
int product(int a, int b) { return a * b; }
int (*op)(int, int) = difference;
op = product;
int result = (*op)(7, 4);
  1. 28
  2. 3
  3. 11
  4. -3
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Pour int readings[8];, quel appel trie tous les éléments à l’aide d’une fonction de rappel compare compatible ?
  1. qsort(readings, sizeof readings, 8, compare);
  2. qsort(readings, 8, sizeof readings, compare);
  3. qsort(readings, 8, sizeof readings[0], compare);
  4. qsort(readings, 8, sizeof readings[0], compare());
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Que valent result et calls après ce code ?

int calls = 0;
int add_one(int x) { calls++; return x + 1; }
int twice(int x) { calls++; return x * 2; }
/* In a function: */
int (*ops[])(int) = {twice, add_one};
int result = ops[1](ops[0](5));
  1. 12, 2
  2. 11, 1
  3. 12, 1
  4. 11, 2
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Une fonction de comparaison d’entiers doit fonctionner pour tout int représentable, en ordre croissant. Quelle expression de retour satisfait cette exigence après lecture des éléments dans x et y ?
  1. (x > y) - (x < y)
  2. (x < y) - (x > y)
  3. x - y
  4. x > y
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Pour un tableau const char *labels[], quelle expression récupère la chaîne transmise dans le paramètre de comparaison const void *a avant l’appel de strcmp ?
  1. (const char *)a
  2. (const char *const *)a
  3. *(const char *const *)a
  4. **(const char *const *)a
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Une table int (*ops[3])(int, int) contient des fonctions valides aux emplacements 0 et 2, et NULL à l’emplacement 1. Pour un entier signé choice, quelle condition permet d’exécuter ops[choice](8, 2) en toute sécurité ?
  1. choice < 3 && ops[choice] != NULL
  2. choice >= 0 && choice < 3 && ops[choice] != NULL
  3. ops[choice] != NULL && choice >= 0 && choice < 3
  4. choice >= 0 && choice < 3
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Une fonction de comparaison trie des entiers par valeur absolue et renvoie zéro pour des valeurs opposées de même valeur absolue. Elle trie {4, -4, 2, -2} avec qsort. Quel résultat est garanti ?
  1. {-2, 2, -4, 4} dans cet ordre exact.
  2. {2, -2, 4, -4} dans cet ordre exact.
  3. {-4, 4, -2, 2} dans cet ordre exact.
  4. Les deux valeurs de valeur absolue 2 précèdent celles de valeur absolue 4.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Une liste est triée par longueur croissante de chaîne à l’aide de ce corps de fonction de rappel. Quel est le défaut ?

const char *x = *(const char *const *)a;
const char *y = *(const char *const *)b;
size_t nx = strlen(x), ny = strlen(y);
return nx < ny;
  1. Elle renvoie une valeur positive pour un texte plus court et zéro pour un plus long.
  2. Elle lit les longueurs des chaînes dans les octets des emplacements de pointeurs.
  3. Elle modifie les chaînes pendant qu’elle mesure leurs longueurs.
  4. Elle compare les adresses des caractères au lieu de leur nombre.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Tu bloques sur ton propre exercice ? Aide aux Devoirs

Mots-Clés

60 Mots-Clés, chacun avec sa définition

A

Adresse mémoire
Une adresse mémoire désigne un octet de stockage ; l’adresse d’un objet désigne le premier octet de la zone qu’il occupe.
Si un entier de quatre octets commence à l’adresse fictive 0x2000, ses octets restants se suivent jusqu’à 0x2003.
Ajustement d’un paramètre tableau en paramètre pointeur
L’ajustement d’un paramètre tableau en paramètre pointeur transforme une déclaration de tableau dans une liste de paramètres de fonction en paramètre pointeur, sans copier la longueur du tableau.
Un paramètre écrit int a[10] est un pointeur dans la fonction : sizeof a mesure donc ce pointeur.
Aliasage
L’aliasage se produit lorsque différentes expressions ou différents pointeurs désignent le même objet, si bien qu’une écriture par un chemin est visible par un autre.
Après int *q = p;, les deux pointeurs atteignent le même entier jusqu’à ce qu’une affectation ultérieure redirige l’un d’eux.
Alignement
L’alignement est la restriction qu’un type impose aux adresses de départ adaptées à ses objets ; il est géré automatiquement pour les déclarations ordinaires.
Une disposition courante d’entiers alignés sur quatre octets place les éléments successifs d’un tableau à des adresses divisibles par quatre.
argc
argc est le nombre d’arguments positif ou nul transmis à main en environnement hébergé ; il compte les chaînes fournies par argv, y compris le nom du programme lorsqu’il est présent.
Avant de lire argv[2], teste que argc > 2 afin que la chaîne d’argument demandée existe réellement.
argv
argv est la liste de pointeurs de caractères transmise à main en environnement hébergé, avec un pointeur nul à l’indice argc et les chaînes des arguments utilisateur à partir de l’indice 1.
Pour une invocation habituelle ./sum 8 13, argv[2] atteint "13" ; l’argument est du texte avant sa conversion numérique.
Arithmétique des pointeurs
L’arithmétique des pointeurs déplace un pointeur d’un nombre d’éléments de son type pointé ; les résultats sont limités à son tableau et à la borne juste après le dernier élément.
Ajouter 2 à un pointeur d’entier le déplace de deux positions d’entiers ; la fonction appelante n’a pas à multiplier le décalage par sizeof.

B

Bornes du tableau
Les bornes du tableau déterminent les indices des éléments existants et les positions où un pointeur peut se déplacer ; une seule position supplémentaire est autorisée pour marquer la fin.
Former a - 1 pour un pointeur au début d’un tableau est invalide, même si le programme ne lit jamais à travers ce pointeur.
Boutisme
Le boutisme est l’ordre des octets dans une valeur occupant plusieurs octets ; les systèmes petits-boutistes stockent l’octet de poids faible en premier.
Sur une plateforme petit-boutiste, la valeur 0x12345678 sur quatre octets donne les octets 78 56 34 12 des adresses les plus basses aux plus hautes.

C

Cadre de pile
Un cadre de pile est la zone de stockage qu’une implémentation typique utilise pour les données de travail d’un appel de fonction, notamment de nombreux objets locaux automatiques.
Le retour d’une fonction permet de réutiliser l’ancienne zone de son cadre ; une adresse locale enregistrée ne peut pas la préserver.
Caractère nul
Le caractère nul est le caractère de valeur zéro, écrit '\0', qui termine une chaîne C dans sa zone de stockage de caractères accessible.
Une boucle de calcul de longueur s’arrête sur le caractère nul de fin, tandis qu’une boucle de copie complète doit aussi écrire ce dernier caractère.
Comportement indéfini
Un comportement indéfini signifie que la norme C n’impose aucune exigence sur le résultat d’une opération, notamment sur l’arrêt ou la poursuite du programme.
Déréférencer un pointeur nul constitue un comportement indéfini, même si une machine particulière signale une erreur de segmentation.
Conversion de tableau en pointeur
La conversion de tableau en pointeur transforme une expression de tableau en pointeur vers son premier élément dans la plupart des contextes d’expression, sans copier ses éléments.
int *p = values; initialise un pointeur vers values[0] ; le tableau initial possède toujours tous ses éléments.

D

Débordement d’entier signé
Un débordement d’entier signé se produit lorsqu’un résultat arithmétique signé se trouve hors de la plage représentable par son type, ce qui constitue un comportement indéfini en C.
Une fonction de comparaison d’entiers utilisant x - y peut déborder même lorsque chaque donnée d’entrée est représentable séparément dans un int.
Débordement de tampon
Un débordement de tampon est une écriture au-delà des bornes d’un objet destination, par exemple lorsqu’une copie de chaîne manque de place pour son caractère nul final.
Un mot de trois lettres nécessite quatre emplacements de caractères ; une destination de trois octets ne peut pas contenir son caractère nul de fin.
Déclarateur de pointeur
Un déclarateur de pointeur utilise * avec un nom déclaré pour indiquer que l’objet nommé est de type pointeur.
Dans int *left, right;, seul left est un pointeur ; déclarer right comme pointeur nécessite une autre étoile.
Déréférencement
Le déréférencement applique l’opérateur unaire * à un pointeur pour désigner l’objet pointé et permettre une lecture ou une écriture appropriée par ce chemin.
Après int *p = &score;, l’affectation *p = 28; modifie le même entier que celui lu ensuite par score.
Différence de pointeurs
Une différence de pointeurs est le nombre signé d’éléments obtenu en soustrayant deux positions dans un même tableau, y compris une position valide juste après le dernier élément.
&a[5] - &a[2] donne 3 éléments, quel que soit le nombre d’octets occupés par un élément sur ce système.
Durée de stockage
La durée de stockage décrit combien de temps le stockage d’un objet existe, par exemple pendant une exécution de bloc pour une variable locale automatique ordinaire.
Un pointeur enregistré depuis un bloc intérieur ne peut pas prolonger ensuite la durée de vie d’un entier local ordinaire de ce bloc.
Durée de stockage automatique
La durée de stockage automatique fait exister un objet local ordinaire pendant l’exécution de son bloc ; son stockage prend fin lorsque l’exécution quitte ce bloc.
Le résultat local ordinaire d’une fonction auxiliaire cesse d’exister au retour, même si la fonction a copié son adresse dans un pointeur.
Durée de stockage statique
La durée de stockage statique fait durer le stockage d’un objet pendant toute l’exécution du programme ; une variable locale statique conserve sa valeur entre les appels.
Deux pointeurs renvoyés vers un même objet résultat statique voient la même valeur actualisée après qu’un autre appel y a écrit.

E

Erreur de segmentation
Une erreur de segmentation est un accès mémoire invalide signalé par le système, qui arrête souvent le programme, même si un accès invalide en C n’en produit pas nécessairement une.
Le déréférencement d’un pointeur nul peut arrêter le programme avec une erreur de segmentation ; une écriture à travers un autre pointeur invalide peut corrompre des données silencieusement.

F

Fonction de comparaison
Une fonction de comparaison ordonne une paire d’éléments à l’aide d’un résultat négatif, nul ou positif selon que le premier vient avant le second, lui est équivalent ou vient après lui.
Renvoyer (x > y) - (x < y) ordonne les entiers par ordre croissant sans soustraire les valeurs elles-mêmes.
Fonction de rappel
Une fonction de rappel est une fonction fournie à une autre fonction pour que celle-ci l’appelle lorsque son travail général nécessite une action ou une décision choisie.
Un parcours accepte un pointeur d’action et l’appelle avec l’adresse de chaque élément pour le mettre à jour.

I

Incrémentation postfixée
L’incrémentation postfixée fournit l’ancienne valeur d’un objet à l’expression qui l’entoure et incrémente cet objet, comme pour le déplacement du pointeur dans *p++.
int x = *p++; lit l’élément à l’ancienne position et laisse p pointant vers la position suivante.

L

Littéral de chaîne de caractères
Un littéral de chaîne de caractères est un texte entre guillemets en C qui fournit un tableau à durée de stockage statique dont les caractères ne doivent pas être modifiés.
const char *p = "oak"; donne accès au littéral par un chemin en lecture seule, tandis que char a[] = "oak"; crée une copie modifiable.

N

Niveau d’indirection
Un niveau d’indirection est une étape de pointeur dans une chaîne d’adresses ; un déréférencement la suit et l’obtention de l’adresse d’un objet en ajoute une.
Pour int **pp, le premier déréférencement donne un int *, et le second atteint un objet entier.

O

Octet
Un octet est l’unité de stockage mesurée par sizeof ; un char C occupe toujours un octet, même si les tailles des autres types dépendent du système.
Sur une plateforme où les entiers occupent quatre octets, un tableau de huit entiers occupe 32 octets avant même la déclaration d’un pointeur.
Opérateur d’adressage
L’opérateur d’adressage unaire & obtient l’adresse d’un objet adressable ou d’une fonction et produit un type pointeur correspondant.
Pour un tableau d’entiers, &values[2] fournit l’adresse du troisième élément sans modifier son contenu.
Opérateur d’indexation
L’opérateur d’indexation accède à un élément en définissant a[i] comme *(a + i) ; il utilise le type d’élément du pointeur pour appliquer le facteur de taille à la position.
Si p pointe vers l’élément initial 2, alors p[1] accède à l’élément initial 3 du même tableau.

P

Paramètre de sortie
Un paramètre de sortie est un paramètre pointeur utilisé pour stocker un résultat dans une zone modifiable fournie par la fonction appelante, en plus d’une éventuelle valeur de retour.
Une fonction auxiliaire de division peut remplir deux entiers distincts pour le quotient et le reste tout en renvoyant un code de succès.
Paramètre pointeur
Un paramètre pointeur est un pointeur local à une fonction, initialisé à partir d’un argument, qui permet à la fonction d’accéder à un objet par son adresse.
Une fonction auxiliaire de remise à zéro reçoit &score par int *out et écrit zéro dans l’entier de la fonction appelante avec *out.
Passage par adresse
En C, le passage par adresse désigne couramment la technique qui consiste à passer une adresse et à modifier sa cible, même si l’adresse elle-même est copiée.
Une fonction d’échange appelée avec &x et &y modifie les variables de la fonction appelante en écrivant à travers les deux paramètres pointeurs locaux.
Passage par valeur
Le passage par valeur donne à un paramètre de fonction une copie de la valeur de son argument, y compris lorsque cette valeur est un pointeur vers une zone de stockage de la fonction appelante.
Une affectation à un paramètre local int laisse l’entier de la fonction appelante inchangé ; une écriture à travers une adresse copiée peut le modifier.
Pointeur
Un pointeur est une valeur typée qui peut désigner un objet ou une fonction ; un pointeur d’objet peut aussi être nul ou désigner la position juste après le dernier élément.
Dans int *p = &count;, la variable de type pointeur stocke une adresse tandis que l’entier conserve sa propre valeur.
Pointeur constant
Un pointeur constant est un objet pointeur dont l’adresse stockée ne peut pas être modifiée après l’initialisation, même si sa cible reste modifiable.
Avec int *const p = &x;, *p = 3; est autorisé, mais p = &y; ne peut pas rediriger le pointeur.
Pointeur de fin de conversion
Un pointeur de fin de conversion indique où une conversion de texte s’est arrêtée, ce qui permet à la fonction appelante d’examiner le suffixe non utilisé indépendamment du résultat numérique.
Convertir "73ms" avec strtol peut renvoyer 73 et faire pointer le pointeur de fin de la fonction appelante vers le m.
Pointeur de fonction
Un pointeur de fonction désigne une fonction appelable et indique les types de retour et de paramètres compatibles requis pour l’appeler à travers ce pointeur.
int (*op)(int, int) = add; stocke une fonction d’addition qui peut ensuite être appelée sous la forme op(4, 7).
Pointeur de pointeur
Un pointeur de pointeur désigne un objet pointeur et ajoute un niveau d’indirection avant que l’accès atteigne les données finales pointées.
Pour int **pp = &p;, l’affectation *pp = &y; redirige p, tandis que l’affectation **pp = 8; modifie sa cible.
Pointeur générique d’objet
Un pointeur générique d’objet utilise void *, ou une forme qualifiée, pour contenir une adresse d’objet qui doit retrouver un type adapté avant l’accès.
Une fonction de comparaison d’entiers convertit const void * en const int * avant de lire l’élément du tableau désigné.
Pointeur juste après le dernier élément
Un pointeur juste après le dernier élément marque la position immédiatement après le dernier élément d’un tableau ; il peut servir de borne, mais ne peut pas être déréférencé.
Pour int a[5], un parcours peut s’arrêter lorsque son pointeur courant est égal à a + 5, avant d’accéder à un autre élément.
Pointeur nul
Un pointeur nul est une valeur de pointeur qui ne désigne aucun objet ni aucune fonction et peut représenter un résultat facultatif vide ou une fonction de rappel absente.
Une recherche d’élément renvoie NULL lorsqu’aucune correspondance n’existe ; la fonction appelante teste donc le résultat avant de le déréférencer.
Pointeur pendant
Un pointeur pendant est un pointeur dont la durée de vie de l’ancienne cible est terminée, ce qui le rend inutilisable pour un accès ultérieur à cet objet.
Renvoyer l’adresse d’une variable locale automatique laisse un pointeur pendant lorsque l’appel de fonction se termine.
Pointeur vers un objet constant
Un pointeur vers un objet constant permet de lire sa cible, mais interdit les écritures à travers ce pointeur ; le pointeur lui-même peut toujours être redirigé.
const int *p = &x; autorise p = &y;, tandis qu’une affectation comme *p = 3; est rejetée.
Pointeur vers un tableau
Un pointeur vers un tableau désigne un tableau entier comme type d’élément : ajouter 1 le fait donc avancer de la taille de stockage de ce tableau complet.
Pour int a[4], &a est de type int (*)[4] ; son pas arithmétique diffère de celui de &a[0].
Priorité des opérateurs
La priorité des opérateurs détermine comment les opérateurs se regroupent lorsque les parenthèses ne le précisent pas, avant l’évaluation de l’expression obtenue.
L’opérateur postfixé ++ est prioritaire sur l’opérateur unaire * : *p++ se regroupe donc comme *(p++), et non comme (*p)++.
ptrdiff_t
ptrdiff_t est le type entier signé utilisé pour la soustraction de pointeurs ; le résultat d’une soustraction définie doit être représentable dans ce type.
Inclus <stddef.h> pour ptrdiff_t et utilise %td pour afficher une différence entre des positions valides d’un même tableau.

Q

qsort
qsort est la fonction de bibliothèque standard qui réorganise un tableau à l’aide de son nombre d’éléments, de la taille d’un élément et d’une fonction de rappel de comparaison fournie.
Le tri d’éléments pointeurs vers des chaînes nécessite sizeof names[0] pour la taille de l’emplacement, et non le nombre de caractères d’une chaîne.

R

Rangement par lignes
Le rangement par lignes stocke tous les éléments d’une ligne avant ceux de la ligne suivante dans un tableau rectangulaire ; la largeur fixe détermine le pas entre les lignes.
Pour int a[3][4], ajouter 1 à a converti fait avancer d’une ligne complète de quatre entiers, et non d’un entier.

S

Signature de fonction
Une signature de fonction décrit le type de retour et les types de paramètres qui doivent être compatibles avec le type de pointeur utilisé pour appeler la fonction.
Un pointeur prévu pour deux paramètres entiers ne peut pas appeler en toute sécurité une fonction qui attend un double, même après une conversion explicite.
sizeof
L’opérateur sizeof indique la taille de stockage d’un type ou d’un objet en octets et renvoie une valeur du type entier non signé size_t.
Pour char code[10] = "go";, sizeof code indique 10 même si le texte ne contient que deux caractères.
strtol
strtol est la fonction de bibliothèque standard qui convertit le texte numérique initial en long et peut indiquer sa position d’arrêt à travers un char **.
Passer &end permet à strtol de remplacer le pointeur de caractères de la fonction appelante par l’adresse du premier caractère non utilisé.

T

Table de dispatch
Une table de dispatch est un tableau de pointeurs de fonctions qui sélectionne une opération selon un indice choisi pendant l’exécution du programme.
Un menu d’opérations vérifie son indice de sélection avant de l’utiliser pour récupérer et appeler une entrée de la table.
Tableau à deux dimensions
Un tableau à deux dimensions est un tableau dont les éléments sont des tableaux de taille fixe ; il stocke des lignes complètes contiguës plutôt que des pointeurs de lignes distincts.
int grid[2][5] est converti en int (*)[5] ; le passer comme int ** décrirait une disposition différente.
Tableau de pointeurs
Un tableau de pointeurs stocke des valeurs d’adresses distinctes comme éléments, les objets pointés étant conservés dans d’autres zones de stockage.
Une liste de quatre pointeurs vers des chaînes possède quatre emplacements d’adresses, même lorsque les chaînes sont de longueurs très différentes.
Tableau irrégulier
Un tableau irrégulier est une collection dont les lignes peuvent avoir des longueurs différentes, souvent représentée en C par des pointeurs vers des lignes stockées séparément.
Des pointeurs de chaînes peuvent désigner "red" et "violet" sans réserver des lignes de caractères de même taille pour les deux mots.
Tranche de tableau
Une tranche de tableau peut être fournie en C sous forme d’un pointeur de départ et d’un nombre d’éléments décrivant une partie consécutive sélectionnée dans un tableau existant.
Passer data + 2 avec un nombre de 3 sélectionne les indices initiaux de 2 à 4 sans les copier.
Tri stable
Un tri stable conserve l’ordre relatif initial des éléments que sa fonction de comparaison considère comme égaux ; qsort ne fournit pas cette garantie.
Trier des noms uniquement par longueur peut réordonner deux noms de même longueur, même si leur résultat de comparaison est nul.
typedef
Une déclaration typedef introduit un nom pour un type existant, ce qui facilite la réutilisation d’un type pointeur de fonction complexe dans des déclarations.
Après typedef int (*operation)(int);, operation chosen; déclare un pointeur de fonction, et non un corps de fonction.

V

Valeur de pointeur indéterminée
Une valeur de pointeur indéterminée est l’état non initialisé d’un pointeur local ordinaire ; elle ne fournit aucune cible connue utilisable.
La seule déclaration int *p; ne permet pas au programme de tester, suivre ou utiliser ce pointeur pour écrire en toute sécurité.

À propos de cette unité

Par StudeliSignaler une erreur

Crée ton propre Matériel d'Étude

Transforme tes notes, diapos et documents en Guides d'Étude, Questions Pratiques, Flashcards et 6 autres outils.

Commencer