Pointers en geheugenadressen in C

Leer hoe een C-programma zijn variabelen in het geheugen plaatst en hoe je met pointers rechtstreeks met geheugenadressen werkt. Vraag adressen op met &, lees en wijzig waarden via pointers en geef variabelen via verwijzing door aan functies. Doorloop arrays en strings met pointerrekenen, gebruik pointers naar pointers voor opdrachtregelargumenten en voor functies die een pointer van de aanroepende functie verplaatsen, en roep functies aan via functiepointers, waaronder vergelijkingsfuncties voor de standaardfunctie qsort.

BeginnerLes 4 van 8 in Cursus Programmeren in C: pointers, geheugenbeheer en debuggen

Wat je leert

  1. Breng in beeld hoe variabelen van een C-programma bytes in het geheugen innemen, sla adressen op in pointervariabelen en gebruik die pointers om de variabelen waarnaar ze wijzen te lezen en te wijzigen.
    • Breng in beeld hoe variabelen in het geheugen zijn geplaatst
      Beschrijf geheugen als genummerde bytes, bepaal de grootte en het adres van variabelen en arrayelementen en leg uit hoelang elke soort variabele bestaat.
    • Declareer pointers en vraag adressen op met &
      Declareer pointervariabelen van het juiste type, sla het adres van een variabele op met de adresoperator en druk een pointer veilig af, laat die naar iets anders wijzen of stel die opnieuw in.
    • Lees en wijzig waarden via pointers
      Gebruik de dereferentieoperator om het object waarnaar een pointer wijst te lezen en te wijzigen, volg code met geheugendiagrammen en voorkom dat je ongeldige pointers derefereert.
  2. Schrijf functies die via pointerparameters variabelen van de aanroepende functie wijzigen, bescherm gegevens die alleen worden gelezen met const en geef pointers terug die geldig blijven.
    • Geef variabelen via verwijzing door met pointerparameters
      Leg uit waarom C-functies kopieën van hun argumenten ontvangen en gebruik pointerparameters om variabelen van de aanroepende functie te wijzigen, waarden te verwisselen, meerdere resultaten terug te geven en invoer te lezen met scanf.
    • Bescherm gegevens met const en geef pointers veilig terug
      Markeer gegevens die alleen worden gelezen met const, onderscheid een pointer naar een const-gekwalificeerd object van een constante pointer en geef pointers terug die na afloop van de functie geldig blijven.
  3. Verplaats pointers door arrays en strings, leg uit hoe arraynamen en pointers samenhangen en houd elke pointer binnen de array waartoe die behoort.
    • Reken met pointers
      Tel gehele getallen op bij pointers, trek pointers naar posities in dezelfde array van elkaar af, vergelijk ze en weet welke resultaten geldig zijn.
    • Leg uit hoe arrays en pointers samenhangen
      Gebruik de conversie van een arraynaam naar een pointer en de gelijkheid van a[i] en *(a + i), en ken de werkelijke verschillen tussen een array en een pointer.
    • Doorloop arrays en strings met pointers
      Schrijf lussen die arrays en strings met een afsluitend nulteken doorlopen met pointers, gebruik constructies zoals *p++ correct en blijf binnen de grenzen.
  4. Volg meerdere indirectieniveaus, laat functies een pointer van de aanroepende functie wijzigen en gebruik arrays van pointers zoals de opdrachtregelargumenten.
    • Gebruik pointers naar pointers
      Declareer en derefereer een pointer naar een pointer, volg die in een geheugendiagram en gebruik die om een functie een pointer van de aanroepende functie te laten wijzigen.
    • Gebruik arrays van pointers en opdrachtregelargumenten
      Maak arrays van stringpointers, verwerk argc en argv als pointers en onderscheid een array van pointers van een tweedimensionale array.
  5. Sla functies op en roep ze aan via pointers, geef functies door aan andere functies en sorteer gegevens van elk type met de standaardfunctie qsort en een vergelijkingsfunctie als callback.
    • Declareer functiepointers, wijs er waarden aan toe en roep functies ermee aan
      Schrijf en lees declaraties van functiepointers, roep er functies mee aan en maak tabellen met functies die tijdens de uitvoering van het programma gedrag kiezen.
    • Schrijf callbackfuncties en sorteer met qsort
      Geef functies door als argumenten, gebruik void-pointers om gegevens van elk type te verwerken en schrijf correcte vergelijkingsfuncties voor qsort.

Pointers en geheugenadressen in C: uitleg

5 hoofdstukken

1

Geheugen, adressen en waarden waarnaar wordt gewezen

Een variabele heeft een waarde, neemt opslagruimte in en heeft een levensduur. Met een pointer bereik je die variabele via haar adres. Code lezen betekent nu dus dat je zowel het adres in een pointer als de waarde op dat adres volgt. Begin met de indeling in bytes en volg daarna de gevolgen van een adres opvragen, een pointer naar iets anders laten wijzen en via een pointer schrijven. Kleine geheugendiagrammen maken deze verschillende bewerkingen zichtbaar voordat ze in functies en lussen voorkomen.

1.1 Bytes, groottes en levensduur

Je kunt geheugen voorstellen als een reeks genummerde bytes. Elk nummer is een adres. Een object zoals een int neemt opeenvolgende bytes in en het adres duidt de eerste byte aan. Via de naam die je in de broncode gebruikt, kan de compiler dat object vinden; de taal vereist niet dat naast de waarde een naamlabel wordt opgeslagen.

Meet opslag met sizeof

sizeof geeft het aantal bytes en levert een waarde van het type size_t op. Gebruik %zu om die af te drukken:

int scores[4] = {12, 18, 15, 21};
printf("%zu %zu\n", sizeof scores, sizeof scores[0]);

Bij int-waarden van 4 bytes drukt dit 16 4 af. Het eerste resultaat meet alle vier de elementen; het tweede meet er één. sizeof(char) is altijd 1. Een gangbare indeling op een 64-bits desktop gebruikt int-waarden van 4 bytes, double-waarden van 8 bytes en objectpointers van 8 bytes. Op andere doelsystemen kan dit verschillen. Ook bij 64-bitssystemen neemt long op Linux en macOS vaak 8 bytes in, maar op Windows 4.

Opslaggrootte en tekstlengte beantwoorden verschillende vragen. Voor char word[20] = "hi"; is sizeof word gelijk aan 20, terwijl strlen(word) 2 is. De array bevat nog steeds 20 elementen, hoewel de string maar twee letters en een afsluitend nulteken gebruikt. Behalve bij arrays met variabele lengte liggen deze objectgroottes vast tijdens de compilatie.

Volg de adressen van een array

Hexadecimale adressen gebruiken de cijfers 0 tot en met 9 en daarna a tot en met f. Het voorvoegsel 0x duidt grondtal 16 aan. Voor een voorbeeldarray die begint op 0x1000, beginnen vier elementen van 4 bytes op:

ElementEerste byteLaatste byte
scores[0]0x10000x1003
scores[1]0x10040x1007
scores[2]0x10080x100B
scores[3]0x100C0x100F

0x100C is decimaal 4.108, twaalf bytes voorbij 0x1000. Tussen arrayelementen zit geen ruimte. Voor afzonderlijk gedeclareerde variabelen is geen volgorde of afstand gegarandeerd; ook echte afgedrukte adressen kunnen tussen uitvoeringen veranderen. Een compiler kan een waarde in een processorregister bewaren als geen adresseerbare opslag nodig is.

Uitlijning en bytevolgorde

Uitlijning beperkt waar objecten van een type kunnen beginnen. Een gangbare indeling plaatst int-waarden van 4 bytes op veelvouden van 4 en double-waarden van 8 bytes op veelvouden van 8. De compiler zorgt voor een geschikte uitlijning van gedeclareerde objecten.

Bytevolgorde is de volgorde van bytes binnen een waarde die uit meerdere bytes bestaat. Voor een unsigned int van 4 bytes met 0x12345678 staan bij little-endianopslag de bytes 78 56 34 12 van lage naar hoge adressen; bij big-endianopslag zijn dat 12 34 56 78. Met een unsigned char * kun je de representatie van een object byte voor byte bekijken. De bytevolgorde verandert de indexvolgorde van de array niet.

Houd het object in leven

Een gewone lokale variabele heeft automatische opslagduur: haar levensduur duurt zolang haar blok wordt uitgevoerd. Gangbare implementaties bewaren zulke objecten in het stackframe van een aanroep en gebruiken die opslag daarna opnieuw. Globale variabelen, static-variabelen en stringliterals bestaan gedurende het hele programma. Een adres opslaan verlengt de levensduur van het object niet.

Belangrijkste punten

  • sizeof meet bytes; strlen telt de tekens vóór het afsluitende nulteken van een string.
  • Arrayelementen nemen aaneengesloten opslag in indexvolgorde in.
  • Voor afzonderlijke variabelen zijn de adressen ten opzichte van elkaar niet gegarandeerd.
  • Uitlijning bepaalt geschikte beginadressen; bytevolgorde bepaalt de volgorde van bytes binnen een waarde.
  • Een opgeslagen adres blijft alleen bruikbaar zolang het object bestaat.

1.2 Sla een adres op en toon het

Een pointer slaat een adres op en heeft een type dat het object beschrijft waartoe die toegang kan krijgen. Als je een int * declareert, krijg je opslag voor een pointer; je maakt daarmee geen int die de pointer kan gebruiken. Geef de pointer een bekende waarde wanneer je die declareert.

Vraag het adres van een object op
int count = 42;
int *p = &count;

De unaire adresoperator & vraagt het adres van count op. Als count begint op het voorbeeldadres 0x1000, bevat p de waarde 0x1000, terwijl count 42 bevat. De pointer is ook een object: &p is het adres van de afzonderlijke opslag die die pointer bevat.

Je kunt het adres van een arrayelement opvragen, zoals &scores[2]. Een numerieke constante of een berekeningsresultaat is geen opgeslagen object waarvan je op deze manier het adres kunt opvragen: &42 en &(count + 1) zijn ongeldig. De binaire expressie a & b gebruikt hetzelfde symbool voor een bitsgewijze EN; het aantal operanden vertelt je welke bewerking wordt bedoeld.

Lees elke declarator
DeclaratieBetekenis
int *p, q;Eén pointer en één gewone int
int *p, *q;Twee pointers naar int
double *r;Eén pointer naar double

Witruimte verandert geen van deze typen. int* p en int *p zijn gelijkwaardig. Als je één pointer per regel declareert, zie je de ster minder snel over het hoofd. Op gangbare 64-bits desktops neemt een objectpointer 8 bytes in, maar de eigen grootte vertelt je niet hoe groot het object is waarnaar de pointer wijst.

Het type van het doelobject is belangrijk. Gebruik voor double price = 9.5; de declaratie double *r = &price;. Als je een int * initialiseert met &price, krijg je een melding over incompatibele pointers. Een expliciete typeconversie die de melding verbergt, maakt een toegang tot die double als int niet geldig: het type bepaalt hoe de opgeslagen bytes worden benaderd.

Laat een pointer naar iets anders wijzen
int a = 5, b = 8;
int *p = &a;
p = &b;

De laatste toewijzing vervangt het adres in p. Geen van beide gehele getallen wordt verplaatst en de waarde van het ene getal wordt niet naar het andere gekopieerd. Nu wijst p naar b; a is nog steeds 5 en b nog steeds 8. Meerdere pointervariabelen kunnen ook hetzelfde adres opslaan.

Gebruik een bekende lege toestand

Een niet-geïnitialiseerde lokale pointer heeft een onbepaalde waarde en wordt soms een wilde pointer genoemd. Bekijk of derefereer die niet. Initialiseer de pointer met het adres van een geldig object of met NULL, beschikbaar via onder meer de headers <stddef.h> en <stdio.h>:

int *pending = NULL;
if (pending == NULL) {
    pending = &count;
}

Een nulpointer duidt geen object aan. !pending is een andere test op een nulpointer. Deze tests zijn alleen zinvol voor geïnitialiseerde pointers; ze valideren geen willekeurig adres of pointer waarvan het object niet meer bestaat.

Druk een objectpointer af
printf("%p\n", (void *)p);

%p verwacht void *, dus gebruik de expliciete typeconversie bij een getypeerde objectpointer. De implementatie kiest de afgedrukte representatie; die is meestal hexadecimaal. %d en %x verwachten gehele getallen van bepaalde typen als argumenten en kunnen %p niet vervangen.

Belangrijkste punten

  • Initialiseer een pointer met een geldig adres of NULL.
  • &count is een adres; count is het opgeslagen gehele getal.
  • Elke pointernaam heeft in een declaratie een eigen * nodig.
  • Pointertypen moeten passen bij de objecten die je ermee benadert.
  • p = &b wijzigt de pointer en laat de gehele getallen ongemoeid.
  • Druk een objectpointer af met %p en een argument van het type void *.

1.3 Volg een pointer om te lezen of te schrijven

De unaire dereferentieoperator * volgt een pointer naar het bijbehorende object. Als p naar x wijst, duidt *p de variabele x zelf aan. Als je *p leest, lees je dat object; als je een waarde toewijst aan *p, wijzig je het object.

Volg een schrijfbewerking via een adres
int x = 10;
int *p = &x;
*p = *p + 5;
int y = *p * 2;
printf("%d %d %d\n", x, *p, y);

Eerst slaat p het adres van x op. Aan de rechterkant van de toewijzing leest *p de waarde 10. Als je er 5 bij optelt, krijg je 15 en de *p aan de linkerkant slaat 15 op in x. De volgende regel vermenigvuldigt de bijgewerkte waarde met 2, zodat y gelijk wordt aan 30. De uitvoer is 15 15 30.

De context onderscheidt de sterren: int *p declareert een pointer, de unaire expressie *p benadert een object en *p * 2 bevat ook een binaire vermenigvuldiging. Voor een geldig object x duidt *&x de variabele x aan; voor een pointer naar een geldig object geeft &*p de pointerwaarde terug.

Houd adressen en inhoud uit elkaar

Begin elk uitvoeringsspoor met twee rijen informatie: welk adres elke pointer bevat en welke waarde elk geheel getal bevat. Stel dat p naar a wijst, met waarde 3, en q naar b, met waarde 8.

Instructie vanuit die begintoestandEffect
*p = *q;Kopieer 8 naar a; pointers blijven naar hetzelfde wijzen
p = q;Laat p naar b wijzen; gehele getallen behouden hun waarden

Een geheugendiagram gebruikt voor elk object een vak. Zet de naam en het adres bij het vak, zet de huidige waarde erin en teken een pijl van een pointer naar het doelobject. Een adrestoewijzing verplaatst een pijl; een toewijzing aan het object waarnaar wordt gewezen verandert de inhoud van het doelvak.

Na p = q zijn de pointers aliassen van b. Als je daarna *p = 20; uitvoert, levert het lezen van *q of b de waarde 20 op. Een adres kopiëren maakt een extra route naar hetzelfde object, geen extra geheel getal.

Controleer of derefereren geldig is

Een pointer die voor toegang wordt gebruikt, moet een bestaand object van een geschikt type aanduiden en schrijven vereist bovendien een wijzigbaar object. Nulpointers duiden geen object aan. Niet-geïnitialiseerde pointers hebben geen bekend bruikbaar doelobject. Een pointer wordt ongeldig wanneer de levensduur van het doelobject eindigt.

Toegang via een ongeldige pointer is ongedefinieerd gedrag: C stelt geen eisen aan het resultaat. Het programma kan stoppen met een segmentatiefout, gegevens beschadigen of normaal lijken te werken. Een schijnbaar geslaagde uitvoering bewijst niet dat de toegang geldig was.

Test een pointer die terecht een nulpointer kan zijn voordat je die gebruikt voor toegang:

if (p != NULL) {
    *p = 0;
}

Door NULL toe te wijzen aan een pointer die je niet meer nodig hebt, kun je voorkomen dat je die variabele later per ongeluk gebruikt. Daarmee stel je andere aliassen niet opnieuw in. Houd de levensduur van het object afzonderlijk bij.

Zet haakjes om de bedoelde waarde

De postfixoperator ++ bindt sterker dan de unaire *. Daarom betekent *p++ hetzelfde als *(p++): de expressie gebruikt de oude pointer om een waarde te bereiken en schuift de pointer op. (*p)++ verhoogt de waarde waarnaar wordt gewezen en laat de pointer staan. *p + 1 leest de waarde en telt er 1 bij op zonder het resultaat op te slaan. Dit zijn drie verschillende handelingen.

Belangrijkste punten

  • *p duidt het object aan dat via p wordt bereikt.
  • Een adres kopiëren maakt aliassen; *p kopiëren kopieert een waarde.
  • Derefereren vereist een bestaand object en een geschikt type voor toegang.
  • Bij ongedefinieerd gedrag is geen resultaat gegarandeerd, ook geen crash.
  • *p++ schuift de pointer op; (*p)++ verhoogt de waarde van het doelobject.
2

Laat functies werken op gegevens van de aanroepende functie

Een functie ontvangt een kopie van elk argument, ook van een pointerargument. Met dat gekopieerde adres kan de functie nog steeds een object van de aanroepende functie bereiken, waardoor verwisselingen, invoerfuncties en meerdere uitvoerwaarden mogelijk zijn. De interface van de functie moet ook laten zien of ze de gegevens wijzigt. Volg het adres de aanroep in, onderscheid lokale wijzigingen van schrijfbewerkingen via het adres en controleer welke objecten nog bestaan wanneer een teruggegeven pointer wordt gebruikt.

2.1 Wijzig variabelen van de aanroepende functie via parameters

C geeft elk argument op waarde door. Een parameter is een lokale variabele die met het argument wordt geïnitialiseerd. Een adres doorgeven verandert die regel niet: de functie ontvangt een kopie van het adres en kan met die kopie het object van de aanroepende functie bereiken.

Vergelijk twee resetfuncties
void reset_copy(int n) {
    n = 0;
}

void reset(int *n) {
    *n = 0;
}

Voor int score = 12; wijzigt reset_copy(score) alleen de lokale n. score blijft 12. De aanroep reset(&score) kopieert het adres van score naar pointerparameter n; een toewijzing aan *n verandert vervolgens score in 0. Deze techniek heet vaak doorgifte via verwijzing, hoewel de taal de argumenten daadwerkelijk op waarde blijft doorgeven.

Het adres in de aanroep en de dereferentie in de functiebeschrijving hebben verschillende taken. &score vertelt de functie waar het object zich bevindt. *n = 0 vertelt wat de functie daar moet opslaan. De aanroep reset(score) levert een geheel getal waar een pointer nodig is en veroorzaakt een diagnostische compilermelding.

Doorloop een verwisseling
void swap(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}

/* Inside a calling function: */
int x = 3, y = 8;
swap(&x, &y);
  1. a wijst naar x en b wijst naar y.
  2. t bewaart de oorspronkelijke waarde van x, namelijk 3.
  3. *a = *b schrijft 8 naar x.
  4. *b = t schrijft de bewaarde 3 naar y.

Beide variabelen van de aanroepende functie veranderen. Een versie met gewone parameters int a, int b verwisselt alleen kopieën. Ook een pointerversie die int *t = a; a = b; b = t; uitvoert, verwisselt de gehele getallen niet: die laat alleen de lokale pointerparameters naar iets anders wijzen.

Lever meer dan één resultaat

Een return-instructie levert één waarde, maar pointerparameters kunnen extra uitvoerwaarden doorgeven:

void divide(int total, int size, int *q, int *r) {
    *q = total / size;
    *r = total % size;
}

/* size must be positive for these example inputs. */
int q, r;
divide(17, 5, &q, &r);

Geheeltallige deling geeft q de waarde 3 en r de waarde 2. De aanroepende functie levert twee afzonderlijke beschrijfbare objecten. Een andere nuttige interface geeft een successtatus terug en schrijft alleen bij succes een uitvoerwaarde; aanroepende functies controleren die status voordat ze de uitvoer gebruiken.

Herken hetzelfde patroon in bibliotheekaanroepen

scanf("%d", &age) heeft het adres van age nodig omdat de functie daar een ingelezen geheel getal opslaat. Als je age doorgeeft, lever je een waarde in plaats van de vereiste int *, wat ongedefinieerd gedrag oplevert. Controleer of scanf 1 teruggeeft voordat je de nieuw ingelezen waarde gebruikt.

Voor tekst geeft char name[20]; scanf("%19s", name); een pointer naar het eerste teken van de array door. De breedte staat maximaal 19 invoertekens toe, zodat er ruimte overblijft voor '\0'. De arrayexpressie levert de vereiste tekenpointer al.

Een arrayparameter ontvangt eveneens een adres, geen gekopieerd blok elementen. In void zero(int *a, size_t n) hebben toewijzingen aan a[i] effect op de array van de aanroepende functie. Bij het doorgeven van het adres wordt alleen een pointer gekopieerd, zodat ook functies die alleen lezen grote arrays kunnen bekijken zonder elk element te kopiëren.

Belangrijkste punten

  • C kopieert elk argument, ook adressen.
  • Een schrijfbewerking via een pointerparameter kan het object van de aanroepende functie wijzigen.
  • Een werkende verwisseling van gehele getallen verwisselt *a en *b met behulp van een bewaarde waarde.
  • Uitvoerparameters hebben geldige beschrijfbare objecten nodig die de aanroepende functie beschikbaar stelt.
  • scanf ontvangt adressen en meldt hoeveel conversies zijn geslaagd.
  • Arrayparameters delen de toegang tot elementen van de aanroepende functie.

2.2 Toegang met const en veilige retourwaarden

Een pointerinterface moet twee dingen duidelijk maken: of de functie via de pointer mag schrijven en hoelang de opslag waarnaar wordt gewezen geldig blijft. const beantwoordt de eerste vraag. De levensduur van het object beantwoordt de tweede.

Bepaal waar const de toegang beperkt
DeclaratieMag p naar iets anders wijzen?Mag je via *p een waarde toewijzen?
const int *p = &x;JaNee
int *const p = &x;NeeJa
const int *const p = &x;NeeNee

Een pointer naar een const-gekwalificeerd object beperkt deze toegang. Als x is gedefinieerd als de gewone int x = 5;, blijft x = 6; toegestaan, ook terwijl een const int * ernaar wijst. Als het object zelf als const is gedefinieerd, is een poging om het via welke pointer dan ook te wijzigen ongedefinieerd gedrag.

Een constante pointer behoudt één adres. De pointer moet worden geïnitialiseerd en het adres kan later niet worden vervangen. Het object op dat adres kan nog steeds veranderen als het type dat toestaat. Lees vanaf de naam naar buiten: int *const p maakt p constant; const int *p beschermt de int die via p wordt bereikt.

Gebruik een parameter voor alleen lezen zoals const char *s wanneer een functie een string alleen bekijkt. Je kunt een gewone char * zonder expliciete typeconversie aan die parameter doorgeven. De functie kan haar lokale s opschuiven, maar een toewijzing via *s wordt afgewezen. Terug converteren naar een beschrijfbaar type vereist meer dan een compilerwaarschuwing verwijderen: het onderliggende object moet daadwerkelijk wijzigbaar zijn.

Geef een positie in opslag van de aanroepende functie terug
int *find(int *a, size_t n, int target) {
    for (size_t i = 0; i < n; i++) {
        if (a[i] == target) return &a[i];
    }
    return NULL;
}

Voor int v[] = {4, 9, 2, 9}; geeft find(v, 4, 9) de waarde &v[1] terug. De functie eindigt, maar v behoort tot de aanroepende functie en blijft daar bestaan. Nadat die het resultaat met NULL heeft vergeleken, kan de aanroepende functie found - v berekenen om index 1 te krijgen of *found = 0 toewijzen om het element bij te werken. Een zoekopdracht naar 7 geeft NULL terug, wat je niet mag derefereren. strchr en strstr gebruiken dezelfde conventie: een nulpointer bij niet vinden.

Wijs adressen van lokale variabelen die niet meer bestaan af
int *bad(void) {
    int result = 42;
    return &result;
}

result heeft automatische opslagduur. De levensduur eindigt wanneer de aanroep terugkeert, waardoor de aanroepende functie een ongeldige pointer naar een niet meer bestaand object overhoudt. De compiler kan waarschuwen en elke latere toegang via die pointer is ongedefinieerd gedrag. Als de oude bytes bij één uitvoering zichtbaar zijn, blijft het object daardoor niet bestaan.

Hetzelfde probleem kan optreden bij de sluitende accolade van een binnenblok. Een pointer in een buitenliggend blok kan de levensduur van een variabele in een binnenblok waarvan die het adres heeft opgeslagen niet verlengen.

Kies opslag die lang genoeg bestaat

Geef een int-waarde terug wanneer de aanroepende functie alleen een geheel getal als resultaat nodig heeft. Laat de aanroepende functie voor meerdere resultaten opslag beschikbaar stellen om te vullen. Een teruggegeven pointer kan ook verwijzen naar de array van de aanroepende functie, een stringliteral die via const char * toegankelijk is, of een static-object.

Een lokale statische variabele wordt één keer geïnitialiseerd en bestaat gedurende het hele programma, maar alle aanroepen delen die variabele. Als een latere aanroep er een nieuwe waarde in opslaat, ziet elke eerder teruggegeven pointer die wijziging. Statische opslag lost het probleem met de levensduur op zonder elke aanroep een eigen onafhankelijk resultaatobject te geven.

Belangrijkste punten

  • const int * beperkt het schrijven via die pointer, terwijl int *const het adres vastlegt.
  • Een object dat als const is gedefinieerd, mag niet worden gewijzigd.
  • Pointers naar posities in de nog bestaande array van de aanroepende functie kunnen geldig blijven nadat een functie terugkeert.
  • Controleer een zoekresultaat dat een nulpointer kan zijn vóór toegang of aftrekking.
  • Het adres van een lokale variabele met automatische opslagduur teruggeven levert een ongeldige pointer naar een niet meer bestaand object op.
  • Een statisch resultaatobject wordt door alle aanroepen gedeeld.
3

Beweeg door arrays en strings

Pointerrekenen verbindt het adres van een element met zijn positie in een array. Het type waarnaar wordt gewezen bepaalt de stapgrootte, terwijl de array het toegestane bereik bepaalt. Gebruik dat model om indexexpressies te lezen, een arraysegment aan een functie door te geven en lussen te maken die stoppen vóór een ongeldige toegang. Strings hebben een tweede stopregel: hun afsluitende nulteken markeert het einde van de tekst, ook als de opslag meer bytes bevat.

3.1 Elementstappen en arraygrenzen

Als je een geheel getal bij een pointer optelt, verplaats je die met elementen van het type waarnaar wordt gewezen. Als p naar arrayelement i wijst, wijst p + n naar element i + n, mits de resulterende positie binnen de array of direct erna ligt.

Schaal het adres één keer

Vanaf een voorbeeldadres 0x1000 geeft 3 optellen deze resultaten:

PointertypeGrootte van één elementAdres na p + 3
int *4 bytes0x100C
double *8 bytes0x1018
char *1 byte0x1003

De compiler voert de schaling uit. Als je p + 3 * sizeof *p schrijft, schaal je twee keer en kom je meestal bij het verkeerde element uit. Gebruik unsigned char * wanneer de bedoelde stap echt één byte van de representatie van een object is.

Voor int a[6] = {10, 20, 30, 40, 50, 60}; en int *p = &a[1]; leest *(p + 2) het element a[3], dat 40 is. De toewijzing p += 3; verplaatst de pointer zelf naar a[4], dat 50 bevat. Vanaf daar leest *(p - 1) de waarde 40. p++, p--, p += n en p -= n gebruiken allemaal stappen in elementen.

Trek posities van elkaar af om elementen te tellen

&a[5] - &a[1] is 4 en als je de operanden omdraait, krijg je -4. Het resultaat telt al elementen; deel het niet door sizeof(int). Het type met teken is ptrdiff_t, gedeclareerd in <stddef.h>, en %td drukt het af.

Trek alleen pointers naar posities in dezelfde array van elkaar af, inclusief de positie direct voorbij het laatste element, met een resultaat dat in ptrdiff_t kan worden weergegeven. Deze bewerking beschrijft posities binnen die array. Adressen van niet-gerelateerde variabelen van elkaar aftrekken is ongedefinieerd gedrag, ook als de afgedrukte getallen toevallig dicht bij elkaar liggen.

Gebruik de positie direct voorbij het laatste element als grens

Voor zes elementen is a + 6 een geldige pointer direct voorbij het laatste element. Die kan als grens dienen en worden gebruikt bij geldige aftrekking binnen dezelfde array, maar kan niet worden gederefereerd. a + 7 en a - 1 zijn al ongedefinieerd gedrag wanneer ze worden gevormd; door ze later niet te derefereren herstel je dat niet.

for (int *p = a; p < a + 6; p++) {
    printf("%d\n", *p);
}

De lusinhoud wordt uitgevoerd voor de indices 0 tot en met 5. Na de laatste ophoging is p gelijk aan a + 6 en stopt de volgende test de lus. Met <= zou de lusinhoud toegang krijgen tot die positie die geen element is.

Voor de rekenregels gedraagt één object zich als een array van één element. Je mag &x + 1 vormen; je mag die pointer niet gebruiken om een vermeende volgende variabele te lezen.

Vergelijk alleen zinvolle posities

Volgordeoperatoren zoals < vergelijken posities binnen één array. Vergelijk pointers naar niet-gerelateerde objecten niet op volgorde. Gelijkheid en ongelijkheid kunnen compatibele pointertypen vergelijken en kunnen testen op een nulpointer.

Je kunt geen twee pointers optellen, pointers vermenigvuldigen of in standaard-C rekenen met een void * of een functiepointer. Om het midden van een bereik [p, q) binnen dezelfde array te vinden, gebruik je p + (q - p) / 2: trek posities van elkaar af om een aantal te krijgen, deel dat aantal en schuif dan op vanaf p.

Belangrijkste punten

  • Een geheel getal optellen verplaatst een pointer met elementen van het doeltype.
  • Een pointerverschil binnen dezelfde array telt al elementen en heeft het type ptrdiff_t.
  • Pointers direct voorbij het laatste element mogen een grens aangeven, maar kunnen niet worden gederefereerd.
  • Een pointer vormen vóór de array of verder dan direct voorbij het laatste element is ongeldig.
  • Gebruik posities binnen dezelfde array voor volgordevergelijkingen.
  • Trek posities van elkaar af voordat je een middenpunt berekent.

3.2 Arrayexpressies en pointerparameters

Een array is een blok elementen. Een pointer is een afzonderlijk object dat een adres bevat. Hun nauwe relatie komt door een automatische conversie die in de meeste expressies plaatsvindt, niet doordat de twee objecten van dezelfde soort zijn.

Converteer een arrayexpressie
int a[5] = {3, 1, 4, 1, 5};
int *p = a;

Hier wordt a geconverteerd naar een pointer naar a[0]. Door deze conversie van array naar pointer is de initialisator gelijkwaardig aan &a[0]. Zowel *p als a[0] leest de waarde 3.

C definieert indexering via pointerrekenen: a[i] betekent *(a + i). Daarom werkt p[3] ook en leest die expressie de waarde 1. &a[i] en a + i duiden hetzelfde element aan. Zelfs 3[a] heeft dezelfde gedefinieerde betekenis als a[3], hoewel de gebruikelijke schrijfwijze veel duidelijker is.

Weet wanneer de hele array een array blijft

De conversie vindt niet plaats voor operanden van sizeof of de unaire &. Ze vindt ook niet plaats voor typeof en typeof_unqual in C23, of wanneer een stringliteral een tekenarray initialiseert.

Expressie, met int-waarden van 4 bytes en een pointer van 8 bytesResultaat
sizeof a20 bytes voor alle vijf elementen
sizeof p8 bytes voor het pointerobject
sizeof a / sizeof a[0]5 elementen

Je kunt geen nieuw adres aan een array toewijzen of de naam ophogen: a = p; en a++; zijn ongeldig. Je kunt p naar iets anders laten wijzen, bijvoorbeeld met p = a + 2;. Kopieer de inhoud van een array met een lus over de elementen of een geschikte aanroep van memcpy.

Volg het omgezette parametertype
int sum(const int *values, size_t n) {
    int total = 0;
    for (size_t i = 0; i < n; i++) total += values[i];
    return total;
}

De aanroepende functie levert zowel een adres als een aantal. Voor de array hierboven bezoekt sum(a + 2, 3) de oorspronkelijke indices 2, 3 en 4 en telt 4, 1 en 5 op om 10 terug te geven. Binnen de functie is index 0 relatief ten opzichte van de opgegeven beginpointer.

In een parameterlijst van een functie declareren int a[], int a[10] en int *a allemaal een pointerparameter. De gewone schrijfwijze [10] geeft geen gecontroleerde grens en geeft geen lengte door. Binnen zo’n functie meet sizeof a een pointer. De expressie sizeof a / sizeof a[0] zou 2 opleveren op een systeem met pointers van 8 bytes en int-waarden van 4 bytes, ongeacht de werkelijke arraylengte van de aanroepende functie.

Onderscheid een elementpointer van een arraypointer

a wordt geconverteerd naar int *, terwijl &a het type int (*)[5] heeft, een pointer naar de hele array van vijf elementen. Beide duiden het begin van dezelfde opslag aan, maar berekeningen volgen hun verschillende typen. a + 1 schuift één int op, oftewel 4 bytes in deze indeling. &a + 1 schuift één hele array op, oftewel 20 bytes, naar de positie direct voorbij de array.

Een afgedrukt beginadres kan dat typeverschil niet laten zien. Lees de declaratie en houd bij het volgen van een expressie het type waarnaar wordt gewezen naast de pointer.

Belangrijkste punten

  • De meeste arrayexpressies worden geconverteerd naar een pointer naar het eerste element.
  • a[i] is gedefinieerd als *(a + i).
  • sizeof a meet alleen de hele array waar a echt een array is.
  • Een arrayparameter wordt omgezet naar een pointer; geef de bruikbare lengte afzonderlijk door.
  • &a wijst naar een hele array, dus pointerrekenen ermee heeft een grotere stap.

3.3 Pointerlussen over getallen en tekst

Een pointerlus heeft een huidige positie en een stopregel nodig. Een array met een bekend aantal elementen gebruikt een eindpointer; een string gebruikt het afsluitende '\0'. Kies de vorm die het duidelijkst laat zien welke elementen worden bezocht.

Doorloop een bereik met een bekend aantal elementen
int a[] = {3, 1, 4, 1, 5};
int total = 0;
for (const int *p = a; p < a + 5; p++) {
    total += *p;
}

De tussentotalen zijn 3, 4, 8, 9 en 14. Een lus met een index die a[i] voor indices kleiner dan 5 optelt, geeft hetzelfde resultaat. Moderne optimaliserende compilers genereren voor deze vormen vaak vergelijkbare code; pointersyntaxis op zichzelf belooft geen snelheidsvoordeel.

Lees vóór of na het verplaatsen

Als p naar de 3 in {3, 1, 4} wijst, zet int v = *p++; de waarde van v op 3 en laat die p naar de 1 wijzen. Postfixincrementatie levert de oude pointerwaarde aan * en werkt de pointer bij. *++p gaat eerst naar het volgende element en leest dat dan. (*p)++ wijzigt het huidige element en verplaatst de pointer niet.

Stop bij een afsluitend nulteken
size_t my_strlen(const char *s) {
    const char *p = s;
    while (*p != '\0') p++;
    return (size_t)(p - s);
}

Voor "pointer" stopt p op het afsluitende nulteken, zeven posities vanaf het begin. Het resultaat telt dat nulteken niet mee. Tekens tellen gebeurt met dezelfde doorloop: als je "mississippi" doorloopt en een teller verhoogt wanneer *p == 's', tel je vier overeenkomsten onder elf letters.

Een pointer naar het midden van een string geeft toegang tot het achterste deel ervan. Met const char *msg = "hello world"; drukt puts(msg + 6) "world" af. De eerdere tekens blijven staan waar ze stonden.

Kopieer ook het afsluitende nulteken
char destination[4];
const char *src = "cat";
char *dst = destination;
while ((*dst++ = *src++) != '\0') {
}

Elke iteratie wijst één teken toe, test de toegewezen waarde en schuift beide pointers op. De laatste iteratie kopieert '\0', zodat de bestemming een complete string is. Beide pointers staan dan direct voorbij hun afsluitende nulteken. De opslag vereist de tekstlengte plus één byte; als die extra byte ontbreekt, ontstaat een bufferoverloop. Dit kopieerpatroon vereist ook dat de bron- en bestemmingsbereiken niet overlappen.

Keer om vanaf beide uiteinden

Bepaal eerst de lengte van een beschrijfbare string. Als de lengte nul is, stop je voordat je een pointer naar het laatste teken vormt. Zet anders left = s en right = s + length - 1. Zolang left < right, verwissel je de tekens, verhoog je left en verlaag je right. Voor "stressed" leveren vier verwisselingen "desserts" op; het afsluitende nulteken blijft op het oorspronkelijke einde staan.

De controle op een lege string is belangrijk omdat je door 1 van de beginpointer af te trekken een pointer vóór de array zou vormen.

Kies beschrijfbare tekenopslag

char word[] = "hello"; maakt een wijzigbare array van zes bytes, inclusief het afsluitende nulteken. const char *word = "hello"; wijst naar een stringliteral met statische opslagduur. Een stringliteral wijzigen is ongedefinieerd gedrag, ook als een declaratie char * zonder const gebruikt. Gebruik const char * zodat de compiler onbedoelde schrijfbewerkingen als fout meldt.

Een arraylus moet vóór de positie direct voorbij het laatste element stoppen en een stringdoorloop moet daadwerkelijk een afsluitend nulteken binnen toegankelijke opslag tegenkomen. Een pointer bevat geen lengte en biedt geen garantie dat de bytes een string vormen.

Belangrijkste punten

  • Een bereik met een bekend aantal elementen eindigt bij een pointer direct voorbij het laatste element; een string eindigt bij het nulteken.
  • *p++ gebruikt de oude positie en schuift de pointer op.
  • Een string kopiëren omvat het afsluitende nulteken en vereist de lengte plus één byte.
  • Controleer op een lege string voordat je een pointer naar het laatste teken vormt.
  • Tekenarrays die vanuit tekst zijn geïnitialiseerd zijn beschrijfbaar; stringliterals zijn dat niet.
  • Een pointer bevat op zichzelf geen lengte of garantie op afsluiting.
4

Volg en wijzig een andere pointer

Een pointer kan zelf het object zijn dat via een adres wordt bereikt. Door twee niveaus te volgen kan een functie de pointer van de aanroepende functie vervangen en kan een programma een array met stringadressen doorlopen. Houd het type op elk niveau zichtbaar: een pointer naar een geheel getal, een pointer naar die pointer en een pointer naar een hele rij vertegenwoordigen verschillende indelingen. Volg welk object elke dereferentie bereikt voordat je bepaalt wat een toewijzing zal wijzigen.

4.1 Twee indirectieniveaus

Een pointer is een opgeslagen object en heeft dus een adres dat een andere pointer kan bevatten. Een pointer naar een pointer voegt nog een stap toe tussen de expressie en de uiteindelijke gegevens.

Volg de pijlen één voor één
int x = 7;
int *p = &x;
int **pp = &p;

Gebruik voorbeeldadressen: x op 0x2000 bevat 7; p op 0x3000 bevat 0x2000; pp op 0x4000 bevat 0x3000. De evaluatie van *pp bereikt p, waarvan de waarde het type int * heeft. De evaluatie van **pp volgt die pointer verder naar x, een int.

ExpressieTypeBereikt object of bereikte waarde
ppint **Adres van p
*ppint *De pointer opgeslagen in p
**ppintHet gehele getal opgeslagen in x

&p heeft één pointerniveau meer dan p; elke dereferentie verwijdert één niveau. int **pp initialiseren met &x past niet, omdat &x het type int * heeft.

Kies het niveau van de toewijzing

Als er nog een bestaand geheel getal y is, verandert **pp = 9; de waarde van x. De toewijzing *pp = &y; wijzigt de pointer p, zodat volgend gebruik van **pp de variabele y bereikt. De huidige waarde van x blijft ongewijzigd. Als q een andere int * is, wijzigt pp = &q; alleen de buitenste pointer, zodat de eerste pijl naar q wijst in plaats van naar p.

Tel niet alleen sterren, maar controleer ook het object op elk niveau. Voordat je **pp evalueert, moeten zowel de route naar het pointerobject als de route van dat object naar het gehele getal geldig zijn.

Verplaats een stringpointer van de aanroepende functie
void skip_spaces(const char **s) {
    while (**s == ' ') {
        (*s)++;
    }
}

/* In the caller: */
const char *text = "   hi";
skip_spaces(&text);
puts(text);

De aanroepende functie geeft het adres van haar pointervariabele door. *s duidt daardoor text aan; **s leest het huidige teken. Elke uitvoering van (*s)++ schuift text één teken op. Na drie iteraties wijst de pointer naar 'h' en drukt puts "hi" af. De functie vereist een geldige pointer naar een pointer die naar een positie in een string met een afsluitend nulteken wijst.

Een versie met const char *s die s ophoogt, wijzigt alleen een lokale kopie. Ook de haakjes in (*s)++ zijn noodzakelijk: *s++ verhoogt de buitenste lokale pointer in plaats van de tekenpointer van de aanroepende functie. In const char **s zijn de tekens via deze toegang alleen leesbaar, maar mag de pointer van de aanroepende functie worden verplaatst.

Lees een uitvoerpointer van een bibliotheekfunctie

strtol uit <stdlib.h> gebruikt een uitvoerparameter van het type char **:

char *end;
long number = strtol("42abc", &end, 10);

De functie geeft 42 terug en slaat in end een pointer op naar het eerste ongebruikte teken; die wijst nu naar "abc". Als de eindpointer gelijk is aan de beginpointer, zijn geen cijfers geconverteerd. Een volledig gecontroleerde omzetting van tekst naar een getal handelt ook bereikfouten af; de taak van de pointer is hier de stoppositie melden.

Belangrijkste punten

  • int **pp kan het adres van een int *-object bevatten.
  • Elke dereferentie volgt één pointerniveau.
  • **pp wijzigt het uiteindelijke gehele getal; *pp kan de tussenliggende pointer wijzigen.
  • Om een pointer van de aanroepende functie bij te werken, is het adres van die pointer nodig.
  • strtol gebruikt char ** om de positie van het eerste ongebruikte teken terug te geven.

4.2 Arrays van stringpointers en rechthoekige arrays

Een array kan pointers als elementen opslaan. Een tweedimensionale array slaat daarentegen volledige rijen als elementen op. Beide ondersteunen twee indices, maar hun opslag en de pointertypen waarnaar ze worden geconverteerd verschillen.

Sla adressen van afzonderlijke strings op
const char *days[] = {"Mon", "Tue", "Wed"};

days bevat drie tekenpointers. Elke pointer wijst naar een afzonderlijke stringliteral. days[1] is de pointer naar "Tue" en days[1][0] leest 'T'. Bij objectpointers van 8 bytes is sizeof days gelijk aan 24, ongeacht de lengte van de strings. De strings zelf nemen elders opslagruimte in.

De lengten kunnen verschillen, waardoor een verzameling met ongelijke rijlengten ontstaat. Als je de lijst opnieuw ordent, kun je pointerelementen verwisselen zonder de tekst te kopiëren. De toewijzing days[0] = "Thursday"; is toegestaan: het pointerelement kan veranderen, ook al zijn de tekens die je ermee bereikt alleen leesbaar.

In de meeste expressies wordt days geconverteerd naar een pointer naar het eerste element. Omdat een element een const char * is, is het geconverteerde type const char **:

for (const char **d = days; d < days + 3; d++) {
    puts(*d);
}

De lus schuift per iteratie één pointerelement op. *d haalt de tekenpointer op die puts nodig heeft.

Lees opdrachtregelstrings

int main(int argc, char *argv[]) ontvangt hetzelfde parametertype als char **argv. argc is niet-negatief en telt de beschikbare strings. Als de waarde positief is, stelt argv[0] de programmanaam voor, of een lege string als die naam niet beschikbaar is. Gebruikersargumenten staan op de indices 1 tot en met argc - 1 en argv[argc] is een nulpointer.

Bij een typische aanroep ./greet Ada 42 is argc gelijk aan 3, wijst argv[1] naar "Ada" en wijst argv[2] naar "42". Het laatste argument is tekst totdat een conversie zoals strtol het leest. argv[1][0] is 'A'. Controleer argc > 2 voordat je argv[2] gebruikt.

Een lus die gebruikersargumenten afdrukt, kan for (int i = 1; i < argc; i++) puts(argv[i]); gebruiken. Om in plaats daarvan vanaf argv + 1 tot een nulpointer te lopen, controleer je eerst argc > 0; als argc nul is, is er geen bereik met gebruikersargumenten dat je zo kunt beginnen. Argumentstrings zijn wijzigbaar, anders dan stringliterals.

Volg volledige rijen

int grid[3][4] bevat twaalf gehele getallen in rijgewijze opslagvolgorde: eerst heel rij 0, dan rij 1 en dan rij 2. Bij int-waarden van 4 bytes neemt elke rij 16 bytes in. grid[2][1] ligt 36 bytes na grid[0][0]: twee volledige rijen dragen 32 bytes bij en de kolom 4.

Declaratie of expressieBetekenis
int (*rows)[4]Pointer naar een rij van vier gehele getallen
int *ptrs[4]Array van vier pointers naar gehele getallen
Geconverteerde gridint (*)[4]

grid + 1 schuift één rij op. Een functie die de array ontvangt, kan int g[][4] of int (*g)[4] gebruiken, met een afzonderlijk aantal rijen. Ze kan geen int ** gebruiken: de tabel bevat geen opgeslagen rijpointers om te derefereren. Een expliciete typeconversie kan die ontbrekende pointers niet maken.

Op dezelfde manier bevat char names[3][6] achttien tekenposities in rijen van vaste grootte. const char *names[3] bevat drie pointerposities die naar strings van verschillende lengten kunnen verwijzen.

Belangrijkste punten

  • Een array van stringpointers slaat adressen op, terwijl de tekst elders staat.
  • Een array van pointers wordt geconverteerd naar een pointer naar het eerste pointerelement.
  • argv[argc] is een nulpointer; controleer het aantal argumenten voordat je een bepaalde string leest.
  • Een tweedimensionale array slaat aaneengesloten rijen op en bevat geen rijpointerobjecten.
  • int (*)[4] en int ** beschrijven verschillende opslagindelingen.
  • Twee indices op zichzelf bewijzen niet dat twee objecten hetzelfde type hebben.
5

Kies functies en geef callbackfuncties door

Met een functiepointer kan een programma tijdens de uitvoering een bewerking kiezen. Het type legt vast welke aanroep is toegestaan en met een callbackparameter kan een andere functie die bewerking aanroepen wanneer dat nodig is. Gebruik deze ideeën eerst in een kleine tabel met rekenfuncties en daarna in qsort, waar de callbackfunctie elementadressen correct moet interpreteren en een consistente volgorde moet teruggeven. Bij het sorteren van stringpointers komen functiepointers en twee niveaus van objectpointers samen.

5.1 Declaraties en aanroepen met functiepointers

Een functiepointer slaat een verwijzing op naar een functie die later kan worden aangeroepen. Het type omvat het retourtype en de parametertypen. Die typen moeten passen bij de functie die via de pointer wordt gebruikt.

Zet haakjes om de pointernaam
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

int (*op)(int, int) = add;

Lees vanaf op: het is een pointer, datgene waarnaar wordt gewezen is een functie die twee int-waarden ontvangt, en die functie geeft een int terug. Zonder de haakjes declareert int *op(int, int); een functie die een pointer naar een geheel getal teruggeeft. De haakjes veranderen de betekenis van de declaratie.

Een functienaam wordt in deze initialisator geconverteerd naar een functiepointer. op = &add; is gelijkwaardig aan op = add;. Zowel op(6, 3) als (*op)(6, 3) roept add aan en geeft 9 terug. Door op = sub; toe te wijzen geeft dezelfde aanroep 3 terug. Aan de aanroepexpressie is niets veranderd, behalve de functie die in de pointer is opgeslagen.

Houd de signatuur compatibel

Een pointer van het type int (*)(int, int) kan niet veilig een functie aanroepen die double ontvangt en teruggeeft. Als je er bijvoorbeeld sqrt aan toewijst, krijg je een melding over incompatibele typen. Een functiepointer expliciet converteren om een melding te onderdrukken maakt een niet-passende aanroep niet geldig; het gedrag is ongedefinieerd.

Standaard-C behandelt objectpointers en functiepointers afzonderlijk. void * is het generieke objectpointertype en geen overdraagbare opslag voor een functiepointer. Functiepointers ondersteunen tests op gelijkheid en ongelijkheid, maar geen pointerrekenen.

Een nulfunctiepointer betekent dat er geen aanroepbare functie is. Als een interface die toestand toestaat, controleer je erop:

if (op != NULL) {
    int result = op(6, 3);
    printf("%d\n", result);
}

Een aanroep via een nulfunctiepointer is ongedefinieerd gedrag.

Geef het type één keer een naam
typedef int (*binop)(int, int);
binop ops[] = {add, sub, mul};

De typedef geeft een pointertype een naam. binop selected = mul; declareert één pointer; binop pick(char symbol); declareert een functie die er één teruggeeft. Zonder de typedef zou die retourdeclaratie int (*pick(char symbol))(int, int); zijn.

Een array van functiepointers is een functiepointertabel. ops[2](6, 3) kiest mul en geeft 18 terug. Controleer voor een selectie-index met teken i de voorwaarde i >= 0 && i < 3 voordat je ops[i] evalueert. Indexeren buiten de tabel is ongeldig, ook als elke opgeslagen functie verder aanroepbaar is.

Lees grotere declaraties van binnen naar buiten

Begin bij de naam en werk haakjes en postfixdelen af voordat je verder naar buiten gaat. In int (*table[3])(int, int) maakt table[3] een array van drie elementen, maakt * van elk element een pointer en maakt (int, int) het doel tot een functie die int teruggeeft.

In char *(*f)(const char *) is f een pointer naar een functie die een const char * ontvangt; die functie geeft een char * terug. Houd de eisen aan de levensduur van de teruggegeven pointer gescheiden van de eisen voor een compatibele aanroep via de functiepointer.

Belangrijkste punten

  • Haakjes onderscheiden een functiepointer van een functie die een pointer teruggeeft.
  • Een functiepointer legt het retourtype en de parametertypen van geldige aanroepen vast.
  • Een functienaam kan een compatibele functiepointer initialiseren zonder &.
  • Een typedef kan functiepointertabellen en retourtypen leesbaarder maken.
  • Valideer de tabelindex en controleer vóór de aanroep of een optionele pointer een nulpointer is.

5.2 Callbackfuncties en correcte vergelijkingen voor qsort

Een callbackfunctie is een functie die aan een andere functie wordt doorgegeven en die deze andere functie als onderdeel van haar werk aanroept. Zo blijft de algemene doorloop- of sorteerbewerking gescheiden van de specifieke handeling of volgorde die je kiest.

Geef een bewerking door zonder die aan te roepen
void twice(int *x) { *x *= 2; }

void for_each(int *a, size_t n, void (*fn)(int *)) {
    for (size_t i = 0; i < n; i++) fn(&a[i]);
}

Voor int v[] = {1, 2, 3}; levert for_each(v, 3, twice); de array {2, 4, 6} op. De functienaam levert een pointer. Als je twice() schrijft, probeer je de functie direct aan te roepen zonder het vereiste argument, in plaats van een bewerking voor latere aanroepen door te geven.

Haal een type terug uit een generieke objectpointer

Een void * kan een objectpointer bevatten en terug converteren naar het oorspronkelijke type. Je kunt die niet derefereren of gebruiken voor pointerrekenen in standaard-C, omdat de pointer geen elementtype geeft. Behoud kwalificaties: gegevens die alleen leesbaar zijn, gaan via const void * en daarna via een geschikte pointer naar een const-gekwalificeerd object.

qsort uit <stdlib.h> ontvangt een basisadres, een aantal elementen, een elementgrootte in bytes en een vergelijkingsfunctie als callback. De callbackfunctie heeft het type int (*)(const void *, const void *). Elk argument wijst naar een arrayelement en het teken van de retourwaarde geeft aan waar het eerste element hoort:

Teken van de retourwaardeVolgorde
NegatiefEerste vóór tweede
NulGelijkwaardig voor deze volgorde
PositiefEerste na tweede
Sorteer gehele getallen zonder overloop
int cmp_int(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);
}

/* In the caller: */
int v[] = {42, -7, 19, 0, 3};
qsort(v, 5, sizeof v[0], cmp_int);

De array wordt {-7, 0, 3, 19, 42}. Vergelijkingen leveren 0 of 1 op, dus de retour-expressie levert alleen -1, 0 of 1 op. Als je de volgorde-expressie omkeert naar (y > x) - (y < x), wordt aflopend gesorteerd.

Gebruik return x - y; niet voor willekeurige gehele getallen met teken. Voor een 32-bits int overschrijdt het aftrekken van -2000000000 van 2000000000 de waarde van INT_MAX. Overloop bij gehele getallen met teken is ongedefinieerd gedrag, dus een teruggelopen resultaat of betrouwbaar vergelijkingsteken is niet gegarandeerd.

Sorteer pointers naar strings
int cmp_str(const void *a, const void *b) {
    return strcmp(*(const char *const *)a,
                  *(const char *const *)b);
}

const char *names[] = {"Lin", "Ada", "Grace"};
qsort(names, 3, sizeof names[0], cmp_str);

Neem <string.h> op voor strcmp. Een arrayelement is hier zelf een pointer. De callbackfunctie ontvangt een adres van dat pointerelement, dus door één keer te derefereren haal je het stringadres op. De toegevoegde binnenste const voorkomt dat je het element via deze toegang wijzigt. Na het sorteren is de volgorde "Ada", "Grace", "Lin".

strcmp(a, b) zou de bytes van de pointerposities als tekst lezen. Gebruik ook sizeof names[0], de grootte van een pointerelement, niet sizeof(char) of de lengte van een naam.

Vergelijkingsfuncties moeten elementen ongewijzigd laten en consistent antwoorden, ook wat betreft transitieve ordening. qsort belooft niet de volgorde te behouden van elementen die als gelijk worden vergeleken. bsearch accepteert dezelfde vorm van vergelijkingsfunctie om een array te doorzoeken die al volgens die volgorde is gesorteerd.

Belangrijkste punten

  • Geef een functienaam door als callbackfunctie; haakjes roepen een functie direct aan.
  • Haal vóór toegang het elementtype terug uit elk generiek vergelijkingsargument.
  • qsort heeft een aantal, de grootte van één element en een compatibele vergelijkingsfunctie nodig.
  • Gebruik vergelijkingstekens zonder een aftrekking die overloop kan veroorzaken.
  • Vergelijkingsfuncties voor stringpointers ontvangen adressen van pointerposities.
  • Gelijke vergelijkingsresultaten garanderen geen stabiele volgorde na qsort.

Pointers en geheugenadressen in C: voorbeelden

5 Walkthroughs

1

Volg een adres en de waarde die je ermee bereikt

13 stappen
Maak een beeld van het geheugen, laat pointers naar iets anders wijzen en voorspel welk object elke toewijzing wijzigt.
  1. 1
    IntroductieHoud drie dingen uit elkaar

    Een opgeslagen geheel getal heeft een waarde, een locatie en een levensduur. Een pointer voegt een ander object toe waarvan de waarde een adres is. Volg een klein programma door beide soorten waarden te noteren: gehele getallen zoals 10 en adressen zoals 0x1000.

    De nuttige vraag bij elke regel is concreet: verandert deze instructie een adres, verandert ze het object dat via dat adres wordt bereikt of leest ze alleen een waarde?

  2. 2
    UitlegPlaats vier gehele getallen

    Stel je voor dat int scores[4] begint op 0x1000 op een systeem met gehele getallen van 4 bytes. Het eerste element gebruikt de bytes 0x1000 tot en met 0x1003. Het volgende begint op 0x1004, gevolgd door 0x1008 en 0x100C.

    De elementen hebben aaneengesloten opslag. Deze indelingsregel geldt voor de array; hij garandeert geen volgorde voor twee afzonderlijk gedeclareerde gehele getallen.

  3. 3
    VoorbeeldMeet de array en daarna de inhoud

    Voor char word[20] = "hi"; geven twee metingen verschillende resultaten:

    • sizeof word is 20 bytes arrayopslag.
    • strlen(word) is 2 tekens vóór '\0'.

    Het afsluitende nulteken neemt nog een arrayelement in en de resterende elementen behoren nog steeds tot de array. sizeof geeft een waarde van het type size_t terug, afgedrukt met %zu; de operator telt niet hoeveel elementen het programma momenteel gebruikt.

  4. 4
    VerbandHoud indeling en levensduur uit elkaar

    Uitlijning bepaalt geschikte beginadressen, terwijl bytevolgorde de volgorde van bytes binnen een waarde bepaalt. Geen van beide vertelt je hoelang een object bestaat.

    Een gewone lokale variabele bestaat zolang het bijbehorende blok wordt uitgevoerd. Een lokale statische of globale variabele bestaat gedurende het hele programma. Een adres opslaan legt een route naar opslag vast; het kan een lokale variabele met automatische opslagduur niet in leven houden nadat het blok eindigt.

  5. 5
    UitdagingBepaal het adres van een element

    Een array van zes gehele getallen begint op 0x1800, met 4 bytes per element. Waar begint element 3 en wat is de grootte van de hele array?

    ---reveal---

    Element 3 begint twaalf bytes na het begin, op 0x180C. Zes elementen van 4 bytes nemen 24 bytes in. De index geeft een afstand in elementen; door die te vermenigvuldigen met de elementgrootte krijg je de afstand in bytes.

  6. 6
    UitlegGeef de pointer een doelobject

    Bekijk int count = 42; int *p = &count;. De operator & vraagt het adres van count op en p slaat dat adres op. Als count op het voorbeeldadres 0x2000 staat, is de waarde van de pointer 0x2000; de waarde van het gehele getal blijft 42.

    De pointer neemt ook eigen opslagruimte in. Het adres ervan is &p, wat andere informatie is dan het adres dat p bevat.

  7. 7
    TipLees de ster naast elke naam

    int *p, q; declareert één pointer en één gewoon geheel getal. Gebruik int *p, *q; om twee pointers te declareren, of zet de declaraties op afzonderlijke regels.

    Initialiseer een pointer met een geschikt objectadres of NULL. Een nulpointer is een bekende lege toestand; een niet-geïnitialiseerde lokale pointer heeft een onbepaalde waarde en kan niet veilig worden getest alsof die al een nulpointer is.

  8. 8
    UitdagingLaat een pointer naar iets anders wijzen zonder gegevens te wijzigen

    Begin met int a = 9, b = 16; int *p = &a;. Voer daarna p = &b; uit. Welke waarden van gehele getallen veranderen en wat bevat p nu?

    ---reveal---

    Geen van beide gehele getallen verandert: a blijft 9 en b blijft 16. Alleen p verandert en slaat het adres van b op. Een pointertoewijzing vervangt een adres; ze kopieert niet de inhoud van het oude of het nieuwe doelobject.

  9. 9
    VoorbeeldLees, tel op en schrijf via p

    Ga verder met int x = 10; int *p = &x; en voer uit:

    *p = *p + 5;
    

    De *p aan de rechterkant leest 10 uit x. Als je er 5 bij optelt, krijg je 15. De *p aan de linkerkant duidt hetzelfde object aan, dus de toewijzing schrijft 15 naar x. Het adres in p is niet veranderd. Vervolgens leest int y = *p * 2; de waarde 15 en maakt een afzonderlijk geheel getal y met waarde 30.

  10. 10
    KernpuntEen kopie van een adres maakt een andere route

    Voor int n = 6; int *p = &n; int *q = p; bereiken beide pointers de variabele n. De toewijzing heeft een adres gekopieerd, niet het object met het gehele getal.

    Na *q = 14; geeft het lezen van n, *p of *q de waarde 14. Teken één vak voor het gehele getal en twee pijlen ernaartoe. Twee vakken voor gehele getallen tekenen zou ten onrechte onafhankelijke gegevens suggereren.

  11. 11
    TipControleer het doelobject vóór toegang

    Toegang vereist een pointer naar een bestaand object van een geschikt type. Een nulpointer duidt geen object aan en een ongeldige pointer naar een niet meer bestaand object heeft zijn doelobject verloren. Een test op een nulpointer behandelt de mogelijke lege toestand van een geïnitialiseerde pointer; de test stelt niet vast dat een doelobject van een pointer die geen nulpointer is nog bestaat.

    Ongeldige toegang heeft ongedefinieerd gedrag. C belooft geen specifieke waarde en ook geen crash, zelfs niet wanneer één uitvoering normaal lijkt.

  12. 12
    UitdagingVolg een gekopieerd adres

    Wat zijn de eindwaarden van a en b?

    int a = 6, b = 19;
    int *p = &a, *q = p;
    p = &b;
    *q = *p + 1;
    

    ---reveal---

    a wordt 20 en b blijft 19. Als je p naar iets anders laat wijzen, blijft q naar a wijzen. De laatste regel leest b via p en schrijft de som via q naar a.

  13. 13
    SamenvattingVolg het object dat de linkerkant aanduidt

    Lees de linkerkant voordat je een wijziging voorspelt. Een toewijzing aan p wijzigt een adres; een toewijzing aan *p wijzigt het doelobject. Houd gekopieerde pointers als afzonderlijke pijlen bij, controleer de levensduur van het doelobject en gebruik het type waarnaar wordt gewezen om elke toegang te interpreteren.

2

Laat een functie gegevens van de aanroepende functie wijzigen

11 stappen
Volg een verwisseling, gebruik uitvoerparameters en bepaal of teruggegeven pointers geldig blijven.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieVolg het adres een aanroep in
  2. 2
    UitlegEen gekopieerd adres bereikt nog steeds de aanroepende functie
  3. 3
    VoorbeeldBewaar de waarde voordat je die vervangt
  4. 4
    VoorbeeldSchrijf de tweede waarde naar de eerste positie
  5. 5
    VoorbeeldVoltooi de verwisseling
  6. 6
    UitdagingLever twee uitvoerwaarden
  7. 7
    UitlegZet const op het bedoelde niveau
  8. 8
    VoorbeeldGeef een geldige positie terug
  9. 9
    UitdagingVerklaar het probleem met de levensduur
  10. 10
    UitdagingKies een zoekinterface met schrijftoegang
  11. 11
    SamenvattingControleer de schrijfbewerking en de levensduur
3

Doorloop een array zonder de grens te overschrijden

15 stappen
Verbind pointerrekenen, arrayconversie en stringlussen door elke bezochte positie te volgen.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieBepaal begin, stap en stop
  2. 2
    UitlegVerplaats met elementen tegelijk
  3. 3
    VoorbeeldTrek twee posities van elkaar af
  4. 4
    KernpuntGebruik een grens die geen element is
  5. 5
    UitdagingBepaal het midden
  6. 6
    UitlegEen arrayexpressie levert een beginpointer
  7. 7
    VoorbeeldGebruik sizeof op de juiste plek
  8. 8
    UitdagingGeef een kleiner bereik door
  9. 9
    VoorbeeldLees en schuif dan op, of schuif op en lees dan
  10. 10
    UitlegZoek het einde van een string
  11. 11
    VoorbeeldKopieer één teken tegelijk
  12. 12
    TipReserveer de positie voor het afsluitende nulteken
  13. 13
    UitdagingHerstel het laatste lusbezoek
  14. 14
    UitdagingKies een veilige voorbereiding voor het omkeren
  15. 15
    SamenvattingMaak elke bezochte positie zichtbaar
4

Wijzig een pointer en lees een array van pointers

12 stappen
Volg twee indirectieniveaus en onderscheid daarna posities met stringpointers van volledige arrayrijen.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieMaak de pointer zelf tot doelobject
  2. 2
    VoorbeeldTeken twee pijlen
  3. 3
    UitlegKies welk niveau verandert
  4. 4
    VoorbeeldGeef de positie van de aanroepende functie via een adres door
  5. 5
    UitdagingVoorspel een wijziging van alleen de pointer
  6. 6
    VerbandLees de stoppositie van een conversie
  7. 7
    UitlegEen array kan pointerposities bevatten
  8. 8
    VoorbeeldDoorloop adressen
  9. 9
    TipControleer het aantal argumenten vóór toegang tot strings
  10. 10
    UitdagingLees een type voor een hele rij
  11. 11
    UitdagingStem een functie af op de aangeboden opslag
  12. 12
    SamenvattingBenoem het object op elk niveau
5

Kies een bewerking en sorteer via een callbackfunctie

15 stappen
Lees functiepointertypen, kies een bewerking uit een tabel en maak vergelijkingsfuncties voor gehele getallen en stringpointers.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieHoud een algemene taak en de gekozen bewerking uit elkaar
  2. 2
    UitlegLees het aanroepbare type
  3. 3
    VoorbeeldSelecteer een andere functie
  4. 4
    TipGeef een pointertype voor een tabel een naam
  5. 5
    UitdagingLees een verwante declaratie
  6. 6
    UitlegGeef een callbackfunctie voor een handeling door
  7. 7
    VoorbeeldGeef de vier sorteerargumenten door
  8. 8
    UitlegHaal het elementtype voor gehele getallen terug
  9. 9
    VoorbeeldGeef het teken van de volgorde terug
  10. 10
    UitdagingVermijd een verkorte aanpak die overloop veroorzaakt
  11. 11
    VerbandSorteer stringpointers, geen tekenrijen
  12. 12
    UitdagingKies de juiste grootte voor het sorteren
  13. 13
    KernpuntGelijkheid belooft geen stabiliteit
  14. 14
    UitdagingVolg een geselecteerde vergelijkingsfunctie als callback
  15. 15
    SamenvattingStem aanroep, indeling en volgorde op elkaar af

Pointers en geheugenadressen in C: oefenvragen

40 Oefenvragen in 4 sets

Set 1: Volg adressen en functieaanroepen10 Oefenvragen

Vraag 1 van 10

Een int neemt 4 bytes in. Een array int levels[7] begint op het voorbeeldadres 0x2400. Op welk adres begint levels[4]?
  1. 0x2404
  2. 0x2410
  3. 0x2414
  4. 0x241C
Toon antwoordVerberg antwoord
Antwoord: B
Index 4 ligt vier elementen na het begin. Elk element neemt 4 bytes in, dus de afstand is 16 bytes, geschreven als 0x10, en het adres is 0x2410. Als je slechts 4 bij het adres optelt, behandel je de index als een aantal bytes en bereik je het tweede element.

Vraag 2 van 10

Gegeven int items = 16;, welke declaratie maakt een pointer naar items?
  1. int *p = items;
  2. int p = &items;
  3. int *p = *items;
  4. int *p = &items;
Toon antwoordVerberg antwoord
Antwoord: D
&items vraagt het adres van het gehele getal op en int *p declareert het bijpassende pointertype. Als je de pointer met items initialiseert, geef je het opgeslagen gehele getal 16 in plaats van een adres van een object met een geheel getal. Een adres opvragen laat de waarde van items ongewijzigd.

Vraag 3 van 10

Welke waarde bevat n na deze code?

int n = 14;
int *p = &n;
*p = 23;
  1. 23
  2. 14
  3. 37
  4. 0
Toon antwoordVerberg antwoord
Antwoord: A
p bevat het adres van n, dus *p duidt hetzelfde gehele getal aan. Als je via *p de waarde 23 toewijst, vervang je de waarde in n. Als je denkt dat 14 behouden blijft, verwar je een schrijfbewerking via de pointer met een wijziging van het adres dat de pointer zelf bevat.

Vraag 4 van 10

Wat is score nadat clear(score) is teruggekeerd?

void clear(int value) { value = 0; }
/* In the caller: */
int score = 27;
clear(score);
  1. 0
  2. 1
  3. 27
  4. Ongedefinieerd gedrag
Toon antwoordVerberg antwoord
Antwoord: C
De functie ontvangt een lokale kopie van het gehele argument. Als ze value op nul zet, verandert die parameter, zodat score van de aanroepende functie 27 blijft. Om nul te krijgen, zou een functie &score via een pointer naar een geheel getal moeten ontvangen en naar het object waarnaar wordt gewezen moeten schrijven.

Vraag 5 van 10

Wat drukt dit af op een systeem met objectpointers van 8 bytes?

char label[12] = "oak";
char *p = label;
printf("%zu %zu
", sizeof label, sizeof p);
  1. 4 8
  2. 12 8
  3. 12 12
  4. 3 8
Toon antwoordVerberg antwoord
Antwoord: B
label is een array van twaalf tekens, dus sizeof label is 12, ook al heeft de opgeslagen tekst maar drie letters. p is een afzonderlijk pointerobject en neemt op dit systeem 8 bytes in. Als je het afsluitende nulteken meetelt, krijg je het gebruikte begindeel van de string, niet de gedeclareerde arraygrootte.

Vraag 6 van 10

Wat zijn left en right na de aanroep?

void exchange(int *a, int *b) {
    int *t = a;
    a = b;
    b = t;
}
/* In the caller: */
int left = 11, right = 29;
exchange(&left, &right);
  1. 29, 11
  2. 29, 29
  3. 11, 11
  4. 11, 29
Toon antwoordVerberg antwoord
Antwoord: D
De functie verwisselt haar lokale pointerkopieën zonder aan *a of *b toe te wijzen. Er wordt geen object met een geheel getal beschreven, dus left blijft 11 en right blijft 29. Als je verwacht dat de waarden worden verwisseld, verwar je het verplaatsen van lokale adreswaarden met het kopiëren van de gehele getallen die via die adressen worden bereikt.

Vraag 7 van 10

Een functie is gedeclareerd als int read_count(int *out);. Ze geeft 1 terug bij succes en schrijft het aantal alleen bij succes. Welke code van de aanroepende functie drukt een nieuw ingelezen aantal veilig af?
  1. int n; if (read_count(&n)) printf("%d", n);
  2. int n; read_count(&n); printf("%d", n);
  3. int n; if (read_count(n)) printf("%d", n);
  4. int *n; if (read_count(n)) printf("%d", *n);
Toon antwoordVerberg antwoord
Antwoord: A
De aanroepende functie geeft het adres van een object met een geheel getal door en gebruikt de waarde alleen nadat de successtatus waar is. Onvoorwaardelijk afdrukken zou een niet-geïnitialiseerde n kunnen lezen als de functie faalt zonder te schrijven. Een niet-geïnitialiseerde pointer levert geen geldig uitvoerobject dat de functie kan vullen.

Vraag 8 van 10

Gegeven int x = 6, y = 17; const int *p = &x;, welke toewijzing is toegestaan?
  1. *p = y;
  2. (*p)++;
  3. p = &y;
  4. *p += 1;
Toon antwoordVerberg antwoord
Antwoord: C
p is een pointer naar const-gekwalificeerde gegevens van een geheelgetaltype, dus de pointer mag naar y gaan wijzen. Een toewijzing via *p wordt afgewezen omdat die toegang alleen lezen toestaat. De beperking geldt voor toegang via deze pointer; x zelf is zonder const gedefinieerd en kan nog steeds via de eigen naam worden gewijzigd.

Vraag 9 van 10

Wat zijn de eindwaarden van a en b?

int a = 4, b = 13;
int *p = &a, *q = p;
*p += 3;
p = &b;
*q = *p - 2;
*p = *q + 5;
  1. 7, 12
  2. 11, 13
  3. 7, 18
  4. 11, 16
Toon antwoordVerberg antwoord
Antwoord: D
De eerste schrijfbewerking maakt a gelijk aan 7. Als p naar iets anders gaat wijzen, blijft q naar a wijzen. De volgende toewijzing zet a op 11 met behulp van de waarde 13 van b, waarna de laatste schrijfbewerking b op 16 zet. Als je a op 7 houdt, verplaats je ten onrechte ook q wanneer alleen p naar iets anders gaat wijzen.

Vraag 10 van 10

Welk paar drukt de laatste regel af?

int *next_value(void) {
    static int n = 20;
    n += 3;
    return &n;
}
/* In the caller: */
int *first = next_value();
int saved = *first;
int *second = next_value();
printf("%d %d
", saved, *first + *second);
  1. 23 49
  2. 23 52
  3. 26 52
  4. 23 46
Toon antwoordVerberg antwoord
Antwoord: B
De eerste aanroep slaat 23 op; die waarde wordt naar saved gekopieerd. De tweede aanroep verandert hetzelfde statische gehele getal in 26 en beide teruggegeven pointers bereiken dat getal, dus hun waarden tellen op tot 52. Het resultaat 49 behandelt de eerste pointer als een bewaarde momentopname, maar alleen saved bevat een afzonderlijk gekopieerd geheel getal.
Set 2: Redeneer over arraybereiken en strings10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Wat is voor double values[8]; de waarde van &values[6] - &values[2]?
  1. 32
  2. 8
  3. 4
  4. 2
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Wat drukt puts(message + 5) af voor const char *message = "blue sky";?
  1. sky
  2. sky
  3. blue
  4. ky
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Wat is de waarde van *(p - 2) na deze instructies?

int bins[] = {6, 12, 18, 24, 30, 36, 42};
int *p = bins + 1;
p += 4;
  1. 18
  2. 36
  3. 12
  4. 24
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Wat geeft total(data + 1, 3) terug?

int total(const int *p, size_t n) {
    int s = 0;
    for (size_t i = 0; i < n; i++) s += p[i];
    return s;
}
/* In the caller: */
int data[] = {9, 4, 7, 2, 8};
  1. 20
  2. 13
  3. 17
  4. 21
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Wat geeft count(values) terug op een systeem met pointers van 8 bytes en int-waarden van 4 bytes?

size_t count(int a[9]) {
    return sizeof a / sizeof a[0];
}
/* In the caller: */
int values[9] = {0};
  1. 2
  2. 9
  3. 8
  4. 4
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Wat zijn taken en a[0] hierna?

int a[] = {8, 15, 22};
int *p = a;
int taken = *p++;
(*p)++;
  1. 15, 9
  2. 8, 9
  3. 8, 8
  4. 9, 8
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Wat is na deze code het grootste niet-negatieve gehele getal k waarvoor p + k binnen het toegestane pointerbereik kan worden gevormd?

int a[8];
int *p = a + 1, *end = a + 8;
p += (end - p) / 2;
end -= 2;
p += end - p;
  1. 3
  2. 2
  3. 1
  4. 4
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een beschrijfbare string met een afsluitend nulteken wordt omgekeerd met deze voorbereiding. Welke invoer brengt een ongeldige pointerberekening aan het licht voordat de lus begint?

size_t n = strlen(s);
char *left = s;
char *right = s + n - 1;
while (left < right) {
    char t = *left; *left++ = *right; *right-- = t;
}
  1. Een string van twee tekens
  2. Een string van één teken
  3. Een string met oneven lengte
  4. Een lege string
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een array int a[6] begint op het voorbeeldadres 0x3000 en elk geheel getal neemt 4 bytes in. Welke adressen stellen a + 1 en &a + 1 voor?
  1. 0x3004 en 0x3018
  2. 0x3004 en 0x3004
  3. 0x3018 en 0x3018
  4. 0x3001 en 0x3006
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Wat zijn count en p - text na deze lus?

char text[] = "abaca";
char *p = text;
int count = 0;
while (*p != '\0') {
    if (*p++ == 'a') count++;
}
  1. 3, 6
  2. 2, 5
  3. 3, 5
  4. 2, 6
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 3: Volg pointerniveaus en opslagindelingen10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Wat is na int n = 31; int *p = &n; int **pp = &p; het type van *pp?
  1. int
  2. int **
  3. int ***
  4. int *
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Wat is sizeof words bij objectpointers van 8 bytes?

const char *words[] = {"red", "violet", "gold", "teal"};
  1. 20
  2. 32
  3. 24
  4. 8
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Wat is *p hierna?

int a = 18, b = 25;
int *p = &a;
int **pp = &p;
*pp = &b;
**pp -= 4;
  1. 21
  2. 14
  3. 25
  4. 18
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Waarnaar wijst end na de conversie in char *end; long n = strtol("57kg", &end, 10);?
  1. Het eerste teken van "57kg"
  2. Het teken 7 in "57kg"
  3. Het teken k in "57kg"
  4. Het afsluitende nulteken na "57kg"
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een programma ontvangt de opdrachtregel ./paint amber 6 in de gebruikelijke indeling bij een gehoste implementatie. Wat zijn argc en argv[2][0]?
  1. 2 en 6
  2. 3 en 6
  3. 3 en '6'
  4. 2 en '6'
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Welk parametertype past bij int board[2][5] van de aanroepende functie na de gebruikelijke arrayconversie?
  1. int **board
  2. int (*board)[5]
  3. int *board[5]
  4. int (*board)[2]
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Wat zijn a, b en *p na de aanroep?

void shift(int **slot, int *next) {
    **slot += 2;
    *slot = next;
    **slot += 3;
}
/* In the caller: */
int a = 5, b = 12;
int *p = &a;
shift(&p, &b);
  1. 7, 15, 15
  2. 10, 12, 12
  3. 7, 15, 7
  4. 5, 15, 15
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Wat is de afstand in bytes van map[2][3] tot map[0][0] en wat is het type van map + 1, voor int map[4][6] met gehele getallen van 4 bytes?
  1. 36 bytes en int **
  2. 60 bytes en int **
  3. 36 bytes en int (*)[6]
  4. 60 bytes en int (*)[6]
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Wat zijn word en de index van p hierna?

char word[] = "abcd";
char *p = word;
char saved = *p++;
*p = saved;
p += 2;
*p = 'z';
  1. "aacz", index 3
  2. "bbcz", index 3
  3. "aazd", index 2
  4. "aacd", index 4
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een hulpfunctie moet een pointer teruggeven naar het grootste element van de niet-lege array van de aanroepende functie, zodat die het element kan wijzigen. Welk ontwerp voldoet aan beide eisen?
  1. Kopieer de grootste waarde naar een lokale int en geef het adres daarvan terug.
  2. Sla de grootste waarde op in een statische int en geef het adres daarvan terug.
  3. Houd de index van het grootste element bij en geef &a[index] terug.
  4. Houd de grootste waarde bij en geef die terug via een const int *.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 4: Kies bewerkingen en maak vergelijkingsfuncties voor sorteren10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Wat declareert int (*filter)(int);?
  1. Een functie die int ontvangt en int * teruggeeft.
  2. Een pointer naar een functie die int ontvangt en int teruggeeft.
  3. Een functie die int * ontvangt en int teruggeeft.
  4. Een pointer naar een functie die int * ontvangt en int teruggeeft.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Een functie heeft de signatuur void visit(int *a, size_t n, void (*action)(int *)); en void mark(int *x); is beschikbaar. Hoe geeft een aanroepende functie mark door voor een array data van vijf elementen?
  1. visit(data, 5, mark());
  2. visit(data, 5, *mark(data));
  3. visit(data, 5, mark(data));
  4. visit(data, 5, mark);
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Welke waarde wordt aan result toegewezen?

int difference(int a, int b) { return a - b; }
int product(int a, int b) { return a * b; }
int (*op)(int, int) = difference;
op = product;
int result = (*op)(7, 4);
  1. 28
  2. 3
  3. 11
  4. -3
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Welke aanroep sorteert voor int readings[8]; alle elementen met een compatibele callbackfunctie compare?
  1. qsort(readings, sizeof readings, 8, compare);
  2. qsort(readings, 8, sizeof readings, compare);
  3. qsort(readings, 8, sizeof readings[0], compare);
  4. qsort(readings, 8, sizeof readings[0], compare());
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Wat zijn result en calls na deze code?

int calls = 0;
int add_one(int x) { calls++; return x + 1; }
int twice(int x) { calls++; return x * 2; }
/* In a function: */
int (*ops[])(int) = {twice, add_one};
int result = ops[1](ops[0](5));
  1. 12, 2
  2. 11, 1
  3. 12, 1
  4. 11, 2
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Een vergelijkingsfunctie voor gehele getallen moet voor elke weergeefbare int werken in oplopende volgorde. Welke retour-expressie voldoet aan die eis nadat de elementen in x en y zijn gelezen?
  1. (x > y) - (x < y)
  2. (x < y) - (x > y)
  3. x - y
  4. x > y
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Wat haalt voor een array const char *labels[] de string op die als vergelijkingsparameter const void *a is doorgegeven, voordat strcmp wordt aangeroepen?
  1. (const char *)a
  2. (const char *const *)a
  3. *(const char *const *)a
  4. **(const char *const *)a
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een tabel int (*ops[3])(int, int) heeft geldige functies op posities 0 en 2 en NULL op positie 1. Welke voorwaarde maakt voor een geheel getal choice met teken de aanroep ops[choice](8, 2) veilig mogelijk?
  1. choice < 3 && ops[choice] != NULL
  2. choice >= 0 && choice < 3 && ops[choice] != NULL
  3. ops[choice] != NULL && choice >= 0 && choice < 3
  4. choice >= 0 && choice < 3
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Een vergelijkingsfunctie sorteert gehele getallen op absolute waarde en geeft nul terug voor tegengestelde waarden met gelijke absolute waarde. Ze sorteert {4, -4, 2, -2} met qsort. Welk resultaat is gegarandeerd?
  1. {-2, 2, -4, 4} in precies die volgorde.
  2. {2, -2, 4, -4} in precies die volgorde.
  3. {-4, 4, -2, 2} in precies die volgorde.
  4. De twee waarden met absolute waarde 2 gaan vooraf aan die met absolute waarde 4.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een lijst wordt met deze callbackfunctie op toenemende stringlengte gesorteerd. Wat is de fout?

const char *x = *(const char *const *)a;
const char *y = *(const char *const *)b;
size_t nx = strlen(x), ny = strlen(y);
return nx < ny;
  1. Ze geeft positief terug voor kortere tekst en nul voor langere tekst.
  2. Ze leest de stringlengten uit bytes van pointerposities.
  3. Ze wijzigt de strings terwijl ze de lengten meet.
  4. Ze vergelijkt tekenadressen in plaats van aantallen tekens.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Kom je niet uit je eigen opgave? Huiswerkhulp

Kernbegrippen

60 Kernbegrippen, elk met een definitie

A

Adresoperator
De unaire adresoperator & vraagt het adres van een adresseerbaar object of een functie op en levert een passend pointertype op.
Voor een array van gehele getallen levert &values[2] het adres van het derde element zonder de inhoud ervan te wijzigen.
Aliasing
Aliasing treedt op wanneer verschillende expressies of pointers hetzelfde object aanduiden, zodat een schrijfbewerking via de ene route zichtbaar is via een andere.
Na int *q = p; bereiken beide pointers hetzelfde gehele getal totdat een latere toewijzing een van beide naar iets anders laat wijzen.
argc
argc is het niet-negatieve aantal argumenten dat aan main bij een gehoste implementatie wordt doorgegeven. Het telt de strings die via argv worden geleverd, inclusief een programmanaam als die aanwezig is.
Controleer voordat je argv[2] leest of argc > 2, zodat de gevraagde argumentstring daadwerkelijk bestaat.
argv
argv is de lijst met tekenpointers die aan main bij een gehoste implementatie wordt doorgegeven, met een nulpointer op index argc en strings met gebruikersargumenten vanaf index 1.
Bij een gebruikelijke aanroep ./sum 8 13 bereikt argv[2] de string "13"; het argument is tekst vóór numerieke conversie.
Array met ongelijke rijlengten
Een array met ongelijke rijlengten is een verzameling waarvan de rijen verschillende lengten kunnen hebben, in C vaak voorgesteld met pointers naar afzonderlijk opgeslagen rijen.
Stringpointers kunnen naar "red" en "violet" verwijzen zonder voor beide woorden tekenrijen van gelijke grootte te reserveren.
Array van pointers
Een array van pointers slaat afzonderlijke adreswaarden op als elementen, terwijl de objecten waarnaar wordt gewezen op andere opslaglocaties staan.
Een lijst van vier stringpointers heeft vier adresposities, ook als de strings heel verschillende lengten hebben.
Arraygrenzen
Arraygrenzen bepalen welke elementindices bestaan en waarheen een pointer mag bewegen, waarbij alleen één extra positie als eindmarkering is toegestaan.
Voor een pointer aan het begin van een array is a - 1 vormen ongeldig, ook als het programma er nooit via leest.
Arraysegment
Een arraysegment kan in C worden doorgegeven als een beginpointer plus een aantal dat een geselecteerd aaneengesloten deel van een bestaande array beschrijft.
Als je data + 2 met een aantal van 3 doorgeeft, selecteer je de oorspronkelijke indices 2 tot en met 4 zonder ze te kopiëren.
Automatische opslagduur
Automatische opslagduur laat een gewoon lokaal object bestaan tijdens de uitvoering van zijn blok; de opslag eindigt wanneer de uitvoering dat blok verlaat.
Een gewoon lokaal resultaat van een hulpfunctie houdt bij terugkeer op te bestaan, ook als de hulpfunctie het adres ervan naar een pointer heeft gekopieerd.

B

Bufferoverloop
Een bufferoverloop schrijft voorbij de grenzen van een doelobject, bijvoorbeeld wanneer bij een stringkopie geen ruimte is voor het afsluitende nulteken.
Een woord van drie letters vereist vier tekenposities; een bestemming van drie bytes kan het afsluitende nulteken niet bevatten.
Byte
Een byte is de opslageenheid die sizeof meet; een char in C neemt altijd één byte in, maar de grootte van andere typen hangt af van het systeem.
Op een doelsysteem met gehele getallen van vier bytes neemt een array van acht gehele getallen 32 bytes in voordat een pointer wordt gedeclareerd.
Bytevolgorde
Bytevolgorde is de volgorde van bytes binnen een waarde die uit meerdere bytes bestaat; little-endiansystemen slaan de minst significante byte eerst op.
Een 0x12345678 van vier bytes heeft op een little-endiandoelsysteem de bytes 78 56 34 12 in volgorde van lage naar hoge adressen.

C

Callbackfunctie
Een callbackfunctie is een functie die aan een andere functie wordt doorgegeven zodat die haar kan aanroepen wanneer haar algemene taak een gekozen handeling of beslissing vereist.
Een doorloop ontvangt een pointer naar een handeling en roept die aan met het adres van elk element om dat element bij te werken.
Constante pointer
Een constante pointer is een pointerobject waarvan het opgeslagen adres na initialisatie niet kan worden gewijzigd, ook als het doelobject beschrijfbaar blijft.
Met int *const p = &x; is *p = 3; toegestaan, maar kan p = &y; de pointer niet naar iets anders laten wijzen.
Conversie van array naar pointer
Conversie van array naar pointer zet een arrayexpressie in de meeste expressiecontexten om in een pointer naar het eerste element, zonder de elementen te kopiëren.
int *p = values; initialiseert een pointer naar values[0]; de oorspronkelijke array bevat nog steeds al haar elementen.

D

Derefereren
Derefereren past de unaire * toe op een pointer om het object waarnaar wordt gewezen aan te duiden, zodat je via die route op passende wijze kunt lezen of schrijven.
Na int *p = &score; verandert de toewijzing *p = 28; hetzelfde gehele getal dat later via score wordt gelezen.
Doorgifte op waarde
Doorgifte op waarde geeft een functieparameter een kopie van de waarde van het argument, ook als die waarde een pointer is naar opslag van de aanroepende functie.
Een toewijzing aan een lokale int-parameter laat het gehele getal van de aanroepende functie ongewijzigd; schrijven via een gekopieerd adres kan het wel wijzigen.
Doorgifte via verwijzing
Doorgifte via verwijzing is in C de gebruikelijke naam voor de techniek waarbij je een adres doorgeeft en het doelobject wijzigt, terwijl het adres zelf wordt gekopieerd.
Een verwisselfunctie die met &x en &y wordt aangeroepen, wijzigt variabelen van de aanroepende functie door via de twee lokale pointerparameters te schrijven.

E

Eindpointer
Een eindpointer legt vast waar een tekstconversie is gestopt, zodat de aanroepende functie het niet-verwerkte achterste deel onafhankelijk van het numerieke resultaat kan bekijken.
Bij de conversie van "73ms" met strtol kan de functie 73 teruggeven en de eindpointer van de aanroepende functie op de m zetten.

F

Functiepointer
Een functiepointer duidt een aanroepbare functie aan en legt de compatibele retour- en parametertypen vast die nodig zijn om de functie ermee aan te roepen.
int (*op)(int, int) = add; slaat een optelfunctie op die later als op(4, 7) kan worden aangeroepen.
Functiepointertabel
Een functiepointertabel is een array van functiepointers waarmee een bewerking wordt geselecteerd via een index die tijdens de uitvoering van het programma wordt gekozen.
Een bewerkingsmenu controleert de selectie-index voordat het die gebruikt om een tabelitem op te halen en aan te roepen.
Functiesignatuur
Een functiesignatuur beschrijft de retour- en parametertypen die compatibel moeten zijn met het pointertype waarmee de functie wordt aangeroepen.
Een pointer voor twee gehele parameters kan niet veilig een functie aanroepen die één double verwacht, zelfs niet via een expliciete typeconversie.

G

Geheugenadres
Een geheugenadres duidt een byte in de opslag aan; het adres van een object duidt de eerste byte van de opslag aan die het object inneemt.
Als een geheel getal van vier bytes op het voorbeeldadres 0x2000 begint, volgen de overige bytes tot en met 0x2003.
Generieke objectpointer
Een generieke objectpointer gebruikt void *, of een gekwalificeerde vorm daarvan, om een objectadres te bevatten dat vóór toegang weer een geschikt type moet krijgen.
Een vergelijkingsfunctie voor gehele getallen converteert const void * naar const int * voordat ze het arrayelement op dat adres leest.

I

Indexoperator
De indexoperator benadert een element door a[i] te definiëren als *(a + i), waarbij het elementtype van de pointer wordt gebruikt om de positie te schalen.
Als p naar het oorspronkelijke element 2 wijst, benadert p[1] het oorspronkelijke element 3 van dezelfde array.
Indirectieniveau
Een indirectieniveau is één pointerstap in een keten van adressen. Je volgt die met een dereferentie en voegt er één toe door het adres van een object op te vragen.
Voor int **pp geeft de eerste dereferentie een int * en bereikt de tweede een object met een geheel getal.

N

Nulpointer
Een nulpointer is een pointerwaarde die geen object of functie aanduidt en een optioneel leeg resultaat of een ontbrekende callbackfunctie kan voorstellen.
Een zoekfunctie voor elementen geeft NULL terug als er geen overeenkomst is, zodat de aanroepende functie het resultaat test voordat ze het derefereert.
Nulteken
Het nulteken is het teken met waarde nul, geschreven als '\0', dat een C-string binnen de toegankelijke tekenopslag afsluit.
Een lus die de lengte bepaalt stopt op het afsluitende nulteken, terwijl een volledige kopieerlus ook dat laatste teken moet schrijven.

O

Omzetting van een arrayparameter naar een pointerparameter
Omzetting van een arrayparameter naar een pointerparameter verandert een arraydeclaratie in een parameterlijst van een functie in een pointerparameter, zonder de arraylengte te kopiëren.
Een parameter die als int a[10] is geschreven, is binnen de functie een pointer, dus sizeof a meet die pointer.
Onbepaalde pointerwaarde
Een onbepaalde pointerwaarde is de niet-geïnitialiseerde toestand van een gewone lokale pointer; die levert geen bekend bruikbaar doelobject op.
De declaratie int *p; alleen laat het programma die pointer niet veilig testen, volgen of gebruiken om erdoorheen te schrijven.
Ongedefinieerd gedrag
Ongedefinieerd gedrag betekent dat de C-standaard geen eisen stelt aan de uitkomst van een bewerking, ook niet of het programma stopt of doorgaat.
Een nulpointer derefereren heeft ongedefinieerd gedrag, ook als een bepaalde machine een segmentatiefout meldt.
Ongeldige pointer naar een niet meer bestaand object
Een ongeldige pointer naar een niet meer bestaand object is een pointer waarvan de levensduur van het vroegere doelobject is geëindigd, zodat die niet bruikbaar is voor latere toegang tot dat object.
Het adres van een lokale variabele met automatische opslagduur teruggeven laat een ongeldige pointer naar een niet meer bestaand object achter wanneer de functieaanroep eindigt.
Operatorprioriteit
Operatorprioriteit bepaalt hoe operatoren worden gegroepeerd wanneer haakjes de groepering niet vastleggen, voordat de resulterende expressie wordt geëvalueerd.
Postfix-++ bindt sterker dan unaire *, dus *p++ wordt gegroepeerd als *(p++), niet als (*p)++.
Opslagduur
Opslagduur beschrijft hoelang de opslag van een object bestaat, bijvoorbeeld één uitvoering van een blok voor een gewone lokale variabele met automatische opslagduur.
Een pointer die uit een binnenblok wordt bewaard kan het gewone lokale gehele getal van dat blok daarna niet in leven houden.
Overloop bij gehele getallen met teken
Overloop bij gehele getallen met teken treedt op wanneer een rekenkundig resultaat met teken buiten het weergeefbare bereik van zijn type valt, wat in C ongedefinieerd gedrag oplevert.
Een vergelijkingsfunctie voor gehele getallen die x - y gebruikt kan overloop veroorzaken, ook als elke invoer afzonderlijk in een int past.

P

Pointer
Een pointer is een getypeerde waarde die een object of functie kan aanduiden; een objectpointer kan ook een nulpointer zijn of een positie direct voorbij het laatste element aanduiden.
In int *p = &count; slaat de pointervariabele een adres op, terwijl het gehele getal zijn eigen waarde behoudt.
Pointer direct voorbij het laatste element
Een pointer direct voorbij het laatste element markeert de positie direct na het laatste element van een array en mag als grens dienen, maar niet worden gederefereerd.
Voor int a[5] kan een doorloop stoppen wanneer de huidige pointer gelijk is aan a + 5, voordat een volgend element wordt benaderd.
Pointer naar een array
Een pointer naar een array duidt een hele array aan als elementtype, zodat 1 optellen de pointer verplaatst met de opslaggrootte van die volledige array.
Voor int a[4] heeft &a het type int (*)[4]; de stap bij pointerrekenen verschilt van die van &a[0].
Pointer naar een const-gekwalificeerd object
Een pointer naar een const-gekwalificeerd object staat toe dat je het doelobject leest, maar wijst schrijfbewerkingen via die pointer af; de pointer zelf mag nog steeds naar iets anders gaan wijzen.
const int *p = &x; staat p = &y; toe, terwijl een toewijzing zoals *p = 3; wordt afgewezen.
Pointer naar een pointer
Een pointer naar een pointer duidt een pointerobject aan en voegt een indirectieniveau toe voordat toegang de uiteindelijke gegevens bereikt waarnaar wordt gewezen.
Voor int **pp = &p; laat de toewijzing *pp = &y; de pointer p naar iets anders wijzen, terwijl **pp = 8; het doelobject wijzigt.
Pointerdeclarator
Een pointerdeclarator gebruikt * bij een afzonderlijke gedeclareerde naam om aan te geven dat het benoemde object een pointertype heeft.
In int *left, right; is alleen left een pointer; om right als pointer te declareren is nog een ster nodig.
Pointerparameter
Een pointerparameter is een lokale pointer van een functie die vanuit een argument wordt geïnitialiseerd en de functie via een adres toegang geeft tot een object.
Een hulpfunctie om een waarde op nul te zetten ontvangt &score via int *out en schrijft met *out nul naar het gehele getal van de aanroepende functie.
Pointerrekenen
Pointerrekenen verplaatst een pointer met aantallen van het elementtype; de resultaten zijn beperkt tot de array en de grens direct voorbij het laatste element.
Als je 2 optelt bij een pointer naar een geheel getal, verplaatst die twee posities voor gehele getallen; de aanroepende functie hoeft de afstand niet met sizeof te schalen.
Pointerverschil
Een pointerverschil is het aantal elementen met teken dat ontstaat door twee posities in één array van elkaar af te trekken, inclusief een geldige positie direct voorbij het laatste element.
&a[5] - &a[2] geeft 3 elementen, ongeacht hoeveel bytes één element op dat systeem inneemt.
Postfixincrementatie
Postfixincrementatie levert de oude waarde van een object aan de omringende expressie en verhoogt dat object, zoals bij de pointerstap in *p++.
int x = *p++; leest het element op de oude positie en laat p naar de volgende positie wijzen.
ptrdiff_t
ptrdiff_t is het geheelgetaltype met teken dat voor pointeraftrekking wordt gebruikt; het resultaat van een gedefinieerde aftrekking moet in dat type kunnen worden weergegeven.
Neem <stddef.h> op voor ptrdiff_t en gebruik %td om een verschil tussen geldige posities in dezelfde array af te drukken.

Q

qsort
qsort is de standaardbibliotheekfunctie die een array herschikt met behulp van het aantal elementen, de elementgrootte en een meegegeven vergelijkingsfunctie als callback.
Bij het sorteren van stringpointerelementen is sizeof names[0] nodig voor de positiegrootte, niet het aantal tekens van een string.

R

Rijgewijze opslagvolgorde
Rijgewijze opslagvolgorde slaat in een rechthoekige array alle elementen van één rij op vóór de volgende rij; de vaste rijbreedte bepaalt de stappen tussen rijen.
Voor int a[3][4] schuift het optellen van 1 bij de geconverteerde a een volledige rij van vier gehele getallen op, niet één geheel getal.

S

Segmentatiefout
Een segmentatiefout is een door het systeem gemelde ongeldige geheugentoegang die een programma vaak stopt, hoewel ongeldige toegang in C niet altijd zo’n fout oplevert.
Het derefereren van een nulpointer kan stoppen met een segmentatiefout; een schrijfbewerking via een andere ongeldige pointer kan ongemerkt gegevens beschadigen.
sizeof
De operator sizeof geeft de opslaggrootte van een type of object in bytes en geeft een waarde terug van het geheelgetaltype zonder teken size_t.
Voor char code[10] = "go"; geeft sizeof code de waarde 10, ook al heeft de tekst maar twee tekens.
Stabiele sortering
Een stabiele sortering behoudt de oorspronkelijke onderlinge volgorde van elementen die de vergelijkingsfunctie als gelijk behandelt; qsort biedt die garantie niet.
Als je namen alleen op lengte sorteert, kunnen twee even lange namen van volgorde veranderen, ook al is hun vergelijkingsresultaat nul.
Stackframe
Een stackframe is de opslag die een gangbare implementatie gebruikt voor de werkgegevens van één functieaanroep, waaronder veel lokale objecten met automatische opslagduur.
Na terugkeer uit een functie kan de oude frameopslag opnieuw worden gebruikt; een opgeslagen lokaal adres kan die niet behouden.
Statische opslagduur
Statische opslagduur laat de opslag van een object gedurende het hele programma bestaan; een lokale statische variabele behoudt haar waarde tussen aanroepen.
Twee pointers die naar één statisch resultaatobject zijn teruggegeven, zien dezelfde bijgewerkte waarde nadat een andere aanroep die heeft geschreven.
Stringliteral
Een stringliteral is tekst tussen aanhalingstekens in C die een array met statische opslagduur oplevert waarvan de tekens niet mogen worden gewijzigd.
const char *p = "oak"; maakt de literal toegankelijk via een toegang voor alleen lezen, terwijl char a[] = "oak"; een beschrijfbare kopie maakt.
strtol
strtol is de standaardbibliotheekfunctie die numerieke tekst aan het begin van een string omzet naar long en de stoppositie via char ** kan melden.
Door &end door te geven kan strtol de tekenpointer van de aanroepende functie vervangen door het adres van het eerste ongebruikte teken.

T

Tweedimensionale array
Een tweedimensionale array is een array waarvan de elementen arrays van vaste grootte zijn; ze slaat volledige aaneengesloten rijen op in plaats van afzonderlijke rijpointers.
int grid[2][5] wordt geconverteerd naar int (*)[5]; de array doorgeven als int ** zou een andere indeling beschrijven.
typedef
Een typedef-declaratie introduceert een naam voor een bestaand type, waardoor een complex functiepointertype gemakkelijker in declaraties opnieuw kan worden gebruikt.
Na typedef int (*operation)(int); declareert operation chosen; een functiepointer, geen functie-inhoud.

U

Uitlijning
Uitlijning is de beperking die een type stelt aan geschikte beginadressen voor zijn objecten; bij gewone declaraties wordt dit automatisch geregeld.
Bij een gangbare indeling met gehele getallen die op vier bytes zijn uitgelijnd, staan opeenvolgende arrayelementen op adressen die deelbaar zijn door vier.
Uitvoerparameter
Een uitvoerparameter is een pointerparameter waarmee een resultaat wordt opgeslagen in beschrijfbare opslag die de aanroepende functie beschikbaar stelt, naast een eventuele retourwaarde.
Een hulpfunctie voor deling kan afzonderlijke gehele getallen voor quotiënt en rest vullen en tegelijk een successtatus teruggeven.

V

Vergelijkingsfunctie
Een vergelijkingsfunctie ordent een paar elementen met een negatief resultaat, nul of een positief resultaat voor respectievelijk eerste ervoor, gelijkwaardig of eerste erna.
De retourwaarde (x > y) - (x < y) ordent gehele getallen oplopend zonder de waarden zelf van elkaar af te trekken.

Over deze les

Door StudeliFout melden

Maak je eigen Studiemateriaal

Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.

Aan de slag