précédent  index  suivant

7. Tableaux et pointeurs


7.1 Quelle est la différence entre un tableau et un pointeur ?

Un tableau n'est pas un pointeur. Un tableau est une zone mémoire pouvant contenir N éléments consécutifs de même type. Un pointeur est une zone mémoire qui contient l'adresse d'une autre zone mémoire. Toutefois, dans un grand nombre de cas, tout se passe comme si c'était la même chose.

À ce titre, il faut bien faire la différence entre a[i] pour un tableau et ap[i] pour un pointeur. Voici un exemple :

	char a[] = "Bonjour";
	char *ap = "Au revoir";
		

L'expression a[3] signifie que l'on accède aux quatrième élément du tableau. ap[3] signifie que l'on accède à la zone mémoire pointée par (ap+3). Autrement dit, a[3] est l'objet situé 3 places après a[0] (a est le tableau entier), alors que ap[3] est l'objet situé 3 places après l'objet pointé par ap. Dans l'exemple, a[3] vaut 'j' et ap[3] vaut 'r'.

Voir aussi la question 5.9.

7.2 Comment passer un tableau à plusieurs dimensions en paramètre d'une fonction ?

Ce n'est pas si facile. La règle de base est qu'il faut connaître la taille des N-1 dernières dimensions. Pour un tableau à deux dimensions, la deuxième doit être connue, et la fonction doit être déclarée ainsi :

	int f1(int a[][NCOLUMNS]);
				/* a est un tableau a deux dimensions (cf. remarque) */
	int f2(int (*ap)[NCOLUMNS]);
				/* ap est un pointeur sur un tableau  */
		

Si elle n'est pas connue, il faut passer la taille du tableau en paramètre (ligne ET colonne) et un pointeur sur le tableau :

	int f(int * a, int nrows, int ncolumns);
		

On accède aux éléments du tableau ainsi :

	a[i * ncolumns + j] /* element de la ieme ligne
	                     * et de la jeme colonne */
		

Une remarque : Dans une déclaration de paramètre

	int f1(int a[][NCOLUMNS])
		

ou

	int f1(int a[42][NCOLUMNS])
		

a est un pointeur sur int[NCOLUMS] malgré l'écriture. La déclaration est interprétée comme

	int (*a) [NCOLUMS]
		

Ainsi les déclarations de f1() et f2() dans l'exemple initial sont exactement les mêmes.

7.3 Comment allouer un tableau à plusieurs dimensions ?

La première solution est d'allouer un tableau de pointeurs, puis d'initialiser chacun de ces pointeurs par un tableau dynamique.

	#include <stdlib.h>

	int ** a = malloc(nrows * sizeof *a);
	for(i = 0; i < nrows; i++)
	   a[i] = malloc(ncolumns * sizeof *(a[i]));
		

Dans la vraie vie, le retour de malloc() doit être vérifié.

Une autre solution est de simuler un tableau multi-dimensions avec une seule allocation :

	int *a = malloc(nrows * ncolumns * sizeof *a);
		

L'accès aux éléments se fait par :

	a[i * ncolumns + j] /* element de la ieme ligne
	                     * et de la jeme colonne */
		

7.4 Comment définir un type pointeur de fonction ?

On utilise typedef, comme pour n'importe quel autre type. Voici un exemple :

	int f(char * sz); /* une fonction                 */
	int (*pf)(char *);/* un pointeur sur une fonction */
	typedef int (*pf_t)(char *);
	                  /* un type pointeur sur fonction*/
		

Il est toutefois préférable de ne pas cacher le pointeur dans un typedef. La solution suivante est plus jolie :

	typedef int (f_t)(char *); /* un type fonction        */
	f_t * pf;                  /* un pointeur sur ce type */
		

On l'utilise alors de cette façon :

	pf = f;
	int ret = pf("Merci pour cette reponse");
		

Voir aussi la question 5.7.

7.5 Que vaut (et signifie) la macro NULL ?

NULL est une macro qui représente une valeur spéciale pour désigner un pointeur nul lorsque converti au type approprié. Elle est définie dans <stddef.h> ou dans <stdio.h>.

La valeur réelle de NULL est dépendante de l'implémentation, et n'est pas nécessairement un pointeur, ni de type pointeur. Des valeurs possibles sont ((void *)0) ou 0.

NULL permet de distinguer les pointeurs valides des pointeurs invalides. Par exemple, malloc() renvoie une valeur comparable à NULL quand elle échoue.

Voir aussi la question 12.3.

7.6 Que signifie l'erreur « NULL-pointer assignment » ?

Cela signifie que vous avez essayé d'accéder à l'adresse 0 de la mémoire. Vous avez probablement déréférencé un pointeur NULL, ou oublié de tester la valeur retour d'une fonction, avant de l'utiliser.

7.7 Comment imprimer un pointeur ?

La seule manière prévue par la norme pour imprimer correctement un pointeur est d'utiliser la fonction printf() avec le code de format %p. Le pointeur doit être d'abord casté en un pointeur générique void *.

	char * p;
	printf("Pointeur p avant initialisation: %p\n", (void *)p);
		

7.8 Quelle est la différence entre void * et char * ?

Le premier est un pointeur générique, qui peut recevoir l'adresse de n'importe quel type d'objet. Le second est un pointeur sur un caractère, généralement utilisé pour les chaînes.

Avant la norme ANSI, le type void n'existait pas. C'était donc char * qui était utilisé pour faire des pointeurs génériques. Depuis la norme, ce n'est plus valide. De nombreux programmeurs ont toutefois gardé cette habitude, notamment dans le cast de fonctions comme malloc() (cf. 12.1).


précédent  index  suivant