Pointeur (programmation informatique) - Pointer (computer programming)

Je considère que les déclarations d'affectation et les variables de pointeur font partie des "trésors les plus précieux" de l'informatique.

Donald Knuth , Programmation structurée, avec go to Statements

Pointeur a pointant sur l'adresse mémoire associée à la variable b . Dans ce diagramme, l'architecture informatique utilise le même espace d'adressage et la même primitive de données pour les pointeurs et les non-pointeurs ; ce besoin ne devrait pas être le cas.

En informatique , un pointeur est un objet dans de nombreux langages de programmation qui stocke une adresse mémoire . Cela peut être celui d'une autre valeur située dans la mémoire de l'ordinateur , ou dans certains cas, celle du matériel informatique mappé en mémoire . Un pointeur référence un emplacement en mémoire, et l'obtention de la valeur stockée à cet emplacement est appelée déréférencement du pointeur. Par analogie, un numéro de page dans l'index d'un livre pourrait être considéré comme un pointeur vers la page correspondante ; le déréférencement d'un tel pointeur se ferait en retournant à la page avec le numéro de page donné et en lisant le texte trouvé sur cette page. Le format et le contenu réels d'une variable de pointeur dépendent de l' architecture informatique sous-jacente .

L' utilisation des pointeurs améliore significativement les performances pour les opérations répétitives, comme la traversée itérables données structures (par exemple des chaînes , tables de consultation , tables de contrôle et d' arbres structures). En particulier, il est souvent beaucoup moins cher en temps et en espace de copier et de déréférencer des pointeurs que de copier et d'accéder aux données vers lesquelles les pointeurs pointent.

Les pointeurs sont également utilisés pour contenir les adresses des points d'entrée des sous-routines appelées dans la programmation procédurale et pour la liaison au moment de l'exécution vers les bibliothèques de liens dynamiques (DLL) . Dans la programmation orientée objet , des pointeurs vers des fonctions sont utilisés pour lier des méthodes , souvent à l'aide de tables de méthodes virtuelles .

Un pointeur est une implémentation simple et plus concrète du type de données de référence plus abstrait . Plusieurs langages, en particulier les langages de bas niveau , prennent en charge certains types de pointeurs, bien que certains aient plus de restrictions sur leur utilisation que d'autres. Alors que "pointeur" a été utilisé pour faire référence à des références en général, il s'applique plus correctement aux structures de données dont l' interface permet explicitement au pointeur d'être manipulé (arithmétiquement via arithmétique de pointeur ) comme adresse mémoire, par opposition à uncookie magiqueou à unecapacitéqui ne le permet pas. Étant donné que les pointeurs permettent à la fois un accès protégé et non protégé aux adresses mémoire, il existe des risques associés à leur utilisation, en particulier dans ce dernier cas. Les pointeurs primitifs sont souvent stockés dans un format similaire à unentier; cependant, tenter de déréférencer ou de « rechercher » un tel pointeur dont la valeur n'est pas une adresse mémoire valide peut provoquer leblocage d'un programme(ou contenir des données non valides). Pour atténuer ce problème potentiel, pour desraisons de sécuritédetype, les pointeurs sont considérés comme un type distinct paramétré par le type de données vers lequel ils pointent, même si la représentation sous-jacente est un entier. D'autres mesures peuvent également être prises (telles que lavalidationet lavérification des limites), pour vérifier que la variable de pointeur contient une valeur qui est à la fois une adresse mémoire valide et dans la plage numérique que le processeur est capable d'adresser.

Histoire

En 1955, l'informaticienne soviétique Kateryna Iouchtchenko a inventé le langage de programmation d'adresses qui a rendu possible l'adressage indirect et les adresses de rang le plus élevé - analogues aux pointeurs. Ce langage était largement utilisé sur les ordinateurs de l'Union soviétique. Cependant, il était inconnu en dehors de l'Union soviétique et généralement Harold Lawson est crédité de l'invention, en 1964, du pointeur. En 2000, Lawson a reçu le Computer Pioneer Award par l' IEEE "[f]or inventer la variable pointeur et introduire ce concept dans PL/I, offrant ainsi pour la première fois, la capacité de traiter de manière flexible les listes chaînées dans un langage de haut niveau". Son article fondateur sur les concepts est paru dans le numéro de juin 1967 de CACM intitulé : PL/I List Processing. Selon l' Oxford English Dictionary , le mot pointeur est apparu pour la première fois sur papier sous forme de pointeur de pile dans un mémorandum technique de la System Development Corporation .

Description formelle

En informatique , un pointeur est une sorte de référence .

Une primitive de données (ou simplement primitive ) est une donnée qui peut être lue ou écrite dans la mémoire de l'ordinateur en utilisant un accès mémoire (par exemple, un octet et un mot sont des primitives).

Un agrégat de données (ou simplement un agrégat ) est un groupe de primitives qui sont logiquement contiguës en mémoire et qui sont considérées collectivement comme une seule donnée (par exemple, un agrégat pourrait être 3 octets logiquement contigus, dont les valeurs représentent les 3 coordonnées d'un point dans l'espace). Lorsqu'un agrégat est entièrement composé du même type de primitive, l'agrégat peut être appelé tableau ; dans un sens, une primitive de mot multi-octets est un tableau d'octets, et certains programmes utilisent des mots de cette manière.

Dans le contexte de ces définitions, un octet est la plus petite primitive ; chaque adresse mémoire spécifie un octet différent. L'adresse mémoire de l'octet initial d'une donnée est considérée comme l'adresse mémoire (ou adresse mémoire de base ) de toute la donnée.

Un pointeur mémoire (ou simplement pointeur ) est une primitive dont la valeur est destinée à être utilisée comme adresse mémoire ; on dit qu'un pointeur pointe sur une adresse mémoire . On dit aussi qu'un pointeur pointe vers une donnée [en mémoire] lorsque la valeur du pointeur est l'adresse mémoire de la donnée.

Plus généralement, un pointeur est une sorte de référence , et on dit qu'un pointeur référence une donnée stockée quelque part en mémoire ; obtenir cette donnée revient à déréférencer le pointeur . La caractéristique qui sépare les pointeurs des autres types de référence est que la valeur d'un pointeur est censée être interprétée comme une adresse mémoire, ce qui est un concept de niveau plutôt bas.

Les références servent de niveau d'indirection : la valeur d'un pointeur détermine quelle adresse mémoire (c'est-à-dire quelle donnée) doit être utilisée dans un calcul. L'indirection étant un aspect fondamental des algorithmes, les pointeurs sont souvent exprimés comme un type de données fondamental dans les langages de programmation ; dans les langages de programmation à typage statique (ou fortement ), le type d'un pointeur détermine le type de la donnée vers laquelle pointe le pointeur.

Racines architecturales

Les pointeurs sont une abstraction très fine qui s'ajoute aux capacités d'adressage fournies par la plupart des architectures modernes . Dans le schéma le plus simple, une adresse ou un index numérique est attribué à chaque unité de mémoire du système, où l'unité est généralement soit un octet, soit un mot - selon que l'architecture est adressable par octet ou par mot - transformer efficacement toute la mémoire en un très grand tableau . Le système fournirait alors également une opération pour récupérer la valeur stockée dans l'unité de mémoire à une adresse donnée (en utilisant généralement les registres à usage général de la machine ).

Dans le cas habituel, un pointeur est suffisamment grand pour contenir plus d'adresses qu'il n'y a d'unités de mémoire dans le système. Cela introduit la possibilité qu'un programme tente d'accéder à une adresse qui ne correspond à aucune unité de mémoire, soit parce qu'il n'y a pas assez de mémoire installée (c'est-à-dire au-delà de la plage de mémoire disponible) ou parce que l'architecture ne prend pas en charge de telles adresses. Le premier cas peut, dans certaines plates-formes telles que l' architecture Intel x86 , être appelé un défaut de segmentation (segfault). Le deuxième cas est possible dans l'implémentation actuelle d' AMD64 , où les pointeurs ont une longueur de 64 bits et les adresses ne s'étendent que sur 48 bits. Les pointeurs doivent se conformer à certaines règles (adresses canoniques), donc si un pointeur non canonique est déréférencé, le processeur lève une faute de protection générale .

D'autre part, certains systèmes ont plus d'unités de mémoire qu'il n'y a d'adresses. Dans ce cas, un schéma plus complexe tel que la segmentation de la mémoire ou la pagination est utilisé pour utiliser différentes parties de la mémoire à différents moments. Les dernières incarnations de l'architecture x86 prennent en charge jusqu'à 36 bits d'adresses de mémoire physique, qui ont été mappées à l'espace d'adressage linéaire de 32 bits via le mécanisme de pagination PAE . Ainsi, seul 1/16 de la mémoire totale possible peut être accédé à la fois. Un autre exemple dans la même famille d'ordinateurs était le mode protégé 16 bits du processeur 80286 , qui, bien que ne prenant en charge que 16 Mo de mémoire physique, pouvait accéder jusqu'à 1 Go de mémoire virtuelle, mais la combinaison d'adresses et de segments 16 bits les registres ont rendu l'accès à plus de 64 Ko dans une structure de données encombrant.

Afin de fournir une interface cohérente, certaines architectures fournissent des E/S mappées en mémoire , ce qui permet à certaines adresses de faire référence à des unités de mémoire tandis que d'autres font référence à des registres de périphériques d'autres périphériques de l'ordinateur. Il existe des concepts analogues tels que les décalages de fichiers, les indices de tableau et les références d'objets distants qui remplissent les mêmes fonctions que les adresses pour d'autres types d'objets.

Les usages

Les pointeurs sont directement pris en charge sans restrictions dans des langages tels que PL/I , C , C++ , Pascal , FreeBASIC et implicitement dans la plupart des langages assembleur . Ils sont principalement utilisés pour construire des références , qui à leur tour sont fondamentales pour construire presque toutes les structures de données , ainsi que pour transmettre des données entre différentes parties d'un programme.

Dans les langages de programmation fonctionnels qui reposent fortement sur des listes, les références de données sont gérées de manière abstraite en utilisant des constructions primitives telles que cons et les éléments correspondants car et cdr , qui peuvent être considérés comme des pointeurs spécialisés vers les premier et deuxième composants d'une contre-cellule. Cela donne naissance à une partie de la « saveur » idiomatique de la programmation fonctionnelle. En structurant les données dans de telles contre-listes , ces langages facilitent les moyens récursifs de construction et de traitement des données, par exemple en accédant de manière récursive aux éléments de tête et de queue de listes de listes ; par exemple "prendre la voiture du cdr du cdr". En revanche, la gestion de la mémoire basée sur le déréférencement des pointeurs dans une certaine approximation d'un tableau d'adresses mémoire facilite le traitement des variables comme des slots dans lesquels des données peuvent être affectées de manière impérative .

Lorsqu'il s'agit de tableaux, l' opération de recherche critique implique généralement une étape appelée calcul d'adresse qui implique la construction d'un pointeur vers l'élément de données souhaité dans le tableau. Dans d'autres structures de données, telles que les listes chaînées , les pointeurs sont utilisés comme références pour lier explicitement une partie de la structure à une autre.

Les pointeurs sont utilisés pour passer des paramètres par référence. Ceci est utile si le programmeur veut que les modifications d'une fonction à un paramètre soient visibles pour l'appelant de la fonction. Ceci est également utile pour renvoyer plusieurs valeurs à partir d'une fonction.

Les pointeurs peuvent également être utilisés pour allouer et désallouer des variables dynamiques et des tableaux en mémoire. Puisqu'une variable deviendra souvent redondante une fois qu'elle aura atteint son objectif, c'est une perte de mémoire de la conserver, et c'est donc une bonne pratique de la désallouer (en utilisant la référence de pointeur d'origine) lorsqu'elle n'est plus nécessaire. Ne pas le faire peut entraîner une fuite de mémoire (là où la mémoire libre disponible diminue progressivement, ou dans les cas graves rapidement, en raison d'une accumulation de nombreux blocs de mémoire redondants).

pointeurs C

La syntaxe de base pour définir un pointeur est :

int *ptr;

Ceci déclare ptrcomme identifiant d'un objet du type suivant :

  • pointeur qui pointe vers un objet de type int

Ceci est généralement indiqué plus succinctement comme " ptrest un pointeur vers int."

Comme le langage C ne spécifie pas d'initialisation implicite pour les objets de durée de stockage automatique, il faut souvent veiller à ce que l'adresse vers laquelle ptrpointe est valide ; c'est pourquoi il est parfois suggéré d'initialiser explicitement un pointeur à la valeur nulle du pointeur , ce qui est traditionnellement spécifié en C avec la macro standardisée NULL:

int *ptr = NULL;

Le déréférencement d'un pointeur nul en C produit un comportement indéfini , ce qui pourrait être catastrophique. Cependant, la plupart des implémentations interrompent simplement l'exécution du programme en question, généralement avec une erreur de segmentation .

Cependant, initialiser des pointeurs inutilement pourrait gêner l'analyse du programme, cachant ainsi des bogues.

Dans tous les cas, une fois qu'un pointeur a été déclaré, la prochaine étape logique est qu'il pointe vers quelque chose :

int a = 5;
int *ptr = NULL;

ptr = &a;

Cela affecte la valeur de l'adresse de aà ptr. Par exemple, si aest stocké à l'emplacement mémoire 0x8130, la valeur de ptrsera 0x8130 après l'affectation. Pour déréférencer le pointeur, un astérisque est à nouveau utilisé :

*ptr = 8;

Cela signifie prendre le contenu de ptr(qui est 0x8130), "localiser" cette adresse en mémoire et définir sa valeur sur 8. Si vous ay accédez à nouveau ultérieurement, sa nouvelle valeur sera 8.

Cet exemple peut être plus clair si la mémoire est examinée directement. Supposons qu'il ase trouve à l'adresse 0x8130 en mémoire et ptrà 0x8134 ; supposez également qu'il s'agit d'une machine 32 bits telle qu'un int a une largeur de 32 bits. Voici ce qui serait en mémoire après l'exécution de l'extrait de code suivant :

int a = 5;
int *ptr = NULL;
Adresse Contenu
0x8130 0x00000005
0x8134 0x00000000

(Le pointeur NULL montré ici est 0x00000000.) En attribuant l'adresse de aà ptr:

 ptr = &a;

renvoie les valeurs de mémoire suivantes :

Adresse Contenu
0x8130 0x00000005
0x8134 0x00008130

Puis par déréférencement ptrpar codage :

 *ptr = 8;

l'ordinateur prendra le contenu de ptr(qui est 0x8130), 'localisera' cette adresse, et affectera 8 à cet emplacement produisant la mémoire suivante :

Adresse Contenu
0x8130 0x00000008
0x8134 0x00008130

Clairement, l'accès adonnera la valeur 8 car l'instruction précédente a modifié le contenu de aau moyen du pointeur ptr.

Utilisation dans les structures de données

Lors de la configuration de structures de données telles que des listes , des files d'attente et des arborescences, il est nécessaire d'avoir des pointeurs pour aider à gérer la manière dont la structure est implémentée et contrôlée. Des exemples typiques de pointeurs sont les pointeurs de début, les pointeurs de fin et les pointeurs de pile . Ces pointeurs peuvent être absolus (l' adresse physique réelle ou une adresse virtuelle dans la mémoire virtuelle ) ou relatifs (un décalage à partir d'une adresse de début absolue ("base") qui utilise généralement moins de bits qu'une adresse complète, mais nécessitera généralement un opération arithmétique à résoudre).

Les adresses relatives sont une forme de segmentation manuelle de la mémoire et partagent bon nombre de ses avantages et inconvénients. Un décalage de deux octets, contenant un entier non signé de 16 bits, peut être utilisé pour fournir un adressage relatif jusqu'à 64 Ko (2 16 octets) d'une structure de données. Cela peut facilement être étendu à 128, 256 ou 512 Ko si l'adresse pointée est forcée d'être alignée sur une limite de demi-mot, de mot ou de double mot (mais, nécessitant une opération supplémentaire "décaler à gauche" au niveau du bit - de 1, 2 ou 3 bits - afin d'ajuster l'offset d'un facteur 2, 4 ou 8, avant son ajout à l'adresse de base). En général, cependant, de tels schémas posent beaucoup de problèmes et, pour plus de commodité pour le programmeur, les adresses absolues (et sous-jacentes, un espace d'adressage plat ) sont préférées.

Un décalage d'un octet, tel que la valeur ASCII hexadécimale d'un caractère (par exemple X'29') peut être utilisé pour pointer vers une autre valeur entière (ou index) dans un tableau (par exemple, X'01'). De cette façon, les caractères peuvent être très efficacement traduits de « données brutes » en un index séquentiel utilisable , puis en une adresse absolue sans table de recherche .

tableaux C

En C, l'indexation des tableaux est formellement définie en termes d'arithmétique de pointeur ; c'est-à-dire que la spécification du langage exige que array[i]soit équivalent à *(array + i). Ainsi, en C, les tableaux peuvent être considérés comme des pointeurs vers des zones de mémoire consécutives (sans lacunes), et la syntaxe pour accéder aux tableaux est identique à celle qui peut être utilisée pour déréférencer les pointeurs. Par exemple, un tableau arraypeut être déclaré et utilisé de la manière suivante :

int array[5];      /* Declares 5 contiguous integers */
int *ptr = array;  /* Arrays can be used as pointers */
ptr[0] = 1;        /* Pointers can be indexed with array syntax */
*(array + 1) = 2;  /* Arrays can be dereferenced with pointer syntax */
*(1 + array) = 2;  /* Pointer addition is commutative */
array[2] = 4;      /* Subscript operator is commutative */

Cela alloue un bloc de cinq entiers et nomme le bloc array, qui agit comme un pointeur vers le bloc. Une autre utilisation courante des pointeurs consiste à pointer vers la mémoire allouée dynamiquement à partir de malloc qui renvoie un bloc de mémoire consécutif d'au moins la taille demandée qui peut être utilisé comme un tableau.

Alors que la plupart des opérateurs sur les tableaux et les pointeurs sont équivalents, le résultat de l' sizeofopérateur diffère. Dans cet exemple, sizeof(array)évaluera à 5*sizeof(int)(la taille du tableau), tandis sizeof(ptr)qu'évaluera à sizeof(int*), la taille du pointeur lui-même.

Les valeurs par défaut d'un tableau peuvent être déclarées comme :

int array[5] = {2, 4, 3, 1, 5};

Si arrayest situé dans la mémoire à partir de l'adresse 0x1000 sur une machine little-endian 32 bits, la mémoire contiendra les éléments suivants (les valeurs sont en hexadécimal , comme les adresses) :

0 1 2 3
1000 2 0 0 0
1004 4 0 0 0
1008 3 0 0 0
100C 1 0 0 0
1010 5 0 0 0

Représentés ici sont cinq entiers : 2, 4, 3, 1 et 5. Ces cinq entiers occupent 32 bits (4 octets) chacun avec l'octet le moins significatif stocké en premier (il s'agit d'une architecture CPU little-endian ) et sont stockés consécutivement à partir de l'adresse 0x1000.

La syntaxe pour C avec des pointeurs est :

  • array signifie 0x1000;
  • array + 1signifie 0x1004 : le " + 1 " signifie ajouter la taille de 1 int, qui est de 4 octets ;
  • *arraysignifie déréférencer le contenu de array. En considérant le contenu comme une adresse mémoire (0x1000), recherchez la valeur à cet emplacement (0x0002) ;
  • array[i]signifie le numéro d'élément i, basé sur 0, arrayqui est traduit en *(array + i).

Le dernier exemple est de savoir comment accéder au contenu de array. Le décomposer :

  • array + iest l'emplacement mémoire du (i) ième élément de array, en commençant à i=0;
  • *(array + i) prend cette adresse mémoire et la déréférence pour accéder à la valeur.

C liste chaînée

Vous trouverez ci-dessous un exemple de définition d'une liste chaînée en C.

/* the empty linked list is represented by NULL
 * or some other sentinel value */
#define EMPTY_LIST  NULL

struct link {
    void        *data;  /* data of this link */
    struct link *next;  /* next link; EMPTY_LIST if there is none */
};

Cette définition récursive de pointeur est essentiellement la même que la définition récursive de référence du langage de programmation Haskell :

 data Link a = Nil
             | Cons a (Link a)

Nilest la liste vide, et Cons a (Link a)est une contre cellule de type aavec un autre lien également de type a.

La définition avec des références, cependant, est vérifiée de type et n'utilise pas de valeurs de signal potentiellement déroutantes. Pour cette raison, les structures de données en C sont généralement traitées via des fonctions wrapper , dont l'exactitude est soigneusement vérifiée.

Adresse de passage à l'aide de pointeurs

Les pointeurs peuvent être utilisés pour passer des variables par leur adresse, permettant de changer leur valeur. Par exemple, considérons le code C suivant :

/* a copy of the int n can be changed within the function without affecting the calling code */
void passByValue(int n) {
    n = 12;
}

/* a pointer m is passed instead. No copy of the value pointed to by m is created */
void passByAddress(int *m) {
    *m = 14;
}

int main(void) {
    int x = 3;

    /* pass a copy of x's value as the argument */
    passByValue(x);
    // the value was changed inside the function, but x is still 3 from here on

    /* pass x's address as the argument */
    passByAddress(&x);
    // x was actually changed by the function and is now equal to 14 here

    return 0;
}

Allocation dynamique de mémoire

Dans certains programmes, la mémoire requise dépend de ce que l'utilisateur peut entrer. Dans de tels cas, le programmeur doit allouer de la mémoire de manière dynamique. Cela se fait en allouant de la mémoire sur le tas plutôt que sur la pile , où les variables sont généralement stockées (les variables peuvent également être stockées dans les registres du processeur, mais c'est une autre affaire). L'allocation dynamique de mémoire ne peut se faire que par des pointeurs, et les noms (comme avec les variables communes) ne peuvent pas être donnés.

Les pointeurs sont utilisés pour stocker et gérer les adresses de blocs de mémoire alloués dynamiquement . De tels blocs sont utilisés pour stocker des objets de données ou des tableaux d'objets. La plupart des langages structurés et orientés objet fournissent une zone de mémoire, appelée tas ou magasin libre , à partir de laquelle les objets sont alloués dynamiquement.

L'exemple de code C ci-dessous illustre comment les objets de structure sont alloués et référencés dynamiquement. La bibliothèque C standard fournit la fonction malloc()d'allocation de blocs de mémoire à partir du tas. Il prend la taille d'un objet à allouer en tant que paramètre et renvoie un pointeur vers un bloc de mémoire nouvellement alloué adapté au stockage de l'objet, ou il retourne un pointeur nul si l'allocation a échoué.

/* Parts inventory item */
struct Item {
    int         id;     /* Part number */
    char *      name;   /* Part name   */
    float       cost;   /* Cost        */
};

/* Allocate and initialize a new Item object */
struct Item * make_item(const char *name) {
    struct Item * item;

    /* Allocate a block of memory for a new Item object */
    item = malloc(sizeof(struct Item));
    if (item == NULL)
        return NULL;

    /* Initialize the members of the new Item */
    memset(item, 0, sizeof(struct Item));
    item->id =   -1;
    item->name = NULL;
    item->cost = 0.0;

    /* Save a copy of the name in the new Item */
    item->name = malloc(strlen(name) + 1);
    if (item->name == NULL) {
        free(item);
        return NULL;
    }
    strcpy(item->name, name);

    /* Return the newly created Item object */
    return item;
}

Le code ci-dessous illustre comment les objets mémoire sont désalloués dynamiquement, c'est-à-dire renvoyés au tas ou au magasin libre. La bibliothèque C standard fournit la fonction free()de désallocation d'un bloc de mémoire précédemment alloué et de le renvoyer dans le tas.

/* Deallocate an Item object */
void destroy_item(struct Item *item) {
    /* Check for a null object pointer */
    if (item == NULL)
        return;

    /* Deallocate the name string saved within the Item */
    if (item->name != NULL) {
        free(item->name);
        item->name = NULL;
    }

    /* Deallocate the Item object itself */
    free(item);
}

Matériel mappé en mémoire

Sur certaines architectures informatiques, les pointeurs peuvent être utilisés pour manipuler directement la mémoire ou les périphériques mappés en mémoire.

L'attribution d'adresses à des pointeurs est un outil précieux lors de la programmation de microcontrôleurs . Ci-dessous un exemple simple déclarant un pointeur de type int et l'initialisant à une adresse hexadécimale dans cet exemple la constante 0x7FFF :

int *hardware_address = (int *)0x7FFF;

Au milieu des années 80, l'utilisation du BIOS pour accéder aux capacités vidéo des PC était lente. Les applications gourmandes en affichage étaient généralement utilisées pour accéder directement à la mémoire vidéo CGA en transtypant la constante hexadécimale 0xB8000 en un pointeur vers un tableau de 80 valeurs int 16 bits non signées. Chaque valeur se composait d'un code ASCII dans l'octet de poids faible et d'une couleur dans l'octet de poids fort. Ainsi, pour mettre la lettre « A » à la ligne 5, colonne 2 en blanc brillant sur bleu, on écrirait un code comme celui-ci :

#define VID ((unsigned short (*)[80])0xB8000)

void foo(void) {
    VID[4][1] = 0x1F00 | 'A';
}

Utilisation dans les tables de contrôle

Les tables de contrôle utilisées pour contrôler le déroulement du programme font généralement un usage intensif des pointeurs. Les pointeurs, généralement intégrés dans une entrée de table, peuvent, par exemple, être utilisés pour contenir les points d'entrée de sous-programmes à exécuter, sur la base de certaines conditions définies dans la même entrée de table. Les pointeurs peuvent cependant être simplement des index vers d'autres tables séparées, mais associées, comprenant un tableau des adresses réelles ou des adresses elles-mêmes (selon les constructions de langage de programmation disponibles). Ils peuvent également être utilisés pour pointer vers des entrées de table antérieures (comme dans le traitement en boucle) ou vers l'avant pour ignorer certaines entrées de table (comme dans un commutateur ou une sortie "précoce" d'une boucle). A cette dernière fin, le "pointeur" peut être simplement le numéro d'entrée de la table lui-même et peut être transformé en une adresse réelle par simple arithmétique.

Pointeurs dactylographiés et casting

Dans de nombreuses langues, les pointeurs ont la restriction supplémentaire que l'objet vers lequel ils pointent a un type spécifique . Par exemple, un pointeur peut être déclaré pour pointer sur un entier ; le langage tentera alors d'empêcher le programmeur de le pointer sur des objets qui ne sont pas des entiers, tels que des nombres à virgule flottante , éliminant ainsi certaines erreurs.

Par exemple, en C

int *money;
char *bags;

moneyserait un pointeur entier et bagsserait un pointeur char. Ce qui suit produirait un avertissement du compilateur de "affectation à partir d'un type de pointeur incompatible" sous GCC

bags = money;

parce que moneyet bagsont été déclarés avec différents types. Pour supprimer l'avertissement du compilateur, il doit être explicite que vous ne souhaitez en effet effectuer la cession par typecasting il

bags = (char *)money;

qui dit de convertir le pointeur entier de moneyen un pointeur de caractère et de l'affecter à bags.

Un projet de norme C de 2005 exige que la conversion d'un pointeur dérivé d'un type vers l'un d'un autre type maintienne l'exactitude de l'alignement pour les deux types (6.3.2.3 Pointeurs, par. 7) :

char *external_buffer = "abcdef";
int *internal_data;

internal_data = (int *)external_buffer;  // UNDEFINED BEHAVIOUR if "the resulting pointer
                                         // is not correctly aligned"

Dans les langages qui autorisent l'arithmétique des pointeurs, l'arithmétique sur les pointeurs prend en compte la taille du type. Par exemple, l'ajout d'un nombre entier à un pointeur produit un autre pointeur qui pointe vers une adresse supérieure de ce nombre à la taille du type. Cela nous permet de calculer facilement l'adresse des éléments d'un tableau d'un type donné, comme cela a été montré dans l'exemple des tableaux C ci-dessus. Lorsqu'un pointeur d'un type est converti en un autre type de taille différente, le programmeur doit s'attendre à ce que l'arithmétique du pointeur soit calculée différemment. En C, par exemple, si le moneytableau commence à 0x2000 et sizeof(int)est de 4 octets alors qu'il sizeof(char)est de 1 octet, alors money + 1pointera vers 0x2004, mais bags + 1pointera vers 0x2001. Les autres risques de transtypage incluent la perte de données lorsque des données "larges" sont écrites dans des emplacements "étroits" (par exemple, bags[0] = 65537;), des résultats inattendus lors du décalage de valeurs et des problèmes de comparaison, en particulier avec des valeurs signées et non signées.

Bien qu'il soit en général impossible de déterminer au moment de la compilation quels transtypages sont sûrs, certains langages stockent des informations de type à l'exécution qui peuvent être utilisées pour confirmer que ces transtypages dangereux sont valides au moment de l'exécution. D'autres langages acceptent simplement une approximation conservatrice des moulages sûrs, ou pas du tout.

Valeur des pointeurs

En C et C++, le résultat de la comparaison entre les pointeurs n'est pas défini. Dans ces langages et LLVM , la règle est interprétée comme signifiant que "juste parce que deux pointeurs pointent vers la même adresse, ne signifie pas qu'ils sont égaux et peuvent être utilisés de manière interchangeable", la différence entre les pointeurs fait référence à leur provenance . Bien que la conversion en un type entier tel que la uintptr_tcomparaison d'offres, la conversion elle-même est définie par l'implémentation. De plus, une conversion supplémentaire en octets et en arithmétique perturbera les optimiseurs qui tentent de suivre l'utilisation des pointeurs, un problème encore en cours d'élucidation dans la recherche universitaire.

Rendre les pointeurs plus sûrs

Comme un pointeur permet à un programme de tenter d'accéder à un objet qui peut ne pas être défini, les pointeurs peuvent être à l'origine de diverses erreurs de programmation . Cependant, l'utilité des pointeurs est si grande qu'il peut être difficile d'effectuer des tâches de programmation sans eux. Par conséquent, de nombreux langages ont créé des constructions conçues pour fournir certaines des fonctionnalités utiles des pointeurs sans certains de leurs pièges , également parfois appelés dangers des pointeurs . Dans ce contexte, les pointeurs qui s'adressent directement à la mémoire (tels qu'utilisés dans cet article) sont appelésraw pointer s, contrairement auxpointeurs intelligentsou à d'autres variantes.

Un problème majeur avec les pointeurs est que tant qu'ils peuvent être directement manipulés comme un nombre, ils peuvent être amenés à pointer vers des adresses inutilisées ou vers des données qui sont utilisées à d'autres fins. De nombreux langages, y compris la plupart des langages de programmation fonctionnels et des langages impératifs récents comme Java , remplacent les pointeurs par un type de référence plus opaque, généralement appelé simplement une référence , qui ne peut être utilisé que pour faire référence à des objets et non manipulé comme des nombres, empêchant cela type d'erreur. L'indexation des tableaux est traitée comme un cas particulier.

Un pointeur auquel aucune adresse n'est assignée est appelé un pointeur sauvage . Toute tentative d'utilisation de ces pointeurs non initialisés peut provoquer un comportement inattendu, soit parce que la valeur initiale n'est pas une adresse valide, soit parce que son utilisation peut endommager d'autres parties du programme. Le résultat est souvent une erreur de segmentation , une violation de stockage ou un branchement sauvage (si utilisé comme pointeur de fonction ou adresse de branchement).

Dans les systèmes avec allocation de mémoire explicite, il est possible de créer un pointeur pendant en désallouant la région de mémoire dans laquelle il pointe. Ce type de pointeur est dangereux et subtil car une région de mémoire désallouée peut contenir les mêmes données qu'avant d'être désallouées, mais peut ensuite être réallouée et écrasée par du code non lié, inconnu du code précédent. Les langages avec garbage collection évitent ce type d'erreur car la désallocation est effectuée automatiquement lorsqu'il n'y a plus de références dans la portée.

Certains langages, comme C++ , prennent en charge les pointeurs intelligents , qui utilisent une forme simple de comptage de références pour aider à suivre l'allocation de mémoire dynamique en plus de servir de référence. En l'absence de cycles de référence, où un objet se réfère indirectement à lui-même via une séquence de pointeurs intelligents, ceux-ci éliminent la possibilité de pointeurs suspendus et de fuites de mémoire. Les chaînes Delphi prennent en charge le comptage de références de manière native.

Le langage de programmation Rust introduit un vérificateur d'emprunt , des durées de vie de pointeur et une optimisation basée sur des types facultatifs pour les pointeurs nuls afin d'éliminer les bogues de pointeur, sans recourir au ramasse-miettes .

Types spéciaux de pointeurs

Types définis par valeur

Pointeur nul

Un pointeur nul a une valeur réservée pour indiquer que le pointeur ne fait pas référence à un objet valide. Les pointeurs nuls sont couramment utilisés pour représenter des conditions telles que la fin d'une liste de longueur inconnue ou l'échec d'effectuer une action ; cette utilisation de pointeurs null peut être comparée aux types nullables et à la valeur Nothing dans un type d'option .

Pointeur pendant

Un pointeur suspendu est un pointeur qui ne pointe pas vers un objet valide et peut par conséquent faire planter un programme ou se comporter de manière étrange. Dans les langages de programmation Pascal ou C , les pointeurs qui ne sont pas spécifiquement initialisés peuvent pointer vers des adresses imprévisibles en mémoire.

L'exemple de code suivant montre un pointeur pendant :

int func(void) {
    char *p1 = malloc(sizeof(char)); /* (undefined) value of some place on the heap */
    char *p2;       /* dangling (uninitialized) pointer */
    *p1 = 'a';      /* This is OK, assuming malloc() has not returned NULL. */
    *p2 = 'b';      /* This invokes undefined behavior */
}

Ici, p2peut pointer n'importe où dans la mémoire, donc l'exécution de l'affectation *p2 = 'b';peut corrompre une zone inconnue de la mémoire ou déclencher une erreur de segmentation .

Branche sauvage

Lorsqu'un pointeur est utilisé comme adresse du point d'entrée d'un programme ou d'un début de fonction qui ne renvoie rien et est également soit non initialisé soit corrompu, si un appel ou un saut est néanmoins effectué à cette adresse, un " wild branch " aurait eu lieu. En d'autres termes, une branche sauvage est un pointeur de fonction qui est sauvage (pendant).

Les conséquences sont généralement imprévisibles et l'erreur peut se présenter de plusieurs manières différentes selon que le pointeur est ou non une adresse « valide » et s'il existe ou non (par coïncidence) une instruction (opcode) valide à cette adresse. La détection d'une branche sauvage peut présenter l'un des exercices de débogage les plus difficiles et les plus frustrants, car une grande partie des preuves peut déjà avoir été détruite au préalable ou par l'exécution d'une ou plusieurs instructions inappropriées à l'emplacement de la branche. S'il est disponible, un simulateur de jeu d'instructions peut généralement non seulement détecter une branche sauvage avant qu'elle ne prenne effet, mais également fournir une trace complète ou partielle de son historique.

Types définis par la structure

Pointeur autorelatif

Un pointeur autorelatif est un pointeur dont la valeur est interprétée comme un décalage par rapport à l'adresse du pointeur lui-même ; ainsi, si une structure de données a un membre pointeur autorelatif qui pointe vers une partie de la structure de données elle-même, alors la structure de données peut être déplacée en mémoire sans avoir à mettre à jour la valeur du pointeur autorelatif.

Le brevet cité utilise également le terme pointeur auto-relatif pour signifier la même chose. Cependant, le sens de ce terme a été utilisé d'autres manières :

  • signifier un décalage par rapport à l'adresse d'une structure plutôt qu'à partir de l'adresse du pointeur lui-même ;
  • pour signifier un pointeur contenant sa propre adresse, qui peut être utile pour reconstruire dans n'importe quelle région arbitraire de la mémoire une collection de structures de données qui pointent les unes vers les autres.

Pointeur basé

Un pointeur basé est un pointeur dont la valeur est un décalage par rapport à la valeur d'un autre pointeur. Cela peut être utilisé pour stocker et charger des blocs de données, en attribuant l'adresse du début du bloc au pointeur de base.

Types définis par utilisation ou type de données

Indirection multiple

Dans certaines langues, un pointeur peut référencer un autre pointeur, ce qui nécessite plusieurs opérations de déréférencement pour atteindre la valeur d'origine. Bien que chaque niveau d'indirection puisse ajouter un coût de performance, il est parfois nécessaire afin de fournir un comportement correct pour des structures de données complexes . Par exemple, en C, il est courant de définir une liste chaînée en termes d'élément qui contient un pointeur vers l'élément suivant de la liste :

struct element {
    struct element *next;
    int            value;
};

struct element *head = NULL;

Cette implémentation utilise un pointeur vers le premier élément de la liste comme substitut pour toute la liste. Si une nouvelle valeur est ajoutée au début de la liste, headelle doit être modifiée pour pointer vers le nouvel élément. Étant donné que les arguments C sont toujours passés par valeur, l'utilisation de la double indirection permet à l'insertion d'être implémentée correctement et a l'effet secondaire souhaitable d'éliminer le code de cas spécial pour traiter les insertions au début de la liste :

// Given a sorted list at *head, insert the element item at the first
// location where all earlier elements have lesser or equal value.
void insert(struct element **head, struct element *item) {
    struct element **p;  // p points to a pointer to an element
    for (p = head; *p != NULL; p = &(*p)->next) {
        if (item->value <= (*p)->value)
            break;
    }
    item->next = *p;
    *p = item;
}

// Caller does this:
insert(&head, item);

Dans ce cas, si la valeur de itemest inférieure à celle de head, celle de l'appelant headest correctement mise à jour à l'adresse du nouvel élément.

Un exemple de base se trouve dans le argv argument de la fonction principale en C (et C ++) , qui est donnée dans le prototype comme char **argv-Ceci est parce que la variable argvelle-même est un pointeur sur un tableau de chaînes (un tableau de tableaux), donc *argvest un pointeur vers la 0e chaîne (par convention le nom du programme), et **argvest le 0e caractère de la 0e chaîne.

Pointeur de fonction

Dans certains langages, un pointeur peut référencer du code exécutable, c'est-à-dire qu'il peut pointer vers une fonction, une méthode ou une procédure. Un pointeur de fonction stockera l'adresse d'une fonction à appeler. Bien que cette fonctionnalité puisse être utilisée pour appeler des fonctions de manière dynamique, il s'agit souvent d'une technique préférée des auteurs de virus et autres logiciels malveillants.

int sum(int n1, int n2) {   // Function with two integer parameters returning an integer value
    return n1 + n2;
}

int main(void) {
    int a, b, x, y;
    int (*fp)(int, int);    // Function pointer which can point to a function like sum
    fp = &sum;              // fp now points to function sum
    x = (*fp)(a, b);        // Calls function sum with arguments a and b
    y = sum(a, b);          // Calls function sum with arguments a and b
}

Pointeur arrière

Dans les listes doublement chaînées ou les arborescences , un pointeur arrière maintenu sur un élément « pointe en arrière » vers l'élément faisant référence à l'élément actuel. Ceux-ci sont utiles pour la navigation et la manipulation, au détriment d'une plus grande utilisation de la mémoire.

Simulation à l'aide d'un index de tableau

Il est possible de simuler le comportement d'un pointeur en utilisant un index vers un tableau (normalement unidimensionnel).

Principalement pour les langages qui ne prennent pas en charge les pointeurs explicitement mais prennent en charge les tableaux, le tableau peut être considéré et traité comme s'il s'agissait de toute la plage de mémoire (dans la portée du tableau particulier) et tout index peut être considéré comme équivalent à un registre à usage général en langage assembleur (qui pointe vers les octets individuels mais dont la valeur réelle est relative au début du tableau, pas son adresse absolue en mémoire). En supposant que le tableau est, disons, une structure de données de caractères contiguë de 16 mégaoctets , des octets individuels (ou une chaîne d'octets contigus dans le tableau) peuvent être directement adressés et manipulés en utilisant le nom du tableau avec un entier non signé de 31 bits comme pointeur simulé (ceci est assez similaire à l' exemple des tableaux C montré ci-dessus). L'arithmétique de pointeur peut être simulée en ajoutant ou en soustrayant de l'index, avec une surcharge supplémentaire minimale par rapport à une véritable arithmétique de pointeur.

Il est même théoriquement possible, en utilisant la technique ci-dessus, avec un simulateur de jeu d'instructions approprié de simuler n'importe quel code machine ou l'intermédiaire ( byte code ) de n'importe quel processeur/langage dans un autre langage qui ne supporte pas du tout les pointeurs (par exemple Java / JavaScript ). Pour ce faire, le code binaire peut être initialement chargé dans des octets contigus du tableau pour que le simulateur « lise », interprète et agisse entièrement dans la mémoire contenue dans le même tableau. Si nécessaire, pour éviter complètement les problèmes de débordement de tampon , la vérification des limites peut généralement être effectuée pour le compilateur (ou sinon, codée à la main dans le simulateur).

Prise en charge de divers langages de programmation

Ada

Ada est un langage fortement typé où tous les pointeurs sont typés et où seules les conversions de type sûres sont autorisées. Tous les pointeurs sont initialisés par défaut sur null, et toute tentative d'accès aux données via un nullpointeur provoque la levée d' une exception . Les pointeurs dans Ada sont appelés types d'accès . Ada 83 n'autorisait pas l'arithmétique sur les types d'accès (bien que de nombreux fournisseurs de compilateurs l'aient fournie en tant que fonctionnalité non standard), mais Ada 95 prend en charge l'arithmétique « sûre » sur les types d'accès via le package System.Storage_Elements.

DE BASE

Plusieurs anciennes versions de BASIC pour la plate-forme Windows prenaient en charge STRPTR() pour renvoyer l'adresse d'une chaîne et pour VARPTR() pour renvoyer l'adresse d'une variable. Visual Basic 5 prenait également en charge OBJPTR() pour renvoyer l'adresse d'une interface d'objet et pour un opérateur ADDRESSOF pour renvoyer l'adresse d'une fonction. Les types de tous ces éléments sont des entiers, mais leurs valeurs sont équivalentes à celles détenues par les types pointeurs.

Cependant, les nouveaux dialectes de BASIC , tels que FreeBASIC ou BlitzMax , ont des implémentations de pointeurs exhaustives. Dans FreeBASIC, l'arithmétique sur les ANYpointeurs (équivalent à C's void*) est traitée comme si le ANYpointeur était une largeur d'octet. ANYles pointeurs ne peuvent pas être déréférencés, comme en C. De plus, le transtypage entre ANYet les pointeurs de tout autre type ne générera aucun avertissement.

dim as integer f = 257
dim as any ptr g = @f
dim as integer ptr i = g
assert(*i = 257)
assert( (g + 4) = (@f + 1) )

C et C++

En C et C++, les pointeurs sont des variables qui stockent des adresses et peuvent être null . Chaque pointeur a un type vers lequel il pointe, mais on peut librement transtyper entre les types de pointeur (mais pas entre un pointeur de fonction et un pointeur d'objet). Un type de pointeur spécial appelé « pointeur vide » permet de pointer sur n'importe quel objet (non fonction), mais est limité par le fait qu'il ne peut pas être déréférencé directement (il doit être transtypé). L'adresse elle-même peut souvent être directement manipulée en lançant un pointeur vers et à partir d'un type intégral de taille suffisante, bien que les résultats soient définis par l'implémentation et puissent en effet provoquer un comportement indéfini ; alors que les normes C antérieures n'avaient pas de type intégral garanti suffisamment grand, C99 spécifie le nom de uintptr_t typedef défini dans <stdint.h>, mais une implémentation n'a pas besoin de le fournir.

C++ prend entièrement en charge les pointeurs C et le transtypage C. Il prend également en charge un nouveau groupe d'opérateurs de transtypage pour aider à détecter certains transtypages dangereux involontaires au moment de la compilation. Depuis C++11 , la bibliothèque standard C++ fournit également des pointeurs intelligents ( unique_ptr, shared_ptret weak_ptr) qui peuvent être utilisés dans certaines situations comme une alternative plus sûre aux pointeurs C primitifs. C++ prend également en charge une autre forme de référence, assez différente d'un pointeur, appelée simplement référence ou type référence .

L'arithmétique du pointeur , c'est-à-dire la possibilité de modifier l'adresse cible d'un pointeur avec des opérations arithmétiques (ainsi que des comparaisons d'amplitude), est limitée par la norme de langage pour rester dans les limites d'un seul objet tableau (ou juste après) et sinon, invoquez un comportement non défini . L'ajout ou la soustraction d'un pointeur le déplace d'un multiple de la taille de son type de données . Par exemple, l'ajout de 1 à un pointeur à des valeurs entières de 4 octets incrémentera l'adresse d'octet pointée du pointeur de 4. Cela a pour effet d'incrémenter le pointeur pour pointer sur l'élément suivant dans un tableau contigu d'entiers, ce qui est souvent le résultat escompté. L'arithmétique du pointeur ne peut pas être effectuée sur les voidpointeurs car le type void n'a pas de taille, et donc l'adresse pointée ne peut pas être ajoutée, bien que gcc et d'autres compilateurs effectueront l'arithmétique des octets void*comme une extension non standard, la traitant comme si c'était char *.

L'arithmétique de pointeur fournit au programmeur une seule façon de traiter différents types : ajouter et soustraire le nombre d'éléments requis au lieu du décalage réel en octets. (L'arithmétique des char *pointeurs avec des pointeurs utilise des décalages d'octets, car sizeof(char)est 1 par définition.) En particulier, la définition C déclare explicitement que la syntaxe a[n], qui est le n-ième élément du tableau a, est équivalente à *(a + n), qui est le contenu de l'élément pointé par a + n. Cela implique qu'est n[a]équivalent à a[n], et on peut écrire, par exemple, a[3]ou 3[a]tout aussi bien pour accéder au quatrième élément d'un tableau a.

Bien que puissante, l'arithmétique du pointeur peut être une source de bogues informatiques . Cela a tendance à semer la confusion chez les programmeurs novices , les forçant dans des contextes différents : une expression peut être une expression arithmétique ordinaire ou une arithmétique de pointeur, et il est parfois facile de confondre l'une avec l'autre. En réponse à cela, de nombreux langages informatiques modernes de haut niveau (par exemple Java ) ne permettent pas un accès direct à la mémoire à l'aide d'adresses. En outre, le dialecte C sécurisé Cyclone résout de nombreux problèmes liés aux pointeurs. Voir langage de programmation C pour plus de discussion.

Le voidpointeur , ou void*, est pris en charge dans ANSI C et C++ en tant que type de pointeur générique. Un pointeur vers voidpeut stocker l'adresse de n'importe quel objet (pas de fonction) et, en C, est implicitement converti en tout autre type de pointeur d'objet lors de l'affectation, mais il doit être explicitement transtypé s'il est déréférencé. K&R C utilisé char*à des fins de « pointeur agnostique de type » (avant ANSI C).

int x = 4;
void* p1 = &x;
int* p2 = p1;       // void* implicitly converted to int*: valid C, but not C++
int a = *p2;
int b = *(int*)p1;  // when dereferencing inline, there is no implicit conversion

C++ n'autorise pas la conversion implicite de void*vers d'autres types de pointeur, même dans les affectations. Il s'agissait d'une décision de conception pour éviter les transtypages imprudents et même involontaires, bien que la plupart des compilateurs n'affichent que des avertissements, pas des erreurs, lorsqu'ils rencontrent d'autres transtypages.

int x = 4;
void* p1 = &x;
int* p2 = p1;                     // this fails in C++: there is no implicit conversion from void*
int* p3 = (int*)p1;               // C-style cast
int* p4 = static_cast<int*>(p1);  // C++ cast

En C++, il n'y a pas de void&(référence à void) à compléter void*(pointeur à void), car les références se comportent comme des alias vers les variables vers lesquelles elles pointent, et il ne peut jamais y avoir de variable de type void.

Présentation de la syntaxe de déclaration de pointeur

Ces déclarations de pointeur couvrent la plupart des variantes de déclarations de pointeur. Bien sûr, il est possible d'avoir des pointeurs triples, mais les grands principes d'un pointeur triple existent déjà dans un pointeur double.

char cff [5][5];    /* array of arrays of chars */
char *cfp [5];      /* array of pointers to chars */
char **cpp;         /* pointer to pointer to char ("double pointer") */
char (*cpf) [5];    /* pointer to array(s) of chars */
char *cpF();        /* function which returns a pointer to char(s) */
char (*CFp)();      /* pointer to a function which returns a char */
char (*cfpF())[5];  /* function which returns pointer to an array of chars */
char (*cpFf[5])();  /* an array of pointers to functions which return a char */

Les () et [] ont une priorité plus élevée que *.

C#

Dans le langage de programmation C# , les pointeurs ne sont pris en charge que sous certaines conditions : tout bloc de code comprenant des pointeurs doit être marqué avec le unsafemot - clé. De tels blocs nécessitent généralement des autorisations de sécurité plus élevées pour pouvoir s'exécuter. La syntaxe est essentiellement la même qu'en C++, et l'adresse pointée peut être une mémoire gérée ou non gérée . Cependant, les pointeurs vers la mémoire gérée (tout pointeur vers un objet géré) doivent être déclarés à l'aide du fixedmot - clé, ce qui empêche le ramasse - miettes de déplacer l'objet pointé dans le cadre de la gestion de la mémoire pendant que le pointeur est dans la portée, gardant ainsi l'adresse du pointeur valide.

Une exception à cette règle est l'utilisation de la IntPtrstructure, qui est un équivalent sécurisé et géré de int*, et ne nécessite pas de code dangereux. Ce type est souvent renvoyé lors de l'utilisation de méthodes de System.Runtime.InteropServices, par exemple :

// Get 16 bytes of memory from the process's unmanaged memory
IntPtr pointer = System.Runtime.InteropServices.Marshal.AllocHGlobal(16);

// Do something with the allocated memory

// Free the allocated memory
System.Runtime.InteropServices.Marshal.FreeHGlobal(pointer);

Le framework .NET inclut de nombreuses classes et méthodes dans les espaces de noms Systemet System.Runtime.InteropServices(comme la Marshalclasse) qui convertissent les types .NET (par exemple, System.String) vers et à partir de nombreux types et pointeurs non managés (par exemple, LPWSTRou void*) pour permettre la communication avec le code non managé . La plupart de ces méthodes ont les mêmes exigences d'autorisation de sécurité que le code non managé, car elles peuvent affecter des emplacements arbitraires en mémoire.

COBOL

Le langage de programmation COBOL prend en charge les pointeurs vers les variables. Les objets de données primitifs ou de groupe (enregistrement) déclarés dans LINKAGE SECTIONun programme sont intrinsèquement basés sur des pointeurs, où la seule mémoire allouée dans le programme est l'espace pour l'adresse de l'élément de données (généralement un seul mot mémoire). Dans le code source du programme, ces éléments de données sont utilisés comme n'importe quelle autre WORKING-STORAGEvariable, mais leur contenu est implicitement accessible indirectement via leurs LINKAGEpointeurs.

L'espace mémoire pour chaque objet de données pointé est généralement alloué dynamiquement à l' aide d' CALLinstructions externes ou via des constructions de langage étendu intégrées telles que des instructions EXEC CICSou EXEC SQL.

Les versions étendues de COBOL fournissent également des variables de pointeur déclarées avec des USAGE IS POINTERclauses. Les valeurs de ces variables pointeurs sont établies et modifiées à l'aide des instructions SETet SET ADDRESS.

Certaines versions étendues de COBOL fournissent également des PROCEDURE-POINTERvariables, capables de stocker les adresses de code exécutable .

PL/I

Le langage PL/I fournit une prise en charge complète des pointeurs vers tous les types de données (y compris les pointeurs vers les structures), la récursivité , le multitâche , la gestion des chaînes et de nombreuses fonctions intégrées . PL/I était un sacré bond en avant par rapport aux langages de programmation de son époque. Les pointeurs PL/I ne sont pas typés et, par conséquent, aucun transtypage n'est requis pour le déréférencement ou l'affectation des pointeurs. La syntaxe de déclaration d'un pointeur est DECLARE xxx POINTER;, qui déclare un pointeur nommé "xxx". Les pointeurs sont utilisés avec des BASEDvariables. Une variable basée peut être déclarée avec un localisateur par défaut ( DECLARE xxx BASED(ppp);ou sans ( DECLARE xxx BASED;), où xxx est une variable basée, qui peut être une variable d'élément, une structure ou un tableau, et ppp est le pointeur par défaut). Une telle variable peut être adressée sans référence de pointeur explicite ( xxx=1;, ou peut être adressée avec une référence explicite au localisateur par défaut (ppp), ou à tout autre pointeur ( qqq->xxx=1;).

L'arithmétique des pointeurs ne fait pas partie du standard PL/I, mais de nombreux compilateurs autorisent les expressions de la forme ptr = ptr±expression. IBM PL/I a également la fonction intégrée PTRADDpour effectuer l'arithmétique. L'arithmétique du pointeur est toujours effectuée en octets.

Les compilateurs IBM Enterprise PL/I ont une nouvelle forme de pointeur typé appelé HANDLE.

Le langage de programmation D est un dérivé de C et C++ qui prend entièrement en charge les pointeurs C et le transtypage C.

Eiffel

Le langage orienté objet Eiffel utilise une sémantique de valeur et de référence sans arithmétique de pointeur. Néanmoins, des classes de pointeur sont fournies. Ils offrent l'arithmétique du pointeur, le transtypage, la gestion explicite de la mémoire, l'interfaçage avec des logiciels non-Eiffel et d'autres fonctionnalités.

Fortran

Fortran-90 a introduit une capacité de pointeur fortement typé. Les pointeurs Fortran contiennent plus qu'une simple adresse mémoire. Ils encapsulent également les limites inférieure et supérieure des dimensions du tableau, les foulées (par exemple, pour prendre en charge des sections de tableau arbitraires) et d'autres métadonnées. Un opérateur d'association , =>est utilisé pour associer a POINTERà une variable qui a un TARGETattribut. L' ALLOCATEinstruction Fortran-90 peut également être utilisée pour associer un pointeur à un bloc de mémoire. Par exemple, le code suivant peut être utilisé pour définir et créer une structure de liste chaînée :

type real_list_t
  real :: sample_data(100)
  type (real_list_t), pointer :: next => null ()
end type

type (real_list_t), target :: my_real_list
type (real_list_t), pointer :: real_list_temp

real_list_temp => my_real_list
do
  read (1,iostat=ioerr) real_list_temp%sample_data
  if (ioerr /= 0) exit
  allocate (real_list_temp%next)
  real_list_temp => real_list_temp%next
end do

Fortran-2003 ajoute la prise en charge des pointeurs de procédure. De plus, dans le cadre de la fonctionnalité d' interopérabilité C , Fortran-2003 prend en charge les fonctions intrinsèques de conversion des pointeurs de style C en pointeurs Fortran et inversement.

Aller

Go a des pointeurs. Sa syntaxe de déclaration est équivalente à celle du C, mais écrite à l'envers, se terminant par le type. Contrairement à C, Go a un ramasse-miettes et interdit l'arithmétique de pointeur. Les types de référence, comme en C++, n'existent pas. Certains types intégrés, comme les cartes et les canaux, sont encadrés (c'est-à-dire qu'ils sont en interne des pointeurs vers des structures mutables) et sont initialisés à l'aide de la makefonction. Dans une approche de syntaxe unifiée entre pointeurs et non-pointeurs, l' ->opérateur flèche ( ) a été supprimé : l'opérateur point sur un pointeur fait référence au champ ou à la méthode de l'objet déréférencé. Ceci, cependant, ne fonctionne qu'avec 1 niveau d'indirection.

Java

Il n'y a pas de représentation explicite des pointeurs en Java . Au lieu de cela, des structures de données plus complexes telles que des objets et des tableaux sont implémentées à l'aide de références . Le langage ne fournit aucun opérateur explicite de manipulation de pointeur. Cependant, il est toujours possible pour le code de tenter de déréférencer une référence nulle (pointeur nul), ce qui entraîne la levée d'une exception d' exécution . L'espace occupé par les objets mémoire non référencés est récupéré automatiquement par le ramasse-miettes au moment de l'exécution.

Module-2

Les pointeurs sont implémentés comme en Pascal, tout comme les VARparamètres dans les appels de procédure. Modula-2 est encore plus fortement typé que Pascal, avec moins de moyens d'échapper au système de types. Certaines des variantes de Modula-2 (telles que Modula-3 ) incluent le ramasse-miettes.

Obéron

Tout comme avec Modula-2, des pointeurs sont disponibles. Il y a encore moins de moyens d'échapper au système de types et donc Oberon et ses variantes sont toujours plus sûrs en ce qui concerne les pointeurs que Modula-2 ou ses variantes. Comme avec Modula-3 , le ramasse-miettes fait partie de la spécification du langage.

Pascal

Contrairement à de nombreux langages qui comportent des pointeurs, le Pascal ISO standard permet uniquement aux pointeurs de référencer des variables créées dynamiquement qui sont anonymes et ne leur permet pas de référencer des variables statiques ou locales standard. Il n'a pas d'arithmétique de pointeur. Les pointeurs doivent également avoir un type associé et un pointeur vers un type n'est pas compatible avec un pointeur vers un autre type (par exemple, un pointeur vers un caractère n'est pas compatible avec un pointeur vers un entier). Cela permet d'éliminer les problèmes de sécurité de type inhérents aux autres implémentations de pointeur, en particulier celles utilisées pour PL/I ou C . Cela supprime également certains risques causés par les pointeurs pendants , mais la possibilité de libérer dynamiquement l'espace référencé en utilisant la procédure standard (qui a le même effet que la fonction de bibliothèque trouvée dans C ) signifie que le risque de pointeurs pendants n'a pas été entièrement éliminé. disposefree

Cependant, dans certaines implémentations commerciales et open source du compilateur Pascal (ou dérivés) - comme Free Pascal , Turbo Pascal ou Object Pascal dans Embarcadero Delphi - un pointeur est autorisé à référencer des variables statiques ou locales standard et peut être converti d'un type de pointeur en un autre. De plus, l'arithmétique du pointeur n'est pas restreinte : l'ajout ou la soustraction d'un pointeur le déplace de ce nombre d'octets dans les deux sens, mais l'utilisation des procédures standard Incou Decavec elle déplace le pointeur de la taille du type de données vers lequel il est déclaré pointer. Un pointeur non typé est également fourni sous le nom Pointer, qui est compatible avec d'autres types de pointeur.

Perl

Le langage de programmation Perl prend en charge les pointeurs, bien que rarement utilisés, sous la forme des fonctions pack et unpack. Ceux-ci ne sont destinés qu'à des interactions simples avec des bibliothèques d'OS compilées. Dans tous les autres cas, Perl utilise des références , qui sont typées et n'autorisent aucune forme d'arithmétique de pointeur. Ils sont utilisés pour construire des structures de données complexes.

Voir également

Les références

Liens externes