Recherche…


Introduction

Un pointeur est un type de variable pouvant stocker l'adresse d'un autre objet ou d'une fonction.

Syntaxe

  • <Type de données> * <Nom de la variable>;
  • int * ptrToInt;
  • void * ptrToVoid; / * C89 + * /
  • struct someStruct * ptrToStruct;
  • int ** ptrToPtrToInt;
  • int arr [longueur]; int * ptrToFirstElem = arr; / * Pour <C99 'length doit être une constante de compilation, pour> = C11 il faudra peut-être en avoir une. * /
  • int * arrayOfPtrsToInt [length]; / * Pour <C99 'length doit être une constante de compilation, pour> = C11 il faudra peut-être en avoir une. * /

Remarques

La position de l'astérisque n'affecte pas le sens de la définition:

/* The * operator binds to right and therefore these are all equivalent. */
int *i;
int * i;
int* i;

Cependant, lors de la définition de plusieurs pointeurs à la fois, chacun nécessite son propre astérisque:

int *i, *j; /* i and j are both pointers */
int* i, j;  /* i is a pointer, but j is an int not a pointer variable */

Un tableau de pointeurs est également possible, où un astérisque est donné avant le nom de la variable du tableau:

int *foo[2]; /* foo is a array of pointers, can be accessed as *foo[0] and *foo[1] */

Erreurs courantes

Une mauvaise utilisation des pointeurs est souvent une source de bogues pouvant inclure des bogues de sécurité ou des plantages de programmes, le plus souvent dus à des erreurs de segmentation.

Ne pas vérifier les échecs d'allocation

L'allocation de mémoire n'est pas garantie pour réussir et peut à la place renvoyer un pointeur NULL . L'utilisation de la valeur renvoyée, sans vérifier si l'allocation a réussi, appelle un comportement non défini . Cela conduit généralement à un plantage, mais rien ne garantit qu’un crash se produira, ce qui peut également entraîner des problèmes.

Par exemple, manière dangereuse:

struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */

Manière sûre:

struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
    s->someValue = 0; /* This is safe, we have checked that s is valid */
}

Utiliser des nombres littéraux au lieu de sizeof lors de la demande de mémoire

Pour une configuration compilateur / machine donnée, les types ont une taille connue. Cependant, il n'existe pas de norme définissant que la taille d'un type donné (autre que char ) sera la même pour toutes les configurations du compilateur / machine. Si le code utilise 4 au lieu de sizeof(int) pour l'allocation de mémoire, cela peut fonctionner sur la machine d'origine, mais le code n'est pas nécessairement portable à d'autres machines ou compilateurs. Les tailles fixes pour les types doivent être remplacées par sizeof(that_type) ou sizeof(*var_ptr_to_that_type) .

Allocation non portable:

 int *intPtr = malloc(4*1000);    /* allocating storage for 1000 int */
 long *longPtr = malloc(8*1000);  /* allocating storage for 1000 long */

Allocation portable:

 int *intPtr = malloc(sizeof(int)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(long)*1000);  /* allocating storage for 1000 long */

Ou mieux encore:

 int *intPtr = malloc(sizeof(*intPtr)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(*longPtr)*1000);  /* allocating storage for 1000 long */

Fuites de mémoire

Si vous ne désallouez pas la mémoire à l’aide de free vous obtenez une accumulation de mémoire non réutilisable, qui n’est plus utilisée par le programme; cela s'appelle une fuite de mémoire . Les fuites de mémoire gaspillent des ressources de mémoire et peuvent entraîner des défaillances d'allocation.

Erreurs logiques

Toutes les allocations doivent suivre le même schéma:

  1. Allocation en utilisant malloc (ou calloc )
  2. Utilisation pour stocker des données
  3. Désaffectation en utilisant free

Ne pas adhérer à ce modèle, comme utiliser la mémoire après un appel à free ( pointeur ) ou avant un appel à malloc ( pointeur sauvage ), appeler deux fois free ("double free"), etc., provoque généralement une erreur de segmentation et entraîne un crash du programme.

Ces erreurs peuvent être transitoires et difficiles à déboguer - par exemple, la mémoire libérée n'est généralement pas récupérée immédiatement par le système d'exploitation et les pointeurs en suspens peuvent donc persister pendant un certain temps et sembler fonctionner.

Sur les systèmes sur lesquels il fonctionne, Valgrind est un outil précieux pour identifier les fuites de mémoire et leur emplacement initial.

Créer des pointeurs pour empiler des variables

Créer un pointeur ne prolonge pas la vie de la variable pointée. Par exemple:

int* myFunction() 
{
    int x = 10;
    return &x;
}

Ici, x a une durée de stockage automatique (communément appelée allocation de pile ). Comme il est alloué sur la pile, sa durée de vie est aussi longue que myFunction s'exécute; une fois myFunction sortie, la variable x est détruite. Cette fonction obtient l'adresse de x (en utilisant &x ), et la renvoie à l'appelant, laissant l'appelant avec un pointeur sur une variable inexistante. Tenter d'accéder à cette variable invoquera alors un comportement indéfini .

La plupart des compilateurs ne nettoient pas réellement un cadre de pile après la fermeture de la fonction, ce qui permet de déréférencer le pointeur retourné et vous donne souvent les données attendues. Lorsqu'une autre fonction est appelée, la mémoire pointée peut être écrasée et il semble que les données pointées ont été corrompues.

Pour résoudre ce problème, malloc le stockage de la variable à renvoyer et renvoyez un pointeur vers le stockage nouvellement créé, ou exigez qu'un pointeur valide soit transmis à la fonction au lieu d'en renvoyer un, par exemple:

#include <stdlib.h>
#include <stdio.h>

int *solution1(void) 
{
    int *x = malloc(sizeof *x);
    if (x == NULL) 
    {
        /* Something went wrong */
        return NULL;
    }

    *x = 10;

    return x;
}

void solution2(int *x) 
{
    /* NB: calling this function with an invalid or null pointer 
       causes undefined behaviour. */

    *x = 10;
}

int main(void) 
{
    { 
        /* Use solution1() */

        int *foo = solution1();  
        if (foo == NULL)
        {
            /* Something went wrong */
            return 1;
        }

        printf("The value set by solution1() is %i\n", *foo);
        /* Will output: "The value set by solution1() is 10" */

        free(foo);    /* Tidy up */
    }

    {
        /* Use solution2() */

        int bar;
        solution2(&bar); 

        printf("The value set by solution2() is %i\n", bar);
        /* Will output: "The value set by solution2() is 10" */
    }

    return 0;
}

Incrémenter / décrémenter et déréférencer

Si vous écrivez *p++ pour incrémenter ce qui est indiqué par p , vous avez tort.

La post-incrémentation / décrémentation est exécutée avant le déréférencement. Par conséquent, cette expression incrémentera le pointeur p lui-même et renverra ce qui a été pointé par p avant de l'incrémenter sans le modifier.

Vous devriez écrire (*p)++ pour incrémenter ce qui est indiqué par p .

Cette règle s'applique également à la post-décrémentation: *p-- décrémentera le pointeur p lui-même, pas ce qui est indiqué par p .

Déréférencer un pointeur

int a = 1;
int *a_pointer = &a;

Pour déréférencer a_pointer et changer la valeur de a, nous utilisons l'opération suivante

*a_pointer = 2;

Cela peut être vérifié en utilisant les instructions d'impression suivantes.

printf("%d\n", a); /* Prints 2 */
printf("%d\n", *a_pointer); /* Also prints 2 */

Cependant, on pourrait se tromper de déréférencer un pointeur NULL ou non valide. Ce

int *p1, *p2;

p1 = (int *) 0xbad;
p2 = NULL;

*p1 = 42;
*p2 = *p1 + 1;

est généralement un comportement indéfini . p1 ne peut pas être déréférencé car il pointe vers une adresse 0xbad qui peut ne pas être une adresse valide. Qui sait ce qu'il y a? Il peut s'agir de la mémoire du système d'exploitation ou de la mémoire d'un autre programme. Le seul code temporel comme celui-ci est le développement intégré, qui stocke des informations particulières à des adresses codées en dur. p2 ne peut pas être déréférencé car il est NULL , ce qui est invalide.

Déréférencer un pointeur à une structure

Disons que nous avons la structure suivante:

struct MY_STRUCT 
{
    int my_int;
    float my_float;
};

Nous pouvons définir MY_STRUCT pour omettre le mot struct clé struct , nous n'avons donc pas à taper struct MY_STRUCT chaque fois que nous l'utilisons. Ceci est cependant facultatif.

typedef struct MY_STRUCT MY_STRUCT;

Si nous avons alors un pointeur sur une instance de cette structure

MY_STRUCT *instance;

Si cette instruction apparaît à la portée du fichier, l' instance sera initialisée avec un pointeur nul au démarrage du programme. Si cette instruction apparaît dans une fonction, sa valeur est indéfinie. La variable doit être initialisée pour pointer sur une variable MY_STRUCT valide ou sur un espace alloué dynamiquement avant de pouvoir être déréférencé. Par exemple:

MY_STRUCT info = { 1, 3.141593F };
MY_STRUCT *instance = &info;

Lorsque le pointeur est valide, nous pouvons le déréférencer pour accéder à ses membres en utilisant l'une des deux notations suivantes:

int a = (*instance).my_int;
float b = instance->my_float;

Bien que ces deux méthodes fonctionnent, il est préférable d'utiliser l'opérateur arrow -> plutôt que la combinaison de parenthèses, l'opérateur dereference * et le point . opérateur car il est plus facile à lire et à comprendre, en particulier avec les utilisations imbriquées.

Une autre différence importante est illustrée ci-dessous:

MY_STRUCT copy = *instance;
copy.my_int    = 2;

Dans ce cas, copy contient une copie du contenu de l' instance . Changer my_int de copy ne le changera pas par instance .

MY_STRUCT *ref = instance;
ref->my_int    = 2;

Dans ce cas, ref est une référence à une instance . Changer my_int utilisant la référence le changera par instance .

Il est courant d'utiliser des pointeurs vers des structures en tant que paramètres dans les fonctions, plutôt que les structures elles-mêmes. L'utilisation des structures en tant que paramètres de fonction pourrait entraîner un débordement de la pile si la structure est volumineuse. L'utilisation d'un pointeur sur une structure n'utilise que suffisamment d'espace de pile pour le pointeur, mais peut entraîner des effets secondaires si la fonction modifie la structure transmise à la fonction.

Pointeurs de fonction

Les pointeurs peuvent également être utilisés pour pointer des fonctions.

Prenons une fonction de base:

int my_function(int a, int b)
{
    return 2 * a + 3 * b;
}

Maintenant, définissons un pointeur du type de cette fonction:

int (*my_pointer)(int, int);

Pour en créer un, utilisez simplement ce modèle:

return_type_of_func (*my_func_pointer)(type_arg1, type_arg2, ...)

Nous devons ensuite affecter ce pointeur à la fonction:

my_pointer = &my_function;

Ce pointeur peut maintenant être utilisé pour appeler la fonction:

/* Calling the pointed function */
int result = (*my_pointer)(4, 2);

...

/* Using the function pointer as an argument to another function */
void another_function(int (*another_pointer)(int, int))
{
    int a = 4;
    int b = 2;
    int result = (*another_pointer)(a, b);

    printf("%d\n", result);
}

Bien que cette syntaxe semble plus naturelle et cohérente avec les types de base, les pointeurs de fonction d'attribution et de déréférencement ne nécessitent pas l'utilisation des opérateurs & et * . Ainsi, l'extrait suivant est également valide:

/* Attribution without the & operator */
my_pointer = my_function;

/* Dereferencing without the * operator */
int result = my_pointer(4, 2);

Pour augmenter la lisibilité des pointeurs de fonction, les typedefs peuvent être utilisés.

typedef void (*Callback)(int a);

void some_function(Callback callback)
{
    int a = 4;
    callback(a);
}

Une autre astuce de lisibilité est que le standard C permet de simplifier un pointeur de fonction dans les arguments comme ci-dessus (mais pas dans la déclaration de variable) à quelque chose qui ressemble à un prototype de fonction; ainsi, les éléments suivants peuvent être utilisés de manière équivalente pour les définitions de fonctions et les déclarations:

void some_function(void callback(int))
{
    int a = 4;
    callback(a);
}

Voir également

Pointeurs de fonction

Initialisation des pointeurs

L'initialisation du pointeur est un bon moyen d'éviter les pointeurs sauvages. L'initialisation est simple et n'est pas différente de l'initialisation d'une variable.

#include <stddef.h>

int main()
{
    int *p1 = NULL; 
    char *p2 = NULL;
    float *p3 = NULL;

         /* NULL is a macro defined in stddef.h, stdio.h, stdlib.h, and string.h */

    ...
}    

Dans la plupart des systèmes d'exploitation, l'utilisation involontaire d'un pointeur initialisé à NULL entraîne souvent une panne immédiate du programme, facilitant ainsi l'identification de la cause du problème. L'utilisation d'un pointeur non initialisé peut souvent causer des bogues difficiles à diagnostiquer.

Mise en garde:

Le résultat du déréférencement d'un pointeur NULL n'est pas défini, il ne provoquera donc pas nécessairement un blocage même si cela est le comportement naturel du système d'exploitation sur lequel le programme s'exécute. Les optimisations du compilateur peuvent masquer la panne, provoquer la panne avant ou après le point dans le code source auquel le déréférencement du pointeur null s'est produit ou provoquer la suppression inattendue de certaines parties du code contenant le déréférencement du pointeur nul du programme. Les versions de débogage ne présentent généralement pas ces comportements, mais cela n'est pas garanti par la norme de langage. Tout autre comportement inattendu et / ou indésirable est également autorisé.

Comme NULL ne pointe jamais sur une variable, sur la mémoire allouée ou sur une fonction, il est possible de l'utiliser comme valeur de garde.

Mise en garde:

Habituellement, NULL est défini comme (void *)0 . Mais cela ne signifie pas que l'adresse mémoire attribuée est 0x0 . Pour plus de précisions, reportez - vous à C-faq pour les pointeurs NULL

Notez que vous pouvez également initialiser des pointeurs pour contenir des valeurs autres que NULL.

int i1;

int main()
{
   int *p1 = &i1;
   const char *p2 = "A constant string to point to";
   float *p3 = malloc(10 * sizeof(float));
}

Adresse de l'opérateur (&)

Pour tout objet (variable, tableau, union, struct, pointeur ou fonction), l'opérateur d'adresse unaire peut être utilisé pour accéder à l'adresse de cet objet.

Supposer que

int i = 1;              
int *p = NULL;

Alors, une déclaration p = &i; , copie l'adresse de la variable i dans le pointeur p .

Il est exprimé en p points à i .

printf("%d\n", *p); imprime 1, qui est la valeur de i .

Arithmétique du pointeur

S'il vous plaît voir ici: Arithmétique Pointeur

void * pointeurs comme arguments et renvoyer des valeurs aux fonctions standard

K & R

void* est un type attrape tous les pointeurs vers les types d'objet. Un exemple de ceci est utilisé avec la fonction malloc , qui est déclarée comme

void* malloc(size_t);

Le type de retour d'un pointeur à un autre signifie qu'il est possible d'affecter la valeur de retour de malloc à un pointeur vers tout autre type d'objet:

int* vector = malloc(10 * sizeof *vector);

Il est généralement considéré comme une bonne pratique de ne pas intégrer explicitement les valeurs dans les pointeurs de vide. Dans le cas spécifique de malloc() c'est parce qu'avec un transtypage explicite, le compilateur peut par ailleurs supposer, mais sans avertir, un type de retour incorrect pour malloc() , si vous oubliez d'inclure stdlib.h . Il s’agit également d’utiliser le comportement correct des pointeurs de vide pour mieux se conformer au principe DRY (ne vous répétez pas); comparez ce qui suit à ce qui suit, dans lequel le code suivant contient plusieurs endroits supplémentaires inutiles où une faute de frappe pourrait causer des problèmes:

int* vector = (int*)malloc(10 * sizeof int*);

De même, des fonctions telles que

void* memcpy(void *restrict target, void const *restrict source, size_t size);

ont leurs arguments spécifiés comme void * car l'adresse de n'importe quel objet, quel que soit son type, peut être transmise. Ici aussi, un appel ne doit pas utiliser de transtypage

unsigned char buffer[sizeof(int)];
int b = 67;
memcpy(buffer, &b, sizeof buffer);

Constants

Pointeurs uniques

  • Pointeur vers un int

    Le pointeur peut pointer vers différents entiers et les int peuvent être modifiés via le pointeur. Cet exemple de code assigne b à pointer sur int b puis modifie la valeur de b à 100 .

    int b;
    int* p;
    p = &b;    /* OK */
    *p = 100;  /* OK */
    
  • Pointeur sur un const int

    Le pointeur peut pointer vers différents entiers mais la valeur de l' int ne peut pas être modifiée via le pointeur.

    int b;
    const int* p;
    p = &b;    /* OK */
    *p = 100;  /* Compiler Error */
    
  • const pointeur sur int

    Le pointeur ne peut pointer que sur un int mais la valeur de l' int peut être modifiée via le pointeur.

    int a, b;
    int* const p = &b; /* OK as initialisation, no assignment */
    *p = 100;  /* OK */
    p = &a;    /* Compiler Error */
    
  • const pointeur sur const int

    Le pointeur ne peut pointer que vers un int et l' int ne peut pas être modifié par le pointeur.

    int a, b;
    const int* const p = &b; /* OK as initialisation, no assignment */
    p = &a;   /* Compiler Error */
    *p = 100; /* Compiler Error */
    

Pointeur vers le pointeur

  • Pointeur sur un pointeur vers un int

    Ce code assigne l'adresse de p1 au double pointeur p (qui pointe alors sur int* p1 (qui pointe sur int )).

    Puis change de p1 pour pointer sur int a . Puis change la valeur de a pour être 100.

    void f1(void)
    {
      int a, b;
      int *p1;
      int **p;
      p1 = &b; /* OK */
      p = &p1; /* OK */
      *p = &a; /* OK */
      **p = 100; /* OK */
    }
    
  • Pointeur vers un pointeur vers un const int

     void f2(void)
    {
      int b;
      const int *p1;
      const int **p;
      p = &p1; /* OK */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • Pointeur sur const pointeur sur un int

    void f3(void)
    {
      int b;
      int *p1;
      int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    
  • const pointeur vers pointeur vers int

    void f4(void)
    {
      int b;
      int *p1;
      int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* OK */
    }
    
  • Pointeur sur const pointeur sur const int

    void f5(void)
    {
      int b;
      const int *p1;
      const int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const pointeur vers pointeur vers const int

    void f6(void)
    {
      int b;
      const int *p1;
      const int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const pointeur vers const pointeur vers int

    void f7(void)
    {
      int b;
      int *p1;
      int * const * const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’  */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    

Même astérisque, significations différentes

Prémisse

L'aspect le plus déroutant entourant la syntaxe de pointeur en C et C ++ est qu'il existe en réalité deux significations différentes qui s'appliquent lorsque le symbole du pointeur, l'astérisque ( * ), est utilisé avec une variable.

Exemple

Tout d'abord, vous utilisez * pour déclarer une variable de pointeur.

int i = 5;
/* 'p' is a pointer to an integer, initialized as NULL */
int *p = NULL;
/* '&i' evaluates into address of 'i', which then assigned to 'p' */
p = &i;
/* 'p' is now holding the address of 'i' */

Lorsque vous ne déclarez pas (ou multipliez), * est utilisé pour déréférencer une variable de pointeur:

*p = 123;
/* 'p' was pointing to 'i', so this changes value of 'i' to 123 */

Lorsque vous souhaitez qu'une variable de pointeur existante contienne l'adresse d'une autre variable, vous n'utilisez pas * , mais faites-le comme ceci:

p = &another_variable;

Une confusion commune entre les débutants de la programmation en C survient lorsqu'ils déclarent et initialisent une variable de pointeur en même temps.

int *p = &i;

Puisque int i = 5; et int i; i = 5; donner le même résultat, certains d'entre eux pourraient penser int *p = &i; et int *p; *p = &i; donner le même résultat aussi. Le fait est que non, int *p; *p = &i; tentera de déférer un pointeur non initialisé qui entraînera UB. N'utilisez jamais * lorsque vous ne déclarez ni ne déréférence un pointeur.

Conclusion

L'astérisque ( * ) a deux significations distinctes dans C par rapport aux pointeurs, selon l'endroit où il est utilisé. Lorsqu'elle est utilisée dans une déclaration de variable , la valeur du côté droit du côté égal doit être une valeur de pointeur vers une adresse en mémoire. Lorsqu'il est utilisé avec une variable déjà déclarée , l'astérisque déréférencera la valeur du pointeur, le suivant à l'emplacement pointé dans la mémoire, et permettant d'attribuer ou de récupérer la valeur stockée à cet endroit.

À emporter

Il est important de se soucier de vos P et Q, pour ainsi dire, quand il s'agit de pointeurs. Soyez conscient du moment où vous utilisez l'astérisque et de ce que cela signifie lorsque vous l'utilisez là. Si vous négligez ce petit détail, vous risquez d’avoir un comportement bogué et / ou indéfini auquel vous ne souhaitez pas avoir à faire face.

Pointeur vers le pointeur

En C, un pointeur peut faire référence à un autre pointeur.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &pA;
  int*** pppA = &ppA;

  printf("%d", ***pppA); /* prints 42 */

  return EXIT_SUCCESS;
}

Mais, référence et référence directement n'est pas autorisé.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &&A; /* Compilation error here! */
  int*** pppA = &&&A;  /* Compilation error here! */

  ...

introduction

Un pointeur est déclaré comme toute autre variable, sauf qu'un astérisque ( * ) est placé entre le type et le nom de la variable pour indiquer qu'il s'agit d'un pointeur.

int *pointer; /* inside a function, pointer is uninitialized and doesn't point to any valid object yet */

Pour déclarer deux variables de pointeur du même type, dans la même déclaration, utilisez le symbole astérisque avant chaque identifiant. Par exemple,

int *iptr1, *iptr2;
int *iptr3,  iptr4;  /* iptr3 is a pointer variable, whereas iptr4 is misnamed and is an int */

L'opérateur d'adresse ou de référence désigné par une esperluette ( & ) donne l'adresse d'une variable donnée qui peut être placée dans un pointeur de type approprié.

int value = 1;
pointer = &value;

L'opérateur d'indirection ou de déréférencement désigné par un astérisque ( * ) récupère le contenu d'un objet désigné par un pointeur.

printf("Value of pointed to integer: %d\n", *pointer);
/* Value of pointed to integer: 1 */

Si le pointeur pointe sur une structure ou un type d'union, vous pouvez le déréférencer et accéder directement à ses membres en utilisant l'opérateur -> :

SomeStruct *s = &someObject;
s->someMember = 5; /* Equivalent to (*s).someMember = 5 */

En C, un pointeur est un type de valeur distinct qui peut être réaffecté, sinon il est traité comme une variable à part entière. Par exemple, l'exemple suivant imprime la valeur du pointeur (variable) lui-même.

printf("Value of the pointer itself: %p\n", (void *)pointer);
/* Value of the pointer itself: 0x7ffcd41b06e4 */
/* This address will be different each time the program is executed */

Comme un pointeur est une variable mutable, il est possible qu'il ne pointe pas vers un objet valide, soit en définissant la valeur null

pointer = 0;     /* or alternatively */
pointer = NULL;

ou simplement en contenant un modèle de bit arbitraire qui n'est pas une adresse valide. La dernière est une très mauvaise situation, car elle ne peut pas être testée avant que le pointeur ne soit déréférencé, il n'y a qu'un test pour le cas où un pointeur est nul:

if (!pointer) exit(EXIT_FAILURE);

Un pointeur ne peut être déréférencé que s'il pointe vers un objet valide , sinon le comportement n'est pas défini. De nombreuses implémentations modernes peuvent vous aider en soulevant un type d'erreur tel qu'une erreur de segmentation et en interrompant l'exécution, mais d'autres peuvent simplement laisser votre programme dans un état invalide.

La valeur renvoyée par l'opérateur de déréférence est un alias mutable à la variable d'origine, de sorte qu'il peut être modifié en modifiant la variable d'origine.

*pointer += 1;
printf("Value of pointed to variable after change: %d\n", *pointer);
/* Value of pointed to variable after change: 2 */

Les pointeurs sont également ré-assignables. Cela signifie qu'un pointeur pointant sur un objet peut être utilisé ultérieurement pour pointer vers un autre objet du même type.

int value2 = 10;
pointer = &value2;
printf("Value from pointer: %d\n", *pointer);
/* Value from pointer: 10 */

Comme toute autre variable, les pointeurs ont un type spécifique. Par exemple, vous ne pouvez pas affecter l'adresse d'un short int à un pointeur long int . Un tel comportement est appelé punition de type et est interdit dans C, bien qu'il y ait quelques exceptions.

Bien que le pointeur doive être d'un type spécifique, la mémoire allouée pour chaque type de pointeur est égale à la mémoire utilisée par l'environnement pour stocker les adresses, plutôt que la taille du type pointé.

#include <stdio.h>

int main(void) {
    printf("Size of int pointer: %zu\n", sizeof (int*));      /* size 4 bytes */
    printf("Size of int variable: %zu\n", sizeof (int));      /* size 4 bytes */
    printf("Size of char pointer: %zu\n", sizeof (char*));    /* size 4 bytes */
    printf("Size of char variable: %zu\n", sizeof (char));    /* size 1 bytes */
    printf("Size of short pointer: %zu\n", sizeof (short*));  /* size 4 bytes */
    printf("Size of short variable: %zu\n", sizeof (short));  /* size 2 bytes */
    return 0;
}

(Remarque: si vous utilisez Microsoft Visual Studio, qui ne prend pas en charge les normes C99 ou C11, vous devez utiliser %Iu 1 au lieu de %zu dans l'exemple ci-dessus.)

Notez que les résultats ci-dessus peuvent varier d'un environnement à l'autre en termes de nombre, mais que tous les environnements affichent des tailles égales pour différents types de pointeurs.

Extrait basé sur des informations de l'Université de Cardiff C Pointers Introduction

Pointeurs et tableaux

Les pointeurs et les tableaux sont intimement liés en C. Les tableaux en C sont toujours situés dans des emplacements contigus en mémoire. L'arithmétique du pointeur est toujours mise à l'échelle en fonction de la taille de l'élément pointé. Donc, si nous avons un tableau de trois doubles et un pointeur sur la base, *ptr réfère au premier double, *(ptr + 1) au second, *(ptr + 2) au troisième. Une notation plus pratique consiste à utiliser la notation de tableau [] .

double point[3] = {0.0, 1.0, 2.0};
double *ptr = point;

/* prints x 0.0, y 1.0 z 2.0 */ 
printf("x %f y %f z %f\n", ptr[0], ptr[1], ptr[2]);

Donc, essentiellement ptr et le nom du tableau sont interchangeables. Cette règle signifie également qu'un tableau se désintègre en un pointeur lorsqu'il est transmis à un sous-programme.

double point[3] = {0.0, 1.0, 2.0};

printf("length of point is %s\n", length(point));

/* get the distance of a 3D point from the origin */ 
double length(double *pt)
{
   return sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2])
}

Un pointeur peut pointer sur un élément d'un tableau ou sur l'élément situé au-delà du dernier élément. C'est cependant une erreur de définir un pointeur sur une autre valeur, y compris l'élément avant le tableau. (La raison en est que sur les architectures segmentées, l'adresse avant le premier élément peut traverser une limite de segment, le compilateur garantit que cela ne se produit pas pour le dernier élément plus un).


Note de bas de page 1: Les informations de format Microsoft peuvent être trouvées via printf() et la syntaxe de spécification de format .

Comportement polymorphe avec des pointeurs de vide

La fonction de bibliothèque standard qsort() est un bon exemple de la façon dont on peut utiliser des pointeurs vides pour faire fonctionner une seule fonction sur une grande variété de types différents.

void qsort (
    void *base,                                 /* Array to be sorted */
    size_t num,                                 /* Number of elements in array */
    size_t size,                                /* Size in bytes of each element */
    int (*compar)(const void *, const void *)); /* Comparison function for two elements */

Le tableau à trier est transmis en tant que pointeur vide, de sorte qu'un tableau de tout type d'élément peut être utilisé. Les deux arguments suivants indiquent à qsort() combien d’éléments il doit attendre dans le tableau et quelle est la taille, en octets, de chaque élément.

Le dernier argument est un pointeur de fonction vers une fonction de comparaison qui prend elle-même deux pointeurs de vide. En faisant en sorte que l'appelant fournisse cette fonction, qsort() peut trier efficacement les éléments de tout type.

Voici un exemple d'une telle fonction de comparaison, pour comparer des flotteurs. Notez que toute fonction de comparaison passée à qsort() doit avoir cette signature de type. La manière dont il est rendu polymorphe consiste à convertir les arguments du pointeur vide en pointeurs du type d’élément que nous souhaitons comparer.

int compare_floats(const void *a, const void *b)
{
    float fa = *((float *)a);
    float fb = *((float *)b);
    if (fa < fb)
        return -1;
    if (fa > fb)
        return 1;
    return 0;
}

Puisque nous savons que qsort utilisera cette fonction pour comparer les flottants, nous renvoyons les arguments du pointeur vide à des pointeurs flottants avant de les déréférencer.

Maintenant, l'utilisation de la fonction polymorphe qsort sur un tableau "array" de longueur "len" est très simple:

qsort(array, len, sizeof(array[0]), compare_floats);


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow