Variables dont le contenu est une adresse
Le type de la variable contenue à l'adresse contenue dans une variable pointeur est le type du pointeur.
Entité qui permet d'accéder indirectement à une information : l'information n'est pas contenue dans la variable, mais à l'adresse contenue dans la variable.
L'accès se fait donc en deux temps.

Si le pointeur ne doit pas contenir l'adresse d'une variable de type connu, son type est void.
Il n'y a pas de conversion implicite entre les types de pointeur, sauf de void vers un autre type.
Attention : cette déclaration définit la zone mémoire portant le nom nom où pourra être mémorisée l'adresse d'une variable de type type_pointé. La zone mémoire qui sera utilisée par cette variable n'est pas réservée par cette déclaration.
Une grande liberté est ainsi laissée à l'utilisateur pour la gestion de cette variable.
Initialiser un pointeur : lui affecter une valeur qui soit une adresse valide. Il faut toujours initialiser un pointeur.

Il est dangereux de laisser un pointeur sans affectation ni initialisation : il risque de contenir une adresse invalide, et toute tentative de lecture ou écriture à cette adresse bloquera le programme.
Si on ne peut pas momentanément lui affecter une valeur valide, on lui affecte la valeur NULL. On pourra faire précéder toute tentative d'utilisation du pointeur par un test sur cette valeur.
Il faut une adresse valide : qui soit dans la zone mémoire affecté à l'exécution du programme. Une partie de cette zone est dédiée à ce type de manipulation : le tas. Cette gestion de la mémoire pendant l'exécution s'appelle la gestion dynamique de la mémoire.
La fonction malloc
Fonction de la bibliothèque standard
fichier d'en-tête : malloc.h
paramètre : le nombre d'octets à allouer.
retourne un pointeur sur void contenant une adresse valide
adresse valide : adresse autorisé, non déjà utilisée, début d'un bloc valide de N octets, si N est le nombre d'octets à allouer.
Il y a donc :
affectation d'une valeur au pointeur,
allocation d'un bloc mémoire dont l'accès se fera par l'intermédiaire de ce pointeur
L'opérateur sizeof
Retourne le nombre d'octets occupé par son opérande, variable dont le nom suit l'opérateur, ou bien type, dont le nom est dans un typecast qui suit l'opérateur :

Allocation

Dans l'exemple ci-dessus, l'application de l'opérateur sizeof au pointeur donnera la valeur 20 (pourquoi ?).
Elle est nécessaire, sinon on arrive plus ou moins vite à saturation du tas.
fonction free

Dans ce cas les deux pointeurs pointent sur le même bloc de mémoire, ce qui est dangereux.
Le rôle essentiel des pointeurs est de donner un autre moyen d'accès aux variables du programme, sans utiliser leur nom : les variables peuvent ne pas avoir de nom.
Appelé parfois opérateur de déréférencement ou opérateur d'indirection, car il donne accès indirectement (en passant par un pointeur) à une variable pointée ou à une variable d'un bloc pointé.

Dans cet exemple, on peut accéder à la variable x, en écriture ou en lecture :
directement, en la nommant,
indirectement, par l'intermédiaire du pointeur ptrDble qui contient son adresse.
*ptrDble est synonyme de x dans cet exemple
C'est le cas par exemple quand la fonction malloc a alloué un multiple plus grand que 1 de la taille de la variable pointée :

Dans cet exemple, ptrDble pointe sur un bloc de 5 doubles qui lui a été alloué : c'est donc un tableau de doubles. Il est possible d'accéder individuellement à chacune des variables qui constituent ce bloc. Il y a deux façons d'obtenir ce résultat :
ptrDble (ou ptrDble + 0) pointe sur la première variable de ce bloc
ptrDble + i pointe sur la i + 1 ème variable de ce bloc

Attention, l'opérateur * vient avant l'opérateur + dans l'ordre des priorités : la parenthèse de l'exemple est indispensable.
ptrDble[i] est la i + 1 ème variable du bloc

ptr[i] est synonyme de *(ptr+i)
Ce mot clé intervient lors d'une déclaration-initialisation pour interdire toute modification ultérieure de la variable à laquelle il s'applique :

Selon sa position dans la déclaration, ce mot clé permet d'interdire toute modification ultérieure :
du pointeur lui-même, c'est à dire de la valeur de l'adresse qu'il contient,
ou de la variable pointée.
La règle de syntaxe est la suivante : le mot const s'applique à l'entité placée immédiatement à sa droite :

Les deux premières lignes indiquent que le double pointé est constant : on ne pourra le modifier par l'intermédiaire du pointeur déclaré dans chacune des deux lignes (mais on pourra le modifier s'il existe une autre façon d'y accéder).
La troisième ligne indique que ptr3Dble, qui est un pointeur, ne pourra être modifé. On peut par contre modifier le contenu de la variable pointée.
On a vu que l'opérateur + et l'opérateur - peuvent s'applique aux pointeurs. La signification de ces opérateurs est adaptée à leur utilisation :
l'addition (ou soustraction) d'un entier N à un pointeur ajoute (ou soustrait) au contenu du pointeur la quantité d'octets qui lui permet ou permettrait) de pointer sur la variable de même type située au Nème rang derrière (ou devant) elle dans l'ordre des adresses. Il lui est donc ajouté (ou soustrait) N fois la taille de la variable pointée.
Attention :
aucun contrôle n'est fait pour vérifier que l'on reste dans le bloc alloué au pointeur. C'est au concepteur du programme de veiller à ce type de vérification.
ces opérateurs ne peuvent s'appliquer aux pointeurs de type void, puisqu'aucun type ne leur est associé.
Cette expression n'est permise qu'entre deux pointeurs de même type, non void. Elle retourne le rang du premier objet pointé par rapport au deuxième (supposé l'objet numéro 0).
L'opérateur == permet de tester si deux pointeurs contiennent la même adresse.
On a vu qu'une fonction ne peut retourner qu'une valeur. Elle peut cependant posséder autant de variables de sortie que nécessaire si elle reçoit en paramètre les adresses de ces variables : il lui sera alors possible d'écrire les valeurs de sortie dans ces variables. Puisque la fonction peut tout aussi bien lire la valeur de ces variables ce sont en fait des variables d'entrée/sortie

Les variables du programme appelant dont les adresses initialisent les paramètres de cette fonction voient leurs valeurs échangées par l'appel à cette fonction.

Dans cet exemple, N doubles successifs de la fonction appelante, situés aux adresses derrière l'adresse dont la valeur est transmise comme paramètre à l'appel de la fonction, se voient affecter des valeurs à l'appel de la fonction remplit.
Une fonction peut bien sûr retourner un pointeur. Il faut cependant se souvenir que ce pointeur sera sans doute utilisé pour accéder à une variable, et qu'il faut donc qu'il reste valide après exécution de la fonction.
Règle simple : ne pas retourner l'adresse d'une variable locale. Cette adresse sera en effet utilisée à autre chose après retour de la fonction et aura perdu sa signification, puisque c'est celle d'une variable locale (souvenez vous que les paramètres sont aussi des variables locales initialisées par les valeurs des variables transmises à l'appel de la fonction).
Il est par contre possible de retourner un pointeur déclaré localement et initialisé par appel à malloc dans le corps de la fonction. En effet, cette allocation lui a affecté la valeur d'une adresse dans le tas, qui reste donc valide après retour de la fonction. C'est l'adresse de ce pointeur qui n'a plus de signification, mais pas son contenu.
On peut transmettre à une fonction l'adresse d'une fonction qu'elle pourra alors utiliser à sa guise.
La déclaration d'un pointeur sur fonction reprend toutes les informations de l'interface de la fonction : un nom, le nom du pointeur, le type retourné et les types des paramètres.

Le nom d'une fonction est l'adresse de cette fonction, c'est à dire l'adresse de branchement lors de l'appel de la fonction.
Les opérations permises sont l'affectation (=) et la comparaison (==), entre pointeurs sur fonctions de même prototype
Les pointeurs permettent de faire face à des situations où le nombre de variables à manipuler ne peut être connu au moment où l'on écrit le programme. La mémoire est donc gérée à l'exécution et non pas à la compilation : c'est la gestion dynamique de la mémoire.
On prévoit que le programme aura à manipuler une collection de N variables d'un type donné, mais la valeur de N dépendra d'événement survenus au cours du déroulement du programme.
La solution est simple : un appel à malloc lorsque N est connu.