NULL
?NULL
-pointer assignment » ?void *
et char *
?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.
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.
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 */
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.
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.
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.
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);
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).