Pointeurs et adresses mémoire en C
&, 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
- 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émoireDé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 pointeursUtilise 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.
- Se représenter la disposition des variables en mémoire
- É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
constet renvoie des pointeurs qui restent valides.- Passer des variables par adresse à l’aide de paramètres pointeursExplique 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 validesIndique 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.
- Passer des variables par adresse à l’aide de paramètres pointeurs
- 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 pointeursAjoute 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 pointeursUtilise 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.
- Effectuer des opérations arithmétiques sur les pointeurs
- 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 pointeursDé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 commandeConstruis des tableaux de pointeurs vers des chaînes, traite
argcetargvcomme des pointeurs et distingue un tableau de pointeurs d’un tableau à deux dimensions.
- Utiliser des pointeurs de pointeurs
- 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
qsortet 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 qsortPasse des fonctions comme arguments, utilise des pointeurs
voidpour manipuler des données de tout type et écris des fonctions de comparaison correctes pourqsort.
- Déclarer des pointeurs de fonctions, leur affecter des valeurs et appeler les fonctions pointées
Pointeurs en C : cours
1Mé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ément | Premier octet | Dernier octet |
|---|---|---|
scores[0] | 0x1000 | 0x1003 |
scores[1] | 0x1004 | 0x1007 |
scores[2] | 0x1008 | 0x100B |
scores[3] | 0x100C | 0x100F |
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
sizeofmesure des octets ;strlencompte 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éclaration | Signification |
|---|---|
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. &countest une adresse ;countest 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 = &bmodifie le pointeur sans toucher aux entiers.- Affiche un pointeur d’objet avec
%pet un argumentvoid *.
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 initial | Effet |
|---|---|
*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
*pdésigne l’objet atteint à traversp.- Copier une adresse crée des alias ; copier
*pcopie 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é.
2Permettre 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);
apointe versxetbpointe versy.tconserve la valeur initiale dex, soit 3.*a = *bécrit 8 dansx.*b = técrit la valeur 3 conservée dansy.
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
*aet*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.
scanfreç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éclaration | Peut-on rediriger p ? | Peut-on affecter une valeur à travers *p ? |
|---|---|---|
const int *p = &x; | Oui | Non |
int *const p = &x; | Non | Oui |
const int *const p = &x; | Non | Non |
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 queint *constfixe l’adresse.- Un objet défini avec
constne 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.
3Parcourir 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 pointeur | Taille d’un élément | Adresse après p + 3 |
|---|---|---|
int * | 4 octets | 0x100C |
double * | 8 octets | 0x1018 |
char * | 1 octet | 0x1003 |
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 octets | Résultat |
|---|---|
sizeof a | 20 octets pour les cinq éléments |
sizeof p | 8 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 ane mesure le tableau entier que là oùaest réellement un tableau.- Un paramètre tableau est ajusté en pointeur ; passe sa longueur utilisable séparément.
&apointe 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.
4Suivre 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.
| Expression | Type | Objet ou valeur atteint |
|---|---|---|
pp | int ** | Adresse de p |
*pp | int * | Pointeur stocké dans p |
**pp | int | Entier 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 **pppeut contenir l’adresse d’un objetint *.- Chaque déréférencement suit un niveau de pointeur.
**ppmodifie l’entier final ;*pppeut modifier le pointeur intermédiaire.- Pour mettre à jour un pointeur de la fonction appelante, il faut disposer de son adresse.
strtolutilisechar **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 expression | Signification |
|---|---|
int (*rows)[4] | Pointeur vers une ligne de quatre entiers |
int *ptrs[4] | Tableau de quatre pointeurs vers des entiers |
grid après conversion | int (*)[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]etint **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.
5Choisir 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ée | Ordre |
|---|---|
| Négatif | Premier avant le second |
| Zéro | Équivalents pour cet ordre |
| Positif | Premier 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.
qsortné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
1Suivre une adresse et la valeur qu’elle permet d’atteindre
13 étapes
- 1IntroductionDistinguer 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 ?
- 2ExplicationDisposer quatre entiers en mémoire
Imagine
int scores[4]commençant à0x1000sur un système dont les entiers occupent 4 octets. Le premier élément utilise les octets de0x1000à0x1003. Le suivant commence à0x1004, puis viennent0x1008et0x100C.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.
- 3ExempleMesurer le tableau, puis son contenu
Pour
char word[20] = "hi";, deux mesures donnent des résultats différents :sizeof wordcorrespond 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.
sizeofrenvoie une valeursize_t, affichée avec%zu; il ne compte pas le nombre d’éléments actuellement utilisés par le programme. - 4LienDistinguer 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.
- 5Dé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. - 6ExplicationDonner une cible au pointeur
Considère
int count = 42; int *p = &count;. L’opérateur&obtient l’adresse decount, etpstocke cette adresse. Sicountse trouve à l’adresse fictive0x2000, la valeur du pointeur est0x2000; 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 dansp. - 7ConseilLire l’étoile à côté de chaque nom
int *p, q;déclare un pointeur et un entier ordinaire. Pour déclarer deux pointeurs, utiliseint *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. - 8DéfiRediriger sans modifier les données
Pars de
int a = 9, b = 16; int *p = &a;. Exécute ensuitep = &b;. Quelles valeurs entières changent et que contient maintenantp?---reveal---
Aucun des deux entiers ne change :
areste égal à 9 etbreste égal à 16. Seulpchange et stocke l’adresse deb. Une affectation de pointeur remplace une adresse ; elle ne copie pas le contenu de l’ancienne ou de la nouvelle cible. - 9ExempleLire, additionner et écrire à travers p
Continue avec
int x = 10; int *p = &x;et exécute :*p = *p + 5;Le
*pde droite lit 10 dansx. Ajouter 5 donne 15. Le*pde gauche désigne le même objet : l’affectation écrit donc 15 dansx. L’adresse danspn’a pas changé. Ensuite,int y = *p * 2;lit 15 et crée un entier distinctycontenant 30. - 10Point cléUne copie d’adresse crée un autre chemin
Pour
int n = 6; int *p = &n; int *q = p;, les deux pointeurs atteignentn. L’affectation a copié une adresse, et non l’objet entier.Après
*q = 14;, liren,*pou*qdonne 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. - 11ConseilVé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.
- 12DéfiSuivre une adresse copiée
Quelles sont les valeurs finales de
aetb?int a = 6, b = 19; int *p = &a, *q = p; p = &b; *q = *p + 1;---reveal---
adevient 20 etbreste égal à 19. Redirigerplaisseqpointant versa. La dernière ligne litbà traverspet écrit la somme dansaà traversq. - 13RécapitulatifSuivre l’objet désigné à gauche
Lis le côté gauche avant de prévoir une modification. Une affectation à
pmodifie une adresse ; une affectation à*pmodifie 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.
2Faire modifier les données de la fonction appelante par une fonction
11 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionSuivre l’adresse dans un appel
- 2ExplicationUne adresse copiée atteint toujours la fonction appelante
- 3ExempleConserver la valeur avant de la remplacer
- 4ExempleÉcrire la seconde valeur dans le premier emplacement
- 5ExempleTerminer l’échange
- 6DéfiFournir deux résultats
- 7ExplicationPlacer const au niveau voulu
- 8ExempleRenvoyer une position encore valide
- 9DéfiExpliquer le problème de durée de vie
- 10DéfiChoisir une interface de recherche permettant l’écriture
- 11RécapitulatifVérifier l’écriture et la durée de vie
3Parcourir un tableau sans dépasser ses bornes
15 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionRepérer le départ, le pas et l’arrêt
- 2ExplicationSe déplacer par éléments
- 3ExempleSoustraire deux positions
- 4Point cléUtiliser une borne qui n’est pas un élément
- 5DéfiTrouver le milieu
- 6ExplicationUne expression de tableau fournit un pointeur de départ
- 7ExempleUtiliser sizeof au bon endroit
- 8DéfiPasser une plage plus petite
- 9ExempleLire puis avancer, ou avancer puis lire
- 10ExplicationTrouver la fin d’une chaîne
- 11ExempleCopier un caractère à la fois
- 12ConseilRéserver l’emplacement du caractère nul de fin
- 13DéfiCorriger la dernière visite de la boucle
- 14DéfiPréparer une inversion en toute sécurité
- 15RécapitulatifRendre explicite chaque position visitée
4Modifier un pointeur et lire un tableau de pointeurs
12 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionFaire du pointeur lui-même une cible
- 2ExempleDessiner deux flèches
- 3ExplicationChoisir le niveau à modifier
- 4ExemplePasser par adresse la position de la fonction appelante
- 5DéfiPrévoir une modification du seul pointeur
- 6LienLire la position d’arrêt d’une conversion
- 7ExplicationUn tableau peut posséder des emplacements de pointeurs
- 8ExempleAvancer parmi les adresses
- 9ConseilVérifier le nombre d’arguments avant d’accéder aux chaînes
- 10DéfiLire le type d’une ligne entière
- 11DéfiAdapter une fonction à la zone de stockage fournie
- 12RécapitulatifNommer l’objet à chaque niveau
5Choisir une opération et trier avec une fonction de rappel
15 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionDistinguer une tâche générale de l’opération choisie
- 2ExplicationLire le type de fonction appelable
- 3ExempleSélectionner une autre fonction
- 4ConseilNommer un type pointeur pour une table
- 5DéfiLire une déclaration apparentée
- 6ExplicationFournir une fonction de rappel pour une action
- 7ExempleFournir les quatre arguments du tri
- 8ExplicationRetrouver le type entier des éléments
- 9ExempleRenvoyer le signe correspondant à l’ordre
- 10DéfiÉviter un raccourci qui provoque un débordement
- 11LienTrier des pointeurs vers des chaînes, pas des lignes de caractères
- 12DéfiChoisir la bonne taille pour le tri
- 13Point cléL’égalité ne garantit pas la stabilité
- 14DéfiSuivre une fonction de rappel de comparaison sélectionnée
- 15RécapitulatifFaire correspondre l’appel, la disposition et l’ordre
Pointeurs en C : QCM
Série 1 : Suivre les adresses et les appels de fonctions10 Questions Pratiques
Question 1 sur 10
int occupe 4 octets. Un tableau int levels[7] commence à l’adresse fictive 0x2400. À quelle adresse commence levels[4] ?Afficher la réponseMasquer la réponse
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
int items = 16;, quelle déclaration crée un pointeur vers items ?Afficher la réponseMasquer la réponse
&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;
Afficher la réponseMasquer la réponse
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);
Afficher la réponseMasquer la réponse
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);
Afficher la réponseMasquer la réponse
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);
Afficher la réponseMasquer la réponse
*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
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 ?Afficher la réponseMasquer la réponse
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
int x = 6, y = 17; const int *p = &x;, quelle affectation est autorisée ?Afficher la réponseMasquer la réponse
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;
Afficher la réponseMasquer la réponse
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);
Afficher la réponseMasquer la réponse
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
double values[8];, que vaut &values[6] - &values[2] ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
puts(message + 5) pour const char *message = "blue sky"; ?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;
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};
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};
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)++;
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;
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;
}
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
int a[6] commence à l’adresse fictive 0x3000 et chaque entier occupe 4 octets. Quelles adresses a + 1 et &a + 1 représentent-ils ?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++;
}
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
int n = 31; int *p = &n; int **pp = &p;, quel est le type de *pp ?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"};
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;
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
char *end; long n = strtol("57kg", &end, 10);, vers quoi end pointe-t-il après la conversion ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
./paint amber 6 dans l’organisation habituelle d’un environnement hébergé. Que valent argc et argv[2][0] ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
int board[2][5] de la fonction appelante après sa conversion habituelle en pointeur ?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);
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
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 ?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';
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
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
int (*filter)(int); ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
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 ?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);
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
int readings[8];, quel appel trie tous les éléments à l’aide d’une fonction de rappel compare compatible ?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));
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
int représentable, en ordre croissant. Quelle expression de retour satisfait cette exigence après lecture des éléments dans x et y ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
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é ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
{4, -4, 2, -2} avec qsort. Quel résultat est garanti ?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;
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
A
Adresse mémoire
0x2000, ses octets restants se suivent jusqu’à 0x2003.Ajustement d’un paramètre tableau en paramètre pointeur
int a[10] est un pointeur dans la fonction : sizeof a mesure donc ce pointeur.Aliasage
int *q = p;, les deux pointeurs atteignent le même entier jusqu’à ce qu’une affectation ultérieure redirige l’un d’eux.Alignement
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.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../sum 8 13, argv[2] atteint "13" ; l’argument est du texte avant sa conversion numérique.Arithmétique des pointeurs
sizeof.B
Bornes du tableau
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
0x12345678 sur quatre octets donne les octets 78 56 34 12 des adresses les plus basses aux plus hautes.C
Cadre de pile
Caractère nul
'\0', qui termine une chaîne C dans sa zone de stockage de caractères accessible.Comportement indéfini
Conversion de tableau en pointeur
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é
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
Déclarateur de pointeur
* avec un nom déclaré pour indiquer que l’objet nommé est de type pointeur.int *left, right;, seul left est un pointeur ; déclarer right comme pointeur nécessite une autre étoile.Déréférencement
* à un pointeur pour désigner l’objet pointé et permettre une lecture ou une écriture appropriée par ce chemin.int *p = &score;, l’affectation *p = 28; modifie le même entier que celui lu ensuite par score.Différence de pointeurs
&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
Durée de stockage automatique
Durée de stockage statique
E
Erreur de segmentation
F
Fonction de comparaison
(x > y) - (x < y) ordonne les entiers par ordre croissant sans soustraire les valeurs elles-mêmes.Fonction de rappel
I
Incrémentation postfixée
*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
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
int **pp, le premier déréférencement donne un int *, et le second atteint un objet entier.O
Octet
sizeof ; un char C occupe toujours un octet, même si les tailles des autres types dépendent du système.Opérateur d’adressage
& obtient l’adresse d’un objet adressable ou d’une fonction et produit un type pointeur correspondant.&values[2] fournit l’adresse du troisième élément sans modifier son contenu.Opérateur d’indexation
a[i] comme *(a + i) ; il utilise le type d’élément du pointeur pour appliquer le facteur de taille à la position.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
Paramètre pointeur
&score par int *out et écrit zéro dans l’entier de la fonction appelante avec *out.Passage par adresse
&x et &y modifie les variables de la fonction appelante en écrivant à travers les deux paramètres pointeurs locaux.Passage par valeur
int laisse l’entier de la fonction appelante inchangé ; une écriture à travers une adresse copiée peut le modifier.Pointeur
int *p = &count;, la variable de type pointeur stocke une adresse tandis que l’entier conserve sa propre valeur.Pointeur constant
int *const p = &x;, *p = 3; est autorisé, mais p = &y; ne peut pas rediriger le pointeur.Pointeur de fin de conversion
"73ms" avec strtol peut renvoyer 73 et faire pointer le pointeur de fin de la fonction appelante vers le m.Pointeur de fonction
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
int **pp = &p;, l’affectation *pp = &y; redirige p, tandis que l’affectation **pp = 8; modifie sa cible.Pointeur générique d’objet
void *, ou une forme qualifiée, pour contenir une adresse d’objet qui doit retrouver un type adapté avant l’accès.const void * en const int * avant de lire l’élément du tableau désigné.Pointeur juste après le dernier élément
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
NULL lorsqu’aucune correspondance n’existe ; la fonction appelante teste donc le résultat avant de le déréférencer.Pointeur pendant
Pointeur vers un objet constant
const int *p = &x; autorise p = &y;, tandis qu’une affectation comme *p = 3; est rejetée.Pointeur vers un tableau
int a[4], &a est de type int (*)[4] ; son pas arithmétique diffère de celui de &a[0].Priorité des opérateurs
++ 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.<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.sizeof names[0] pour la taille de l’emplacement, et non le nombre de caractères d’une chaîne.R
Rangement par lignes
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
sizeof
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.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 **.&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
Tableau à deux dimensions
int grid[2][5] est converti en int (*)[5] ; le passer comme int ** décrirait une disposition différente.Tableau de pointeurs
Tableau irrégulier
"red" et "violet" sans réserver des lignes de caractères de même taille pour les deux mots.Tranche de tableau
data + 2 avec un nombre de 3 sélectionne les indices initiaux de 2 à 4 sans les copier.Tri stable
qsort ne fournit pas cette garantie.typedef
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.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
int *p; ne permet pas au programme de tester, suivre ou utiliser ce pointeur pour écrire en toute sécurité.À propos de cette unité
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.