Pointeur « near », « far » et « huge »

A l’époque du MS Dos et des architectures 16 bit, certains processeurs intel avaient des registres de valeur de 16 bit pouvant contenir des valeurs allant jusque 64KB, c’est à dire 16 bits, mais la possibilité d’adresser jusque 1024KB, soit 1MB ou 20 bits.

Typiquement les ordinqteurs avaient des registres trop petits pour adresser toute lq mémoire disponible avec un seul registre et mettaient à disposition des pointeurs rapides si l’adresse etait inférieure à 64kb, typiquement pour un pointeur pointant sur une variable située sur la stack , et des pointeurs plus lents stockés sur deux registres permettant de travailler avec des tableaux de grande taille (images bitmap, videos…) obtenus via malloc.

Lorsque le pointeur cible une variable située dans le même segment que le pointeur lui même, typiquement une variable automatique sur la pile on peut utiliser un pointeur de 16 bits pouvant adresser 64KB adresses differentes. Sur une mémoire de 1MB, on peut mettre 16 (2^4) segments de 64KB (2^16) – et donc avoir 16 pointeurs ayant la même valeur mais pointant a différents endroits de la machine. Ces pointeurs sont contenus dans un seul registre d’adresse et sont les plus rapides à l’utilisation.

Un pointeur far est stocké sur un registre de segment et un registre d’adresse, tous deux de 16 bits et doit passer par une phase de conversion vers une valeur de 20 bits avant de pouvoir être utilisé par le processeur, ce qui est assez lent.

Les 16 bits du registre de segment sont décalés de 4 crans à gauche (multipliés par 16) puis additionnés avec les 16 bits du registre d’adresse. En decalant 16 de 4 crans a gauche on obtien 20 bits, soit 2^20 valeurs, soient 1 048 576 valeurs, soient en divisant par 1024, 1024KB, en divisant encore par 1024 1MB, c’est à dire 1 mébibits ou un peu plus de 1 mégabits.

En pratique seul 4 bits du registre d’adresse sont utiles pour obtenir l’adresse finale, mais en pratique les 16 bits sont utilisés pour l’addition : il existe donc 16 - 4 soient 2^12 soient 4096 differentes valeurs possibles pour un pointeur far adressant le même emplacement en mémoire.

L’arithmétique sur un pointeur far n’affecte que le registre d’adresse : un dépassement d’entier cause une troncature du registre d’adresse et n’affecte pas le registre de segment. Il est donc impossible de travailler avec un tableau plus grand que 65536 octets avec un pointeur near ou far.

Pour les application graphiques qui travaillent avec des bitmaps de plusieurs millions d’octets il est possible de travailler avec des pointeurs huge qui sont stockés sur 2 registres de 16 bits, mais passent par une étape de normalisation où les 12 bits de poids fort du segment d’adresse sont testés et si besoin tronqués avant d’être additionnés, si besoin avec le registre de segment : déréférencer un pointeur huge sera beaucou plus lent et couteux que déréférencer un pointeur near ou far.

Depuis que les architectures 32 et 64 bits sont la norme le registre de valeur et d’adresse ont la même taille, deux pointeurs de même valeur sont garantis de pointer sur la même adresse, et la distinction entre pointeur near, far et huge est obsolète.

Voici un exemple illustrant montrant la propriété des pointeurs far.

#include <stdio.h>

int main() {
    char far *p =(char far *)0x55550005;
    char far *q =(char far *)0x53332225;
    *p = 80;
    (*p)++;           // on icrement la valeur *p
    
    printf("%d",*q);  // *q vaut 81 : p et q pointent au meme endroit malgré leur différente valeur

    return 0;
}

p vaut 0x5555 << 4 + 5, soit 0x55550 + 4, soit 0x55554 ; q vaut 0x5333 << 4 + 0x2225, soit 0x53330 + 0x2225, soit 0x55554.

Watcom C compiler

Le compilateur C de Watcom definissait des pointeur __near, __far et __huge1.

Ils s´‘ecrivent ainsi

int __far * __far X[];     // X is a far array of far pointers to integers.

Compilateur AIX de IBM

Le compilateur AIX permet dans le cas où l’environnement d’exécution est freestanding de spécifier la représentation interne de celui-ci.2

Dans un environnement 31 bits, la taille du pointeur passe à 64 bits (8 octets), et dans un environnement 64 bits la taille du pointeur passe à 128 bits (16 octets).

La notation d’un pointeur 128 bits est ainsi

int * __far p;

Dans le futur

Il peut être souhaitable de pouvoir spécifier la taille que l’on souhaite que la représentation interne d’un pointeur ait pour économiser de l’espace.

Imaginons que nous ayons un arbre binaire de petite taille où chaque noeud contienne un pointeur vers d’autres noeuds. Si cette structure est plus petite que 64KB, seuls deux octets suffiraient pour adresser n’importe quel élément de celle-ci, utiliser deux octets au lieu de 4 pour chaque noeud permet des gains substantiels de mémoire et augmente le nombre d’éléments qu’on pourrait mettre dans la structure de données.

Il est en discution de les ajouter dans la norme C++3.