Zoeken…


Invoering

Een aanwijzer is een type variabele waarmee het adres van een ander object of een functie kan worden opgeslagen.

Syntaxis

  • <Gegevenstype> * <Naam variabele>;
  • int * ptrToInt;
  • void * ptrToVoid; / * C89 + * /
  • struct someStruct * ptrToStruct;
  • int ** ptrToPtrToInt;
  • int arr [lengte]; int * ptrToFirstElem = arr; / * Voor <C99 moet 'lengte' een compilatietijdconstante zijn, voor> = C11 moet dit er mogelijk een zijn. * /
  • int * arrayOfPtrsToInt [lengte]; / * Voor <C99 moet 'lengte' een compilatietijdconstante zijn, voor> = C11 moet dit er mogelijk een zijn. * /

Opmerkingen

De positie van het sterretje heeft geen invloed op de betekenis van de definitie:

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

Wanneer u echter meerdere aanwijzers tegelijk definieert, heeft elk zijn eigen asterisk nodig:

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 */

Een reeks aanwijzers is ook mogelijk, waarbij een asterisk wordt gegeven vóór de naam van de matrixvariabele:

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

Veel voorkomende fouten

Onjuist gebruik van pointers is vaak een bron van bugs, waaronder beveiligingsbugs of programmacrashes, meestal vanwege segmentatiefouten.

Niet controleren op toewijzingsfouten

Het is niet gegarandeerd dat geheugentoewijzing slaagt en kan in plaats daarvan een NULL aanwijzer retourneren. Het gebruik van de geretourneerde waarde, zonder te controleren of de toewijzing succesvol is, roept ongedefinieerd gedrag op . Dit leidt meestal tot een crash, maar er is geen garantie dat een crash zal gebeuren, dus erop vertrouwen kan ook tot problemen leiden.

Bijvoorbeeld op een onveilige manier:

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

Veilige manier:

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

Letterlijke cijfers gebruiken in plaats van sizeof bij het aanvragen van geheugen

Voor een gegeven compiler / machine-configuratie hebben types een bekende grootte; er is echter geen standaard die bepaalt dat de grootte van een bepaald type (behalve char ) hetzelfde zal zijn voor alle compiler / machine-configuraties. Als de code 4 gebruikt in plaats van sizeof(int) voor geheugentoewijzing, werkt deze mogelijk op de oorspronkelijke machine, maar de code is niet noodzakelijkerwijs overdraagbaar naar andere machines of compilers. Vaste formaten voor typen moeten worden vervangen door sizeof(that_type) of sizeof(*var_ptr_to_that_type) .

Niet-draagbare toewijzing:

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

Draagbare toewijzing:

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

Of, nog beter:

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

Geheugenlekken

Het niet toewijzen van geheugen met behulp van free leidt tot een opeenhoping van niet-herbruikbaar geheugen, dat niet langer door het programma wordt gebruikt; dit wordt een geheugenlek genoemd . Geheugenlekken verspillen geheugenbronnen en kunnen leiden tot toewijzingsfouten.

Logische fouten

Alle toewijzingen moeten hetzelfde patroon volgen:

  1. Toewijzing met behulp van malloc (of calloc )
  2. Gebruik om gegevens op te slaan
  3. De-toewijzing met free

Het niet naleven van dit patroon, zoals het gebruik van geheugen na een oproep om free ( hangende wijzer ) of vóór een oproep naar malloc ( wilde wijzer ), tweemaal free bellen ("dubbel vrij"), enz., Veroorzaakt meestal een segmentatiefout en resulteert in een crash van het programma.

Deze fouten kunnen van voorbijgaande aard zijn en moeilijk te debuggen - vrijgemaakt geheugen wordt bijvoorbeeld meestal niet onmiddellijk door het besturingssysteem teruggevorderd en daarom kunnen hangende pointers een tijdje aanhouden en lijken te werken.

Op systemen waar het werkt, is Valgrind een hulpmiddel van onschatbare waarde om te identificeren welk geheugen is gelekt en waar het oorspronkelijk was toegewezen.

Aanwijzers maken om variabelen te stapelen

Het creëren van een aanwijzer verlengt niet de levensduur van de variabele waarnaar wordt verwezen. Bijvoorbeeld:

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

Hier heeft x automatische opslagduur (algemeen bekend als stackallocatie ). Omdat het op de stapel wordt toegewezen, is de levensduur ervan slechts zolang myFunction wordt uitgevoerd; nadat myFunction is afgesloten, wordt de variabele x vernietigd. Deze functie haalt het adres van x (met &x ) en geeft dit terug aan de beller, waardoor de beller een pointer naar een niet-bestaande variabele krijgt. Poging om toegang te krijgen tot deze variabele zal dan ongedefinieerd gedrag oproepen.

De meeste compilers wissen een stapelframe niet nadat de functie is afgesloten, dus als u de geretourneerde aanwijzer niet meer gebruikt, krijgt u vaak de verwachte gegevens. Wanneer echter een andere functie wordt aangeroepen, kan het geheugen waarnaar wordt verwezen, worden overschreven en het lijkt erop dat de gegevens waarnaar wordt verwezen, zijn beschadigd.

Om dit op te lossen, ofwel malloc de opslagruimte voor de variabele worden teruggebracht, en terug te keren een pointer naar de nieuw gecreëerde opslag, of eisen dat een geldige pointer wordt doorgegeven aan de functie in plaats van de terugkeer op een, bijvoorbeeld:

#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;
}

Verhogen / verlagen en dereferencing

Als je *p++ schrijft om te verhogen wat wordt aangeduid met p , heb je het mis.

Verhogen / verlagen van de post wordt uitgevoerd vóór dereferencing. Daarom zal deze uitdrukking de aanwijzer p zelf ophogen en retourneren wat door p werd aangegeven voordat hij werd verhoogd zonder deze te wijzigen.

Je moet (*p)++ om te verhogen wat wordt aangegeven door p .

Deze regel is ook van toepassing op post-verlaging: *p-- de aanwijzer p zelf, niet wat wordt aangegeven door p .

Verwijzen naar een aanwijzer

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

Om a_pointer en de waarde van a te wijzigen, gebruiken we de volgende bewerking

*a_pointer = 2;

Dit kan worden geverifieerd met behulp van de volgende afdrukverklaringen.

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

Men zou zich echter vergissen om een NULL of anderszins ongeldige wijzer af te leiden. Deze

int *p1, *p2;

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

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

is meestal ongedefinieerd gedrag . p1 mag niet worden verwijderd als referentie omdat het 0xbad naar een adres 0xbad dat mogelijk geen geldig adres is. Wie weet wat daar is? Het kan besturingssysteemgeheugen zijn of het geheugen van een ander programma. De enige tijdcode zoals deze wordt gebruikt, is in embedded ontwikkeling, die bepaalde informatie op hard-gecodeerde adressen opslaat. p2 kan niet meer worden verwijderd omdat het NULL , wat ongeldig is.

Verwijzen naar een aanwijzer naar een struct

Laten we zeggen dat we de volgende structuur hebben:

struct MY_STRUCT 
{
    int my_int;
    float my_float;
};

We kunnen MY_STRUCT definiëren om het struct sleutelwoord weg te laten, zodat we niet telkens struct MY_STRUCT hoeven te typen. Dit is echter optioneel.

typedef struct MY_STRUCT MY_STRUCT;

Als we dan een verwijzing hebben naar een instantie van deze structuur

MY_STRUCT *instance;

Als deze instructie wordt weergegeven als bestandsbereik, wordt instance geïnitialiseerd met een lege aanwijzer wanneer het programma wordt gestart. Als deze instructie in een functie wordt weergegeven, is de waarde ervan niet gedefinieerd. De variabele moet worden geïnitialiseerd om te verwijzen naar een geldige MY_STRUCT variabele, of naar dynamisch toegewezen ruimte, voordat er naar de variabele kan worden verwezen. Bijvoorbeeld:

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

Wanneer de aanwijzer geldig is, kunnen we de toegang tot zijn leden opheffen met behulp van een van twee verschillende notaties:

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

Hoewel beide methoden werken, is het beter om de pijl -> operator te gebruiken in plaats van de combinatie van haakjes, de dereference * operator en de stip . omdat het gemakkelijker te lezen en te begrijpen is, vooral bij genest gebruik.

Een ander belangrijk verschil wordt hieronder getoond:

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

In dit geval bevat copy een kopie van de inhoud van een instance . Het my_int van mijn copy zal dit instance niet veranderen.

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

In dit geval is ref een verwijzing naar een instance . Door my_int wijzigen my_int behulp van de referentie, wordt deze instance gewijzigd.

Het is gebruikelijk om verwijzingen naar structs te gebruiken als parameters in functies, in plaats van de structs zelf. Als de structs als functieparameters worden gebruikt, kan de stapel overlopen als de struct groot is. Het gebruik van een pointer naar een struct gebruikt alleen voldoende stapelruimte voor de pointer, maar kan bijwerkingen veroorzaken als de functie de struct verandert die in de functie wordt doorgegeven.

Functie wijzers

Aanwijzers kunnen ook worden gebruikt om naar functies te wijzen.

Laten we een basisfunctie nemen:

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

Laten we nu een aanwijzer van het type van die functie definiëren:

int (*my_pointer)(int, int);

Gebruik gewoon deze sjabloon om er een te maken:

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

Vervolgens moeten we deze aanwijzer toewijzen aan de functie:

my_pointer = &my_function;

Deze aanwijzer kan nu worden gebruikt om de functie aan te roepen:

/* 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);
}

Hoewel deze syntaxis natuurlijker en coherenter lijkt met basistypen, zijn voor het toewijzen en dereferencen van functie-aanwijzers geen gebruik van & en * -operators vereist. Het volgende fragment is dus even geldig:

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

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

Om de leesbaarheid van functie-aanwijzers te vergroten, kunnen typedefs worden gebruikt.

typedef void (*Callback)(int a);

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

Een andere truc voor leesbaarheid is dat de C-standaard het mogelijk maakt om een functiepointer te vereenvoudigen in argumenten zoals hierboven (maar niet in variabele declaratie) tot iets dat lijkt op een functieprototype; dus het volgende kan op gelijkwaardige wijze worden gebruikt voor functiedefinities en verklaringen:

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

Zie ook

Functie wijzers

Aanwijzers initialiseren

Initialisatie van de aanwijzer is een goede manier om wilde aanwijzers te vermijden. De initialisatie is eenvoudig en verschilt niet van de initialisatie van een variabele.

#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 */

    ...
}    

In de meeste besturingssystemen leidt het onbedoeld gebruiken van een aanwijzer die op NULL is geïnitialiseerd, vaak tot onmiddellijke crashing van het programma, waardoor de oorzaak van het probleem gemakkelijk kan worden achterhaald. Het gebruik van een niet-geïnitialiseerde aanwijzer kan vaak moeilijk te diagnosticeren bugs veroorzaken.

Voorzichtigheid:

Het resultaat van het dereferen van een NULL aanwijzer is niet gedefinieerd, dus het hoeft niet noodzakelijkerwijs een crash te veroorzaken, zelfs als dat het natuurlijke gedrag is van het besturingssysteem waarop het programma wordt uitgevoerd. Compileroptimalisaties kunnen de crash maskeren, ervoor zorgen dat de crash plaatsvindt vóór of na het punt in de broncode waarop de foutieve verwijzing naar de nulwaarde plaatsvond, of ervoor zorgen dat delen van de code die de foutieve referentie naar de nul bevat onverwacht uit het programma worden verwijderd. Debug builds zullen dit gedrag meestal niet vertonen, maar dit wordt niet gegarandeerd door de taalstandaard. Ander onverwacht en / of ongewenst gedrag is ook toegestaan.

Omdat NULL nooit verwijst naar een variabele, naar toegewezen geheugen of naar een functie, is het veilig om als bewakingswaarde te gebruiken.

Voorzichtigheid:

Gewoonlijk wordt NULL gedefinieerd als (void *)0 . Maar dit betekent niet dat het toegewezen geheugenadres 0x0 . Raadpleeg C-faq voor NULL-verwijzingen voor meer uitleg

Merk op dat u ook pointers kunt initialiseren die andere waarden dan NULL bevatten.

int i1;

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

Adres van operator (&)

Voor elk object (dwz variabele, array, unie, struct, pointer of functie) kan de unaire adresoperator worden gebruikt om toegang te krijgen tot het adres van dat object.

Stel dat

int i = 1;              
int *p = NULL;

Dus dan een verklaring p = &i; , kopieert het adres van de variabele i naar de aanwijzer p .

Het wordt uitgedrukt als p wijst naar i .

printf("%d\n", *p); drukt 1 af, wat de waarde is van i .

Aanwijzer rekenen

Zie hier: Pointer Arithmetic

void * verwijst naar argumenten en retourneert waarden naar standaardfuncties

K & R

void* is een catch all type voor verwijzingen naar objecttypen. Een voorbeeld hiervan is de malloc functie, die wordt aangegeven als

void* malloc(size_t);

Het retourtype pointer-to-void betekent dat het mogelijk is om de retourwaarde van malloc aan een pointer toe te wijzen aan elk ander type object:

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

Het wordt in het algemeen als een goede praktijk beschouwd om de waarden niet expliciet in en uit lege aanwijzers te werpen. In specifiek geval van malloc() dit omdat bij een expliciete cast de compiler anders een onjuist stdlib.h voor malloc() kan aannemen, maar er niet voor waarschuwt, als u vergeet stdlib.h . Het is ook een geval van het gebruik van het juiste gedrag van nietige verwijzingen om beter te voldoen aan het DROGE (niet herhalen) principe; vergelijk het bovenstaande met het volgende, waarbij de volgende code verschillende onnodige extra plaatsen bevat waar een typfout problemen kan veroorzaken:

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

Evenzo functies zoals

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

hun argumenten zijn void * omdat het adres van elk object, ongeacht het type, kan worden doorgegeven. Ook hier mag een oproep geen cast gebruiken

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

Const Aanwijzers

Enkele aanwijzers

  • Aanwijzer naar een int

    De aanwijzer kan naar verschillende gehele getallen wijzen en de int 's kunnen via de aanwijzer worden gewijzigd. Dit codevoorbeeld wijst b toe naar int b en wijzigt vervolgens de waarde van b in 100 .

    int b;
    int* p;
    p = &b;    /* OK */
    *p = 100;  /* OK */
    
  • Aanwijzer naar een const int

    De aanwijzer kan naar verschillende gehele getallen wijzen, maar de waarde van de int kan niet door de aanwijzer worden gewijzigd.

    int b;
    const int* p;
    p = &b;    /* OK */
    *p = 100;  /* Compiler Error */
    
  • const pointer naar int

    De aanwijzer kan slechts naar één int wijzen, maar de waarde van de int kan door de aanwijzer worden gewijzigd.

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

    De aanwijzer kan slechts naar één int wijzen en de int kan niet via de aanwijzer worden gewijzigd.

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

Aanwijzer naar aanwijzer

  • Aanwijzer naar een aanwijzer naar een int

    Deze code wijst het adres van p1 aan de naar dubbele aanwijzer p (die vervolgens naar int* p1 wijst (die naar int wijst)).

    Verandert vervolgens p1 om te wijzen naar int a . Hiermee wijzigt u de waarde van a in 100.

    void f1(void)
    {
      int a, b;
      int *p1;
      int **p;
      p1 = &b; /* OK */
      p = &p1; /* OK */
      *p = &a; /* OK */
      **p = 100; /* OK */
    }
    
  • Aanwijzer naar aanwijzer naar een 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’ */
    }
    
  • Wijzer naar const wijzer naar een 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 pointer to pointer to 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 */
    }
    
  • Aanwijzer naar const Aanwijzer naar 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 pointer to pointer to 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 pointer to const pointer to 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 */
    }
    

Dezelfde asterisk, verschillende betekenissen

premisse

Het meest verwarrende aan de syntaxis van de aanwijzer in C en C ++ is dat er eigenlijk twee verschillende betekenissen zijn die van toepassing zijn wanneer het aanwijzersymbool, de asterisk ( * ), wordt gebruikt met een variabele.

Voorbeeld

Ten eerste gebruikt u * om een pointervariabele te declareren .

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' */

Wanneer u niet declareert (of vermenigvuldigt), wordt * gebruikt om een verwijzing naar een pointervariabele te herleiden :

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

Wanneer u een bestaande pointer variabele hold-adres van de andere variabele wilt, gebruik je niet * , maar doe het als volgt:

p = &another_variable;

Een veel voorkomende verwarring bij C-programmeringsnieuwkomers ontstaat wanneer ze tegelijkertijd een pointervariabele declareren en initialiseren.

int *p = &i;

Omdat int i = 5; en int i; i = 5; geven hetzelfde resultaat, sommigen van hen dachten misschien int *p = &i; en int *p; *p = &i; hetzelfde resultaat geven. Het feit is, nee, int *p; *p = &i; zal proberen een niet-geïnitialiseerde aanwijzer te defereren die resulteert in UB. Gebruik nooit * wanneer u een aanwijzer niet declareert of derefereert.

Conclusie

De asterisk ( * ) heeft twee verschillende betekenissen binnen C met betrekking tot wijzers, afhankelijk van waar het wordt gebruikt. Bij gebruik in een variabele declaratie moet de waarde aan de rechterkant van de gelijk-zijde een pointerwaarde zijn naar een adres in het geheugen. Bij gebruik met een reeds gedeclareerde variabele , zal de asterisk de verwijzing naar de aanwijzingswaarde herleiden , en deze volgen naar de aangewezen plaats in het geheugen, en toestaan dat de daar opgeslagen waarde wordt toegewezen of opgehaald.

Meenemen

Het is belangrijk om bij wijze van spreken rekening te houden met uw P's en Q's. Let op wanneer u de asterisk gebruikt en wat het betekent wanneer u deze gebruikt. Als u dit kleine detail over het hoofd ziet, kan dit leiden tot fouten en / of ongedefinieerd gedrag waar u echt niet mee te maken wilt hebben.

Aanwijzer naar aanwijzer

In C kan een aanwijzer verwijzen naar een andere aanwijzer.

#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;
}

Maar referentie-en-referentie is niet toegestaan.

#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! */

  ...

Invoering

Een pointer wordt net als elke andere variabele gedeclareerd, behalve dat er een asterisk ( * ) tussen het type en de naam van de variabele wordt geplaatst om aan te geven dat het een pointer is.

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

Om twee pointervariabelen van hetzelfde type, in dezelfde aangifte, te declareren, gebruikt u het asterisk-symbool voor elke identifier. Bijvoorbeeld,

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

De adres- of referentie-operator aangegeven met een en-teken ( & ) geeft het adres van een gegeven variabele die in een geschikte aanwijzer kan worden geplaatst.

int value = 1;
pointer = &value;

De indirection- of dereference-operator die wordt aangegeven met een asterisk ( * ), krijgt de inhoud van een object waarnaar wordt verwezen door een aanwijzer.

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

Als de aanwijzer naar een structuur of unietype verwijst, kunt u de verwijzing naar deze structuur opheffen en rechtstreeks toegang krijgen tot de leden met de operator -> :

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

In C is een aanwijzer een verschillend waardetype dat opnieuw kan worden toegewezen en anders als een variabele op zichzelf wordt behandeld. In het volgende voorbeeld wordt bijvoorbeeld de waarde van de aanwijzer (variabele) zelf afgedrukt.

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 */

Omdat een aanwijzer een veranderlijke variabele is, is het mogelijk dat deze niet naar een geldig object verwijst, hetzij door op nul te worden ingesteld

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

of gewoon door een willekeurig bitpatroon te bevatten dat geen geldig adres is. Dit laatste is een zeer slechte situatie, omdat het niet kan worden getest voordat de aanwijzer wordt verwijderd, er is alleen een test voor het geval dat een aanwijzer nul is:

if (!pointer) exit(EXIT_FAILURE);

Een aanwijzer mag alleen worden verwijderd als deze naar een geldig object verwijst, anders is het gedrag niet gedefinieerd. Veel moderne implementaties kunnen u helpen door een soort fout op te roepen, zoals een segmentatiefout en de uitvoering te beëindigen, maar anderen kunnen uw programma gewoon in een ongeldige staat laten.

De waarde die door de dereference-operator wordt geretourneerd, is een veranderlijke alias voor de oorspronkelijke variabele, dus deze kan worden gewijzigd door de oorspronkelijke variabele te wijzigen.

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

Aanwijzers kunnen ook opnieuw worden toegewezen. Dit betekent dat een aanwijzer die naar een object verwijst, later kan worden gebruikt om naar een ander object van hetzelfde type te wijzen.

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

Net als elke andere variabele hebben aanwijzers een specifiek type. Je kunt bijvoorbeeld het adres van een short int aan een pointer niet toewijzen aan een long int . Dergelijk gedrag wordt type punning genoemd en is verboden in C, hoewel er enkele uitzonderingen zijn.

Hoewel de aanwijzer van een specifiek type moet zijn, is het toegewezen geheugen voor elk type aanwijzer gelijk aan het geheugen dat door de omgeving wordt gebruikt om adressen op te slaan, in plaats van de grootte van het type waarnaar wordt gewezen.

#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;
}

(NB: als u Microsoft Visual Studio gebruikt, die de C99- of C11-normen niet ondersteunt, moet u %Iu 1 gebruiken in plaats van %zu in het bovenstaande voorbeeld.)

Merk op dat de bovenstaande resultaten kunnen variëren van omgeving tot omgeving in aantallen, maar alle omgevingen zouden dezelfde grootte hebben voor verschillende soorten aanwijzers.

Uittreksel gebaseerd op informatie van C Carders University C Pointers Inleiding

Aanwijzers en arrays

Aanwijzers en arrays zijn nauw verbonden in C. Arrays in C worden altijd op aaneengesloten locaties in het geheugen bewaard. Rekenkundige aanwijzer wordt altijd geschaald met de grootte van het item waarnaar wordt verwezen. Als we dus een reeks van drie dubbele waarden hebben en een wijzer naar de basis, verwijst *ptr naar de eerste dubbele, *(ptr + 1) naar de tweede, *(ptr + 2) naar de derde. Een handiger notatie is het gebruik van matrixnotatie [] .

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]);

Dus in wezen zijn ptr en de arraynaam uitwisselbaar. Deze regel betekent ook dat een array vervalt naar een pointer wanneer deze wordt doorgegeven aan een subroutine.

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])
}

Een aanwijzer kan naar elk element in een array verwijzen, of naar het element voorbij het laatste element. Het is echter een fout om een aanwijzer op een andere waarde in te stellen, inclusief het element vóór de array. (De reden is dat op gesegmenteerde architecturen het adres vóór het eerste element een segmentgrens mag overschrijden, de compiler zorgt ervoor dat dit niet gebeurt voor het laatste element plus één).


Voetnoot 1: Microsoft-indelingsinformatie is te vinden via printf() en syntaxis van de indelingsspecificatie .

Polymorf gedrag met lege wijzers

De standaard bibliotheekfunctie van qsort() is een goed voorbeeld van hoe men lege aanwijzers kan gebruiken om een enkele functie op een groot aantal verschillende typen te laten werken.

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 */

De te sorteren array wordt als een lege aanwijzer doorgegeven, zodat een array van elk type element kan worden bediend. De volgende twee argumenten vertellen qsort() hoeveel elementen het in de array moet verwachten, en hoe groot, in bytes, elk element is.

Het laatste argument is een functiepointer naar een vergelijkingsfunctie die zelf twee lege aanwijzers aanneemt. Door de beller deze functie te laten bieden, kan qsort() elementen van elk type effectief sorteren.

Hier is een voorbeeld van een dergelijke vergelijkingsfunctie, voor het vergelijken van drijvers. Merk op dat elke vergelijkingsfunctie die wordt doorgegeven aan qsort() deze typeaanduiding moet hebben. De manier waarop het polymorf wordt gemaakt, is door de ongeldige aanwijzingsargumenten te werpen op verwijzingen naar het type element dat we willen vergelijken.

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;
}

Omdat we weten dat qsort deze functie zal gebruiken om floats te vergelijken, werpen we de ongeldige aanwijzingsargumenten terug naar float-pointers voordat ze naar de referentie worden afgeleid.

Het gebruik van de polymorfe functie qsort op een array "array" met lengte "len" is heel eenvoudig:

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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow