- Leren
- Informatica & ICT
- Programmeren
- Programmeren in C: pointers, geheugenbeheer en debuggen
- Pointers en geheugenadressen in C
Pointers en geheugenadressen in C
&, 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
- 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 geplaatstBeschrijf 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 pointersGebruik 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.
- Breng in beeld hoe variabelen in het geheugen zijn geplaatst
- Schrijf functies die via pointerparameters variabelen van de aanroepende functie wijzigen, bescherm gegevens die alleen worden gelezen met
consten geef pointers terug die geldig blijven.- Geef variabelen via verwijzing door met pointerparametersLeg 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 terugMarkeer 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.
- Geef variabelen via verwijzing door met pointerparameters
- 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 pointersTel 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 samenhangenGebruik 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 pointersSchrijf lussen die arrays en strings met een afsluitend nulteken doorlopen met pointers, gebruik constructies zoals
*p++correct en blijf binnen de grenzen.
- Reken met pointers
- Volg meerdere indirectieniveaus, laat functies een pointer van de aanroepende functie wijzigen en gebruik arrays van pointers zoals de opdrachtregelargumenten.
- Gebruik pointers naar pointersDeclareer 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 opdrachtregelargumentenMaak arrays van stringpointers, verwerk
argcenargvals pointers en onderscheid een array van pointers van een tweedimensionale array.
- Gebruik pointers naar pointers
- Sla functies op en roep ze aan via pointers, geef functies door aan andere functies en sorteer gegevens van elk type met de standaardfunctie
qsorten een vergelijkingsfunctie als callback.- Declareer functiepointers, wijs er waarden aan toe en roep functies ermee aanSchrijf 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 qsortGeef functies door als argumenten, gebruik
void-pointers om gegevens van elk type te verwerken en schrijf correcte vergelijkingsfuncties voorqsort.
- Declareer functiepointers, wijs er waarden aan toe en roep functies ermee aan
Pointers en geheugenadressen in C: uitleg
1Geheugen, 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:
| Element | Eerste byte | Laatste byte |
|---|---|---|
scores[0] | 0x1000 | 0x1003 |
scores[1] | 0x1004 | 0x1007 |
scores[2] | 0x1008 | 0x100B |
scores[3] | 0x100C | 0x100F |
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
sizeofmeet bytes;strlentelt 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
| Declaratie | Betekenis |
|---|---|
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. &countis een adres;countis het opgeslagen gehele getal.- Elke pointernaam heeft in een declaratie een eigen
*nodig. - Pointertypen moeten passen bij de objecten die je ermee benadert.
p = &bwijzigt de pointer en laat de gehele getallen ongemoeid.- Druk een objectpointer af met
%pen een argument van het typevoid *.
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 begintoestand | Effect |
|---|---|
*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
*pduidt het object aan dat viapwordt bereikt.- Een adres kopiëren maakt aliassen;
*pkopië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.
2Laat 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);
awijst naarxenbwijst naary.tbewaart de oorspronkelijke waarde vanx, namelijk 3.*a = *bschrijft 8 naarx.*b = tschrijft de bewaarde 3 naary.
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
*aen*bmet behulp van een bewaarde waarde. - Uitvoerparameters hebben geldige beschrijfbare objecten nodig die de aanroepende functie beschikbaar stelt.
scanfontvangt 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
| Declaratie | Mag p naar iets anders wijzen? | Mag je via *p een waarde toewijzen? |
|---|---|---|
const int *p = &x; | Ja | Nee |
int *const p = &x; | Nee | Ja |
const int *const p = &x; | Nee | Nee |
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, terwijlint *consthet adres vastlegt.- Een object dat als
constis 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.
3Beweeg 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:
| Pointertype | Grootte van één element | Adres na p + 3 |
|---|---|---|
int * | 4 bytes | 0x100C |
double * | 8 bytes | 0x1018 |
char * | 1 byte | 0x1003 |
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 bytes | Resultaat |
|---|---|
sizeof a | 20 bytes voor alle vijf elementen |
sizeof p | 8 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 ameet alleen de hele array waaraecht een array is.- Een arrayparameter wordt omgezet naar een pointer; geef de bruikbare lengte afzonderlijk door.
&awijst 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.
4Volg 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.
| Expressie | Type | Bereikt object of bereikte waarde |
|---|---|---|
pp | int ** | Adres van p |
*pp | int * | De pointer opgeslagen in p |
**pp | int | Het 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 **ppkan het adres van eenint *-object bevatten.- Elke dereferentie volgt één pointerniveau.
**ppwijzigt het uiteindelijke gehele getal;*ppkan de tussenliggende pointer wijzigen.- Om een pointer van de aanroepende functie bij te werken, is het adres van die pointer nodig.
strtolgebruiktchar **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 expressie | Betekenis |
|---|---|
int (*rows)[4] | Pointer naar een rij van vier gehele getallen |
int *ptrs[4] | Array van vier pointers naar gehele getallen |
Geconverteerde grid | int (*)[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]enint **beschrijven verschillende opslagindelingen.- Twee indices op zichzelf bewijzen niet dat twee objecten hetzelfde type hebben.
5Kies 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 retourwaarde | Volgorde |
|---|---|
| Negatief | Eerste vóór tweede |
| Nul | Gelijkwaardig voor deze volgorde |
| Positief | Eerste 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.
qsortheeft 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
1Volg een adres en de waarde die je ermee bereikt
13 stappen
- 1IntroductieHoud 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?
- 2UitlegPlaats vier gehele getallen
Stel je voor dat
int scores[4]begint op0x1000op een systeem met gehele getallen van 4 bytes. Het eerste element gebruikt de bytes0x1000tot en met0x1003. Het volgende begint op0x1004, gevolgd door0x1008en0x100C.De elementen hebben aaneengesloten opslag. Deze indelingsregel geldt voor de array; hij garandeert geen volgorde voor twee afzonderlijk gedeclareerde gehele getallen.
- 3VoorbeeldMeet de array en daarna de inhoud
Voor
char word[20] = "hi";geven twee metingen verschillende resultaten:sizeof wordis 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.
sizeofgeeft een waarde van het typesize_tterug, afgedrukt met%zu; de operator telt niet hoeveel elementen het programma momenteel gebruikt. - 4VerbandHoud 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.
- 5UitdagingBepaal 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. - 6UitlegGeef de pointer een doelobject
Bekijk
int count = 42; int *p = &count;. De operator&vraagt het adres vancountop enpslaat dat adres op. Alscountop het voorbeeldadres0x2000staat, is de waarde van de pointer0x2000; 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 datpbevat. - 7TipLees de ster naast elke naam
int *p, q;declareert één pointer en één gewoon geheel getal. Gebruikint *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. - 8UitdagingLaat een pointer naar iets anders wijzen zonder gegevens te wijzigen
Begin met
int a = 9, b = 16; int *p = &a;. Voer daarnap = &b;uit. Welke waarden van gehele getallen veranderen en wat bevatpnu?---reveal---
Geen van beide gehele getallen verandert:
ablijft 9 enbblijft 16. Alleenpverandert en slaat het adres vanbop. Een pointertoewijzing vervangt een adres; ze kopieert niet de inhoud van het oude of het nieuwe doelobject. - 9VoorbeeldLees, tel op en schrijf via p
Ga verder met
int x = 10; int *p = &x;en voer uit:*p = *p + 5;De
*paan de rechterkant leest 10 uitx. Als je er 5 bij optelt, krijg je 15. De*paan de linkerkant duidt hetzelfde object aan, dus de toewijzing schrijft 15 naarx. Het adres inpis niet veranderd. Vervolgens leestint y = *p * 2;de waarde 15 en maakt een afzonderlijk geheel getalymet waarde 30. - 10KernpuntEen kopie van een adres maakt een andere route
Voor
int n = 6; int *p = &n; int *q = p;bereiken beide pointers de variabelen. De toewijzing heeft een adres gekopieerd, niet het object met het gehele getal.Na
*q = 14;geeft het lezen vann,*pof*qde 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. - 11TipControleer 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.
- 12UitdagingVolg een gekopieerd adres
Wat zijn de eindwaarden van
aenb?int a = 6, b = 19; int *p = &a, *q = p; p = &b; *q = *p + 1;---reveal---
awordt 20 enbblijft 19. Als jepnaar iets anders laat wijzen, blijftqnaarawijzen. De laatste regel leestbviapen schrijft de som viaqnaara. - 13SamenvattingVolg het object dat de linkerkant aanduidt
Lees de linkerkant voordat je een wijziging voorspelt. Een toewijzing aan
pwijzigt een adres; een toewijzing aan*pwijzigt 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.
2Laat een functie gegevens van de aanroepende functie wijzigen
11 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVolg het adres een aanroep in
- 2UitlegEen gekopieerd adres bereikt nog steeds de aanroepende functie
- 3VoorbeeldBewaar de waarde voordat je die vervangt
- 4VoorbeeldSchrijf de tweede waarde naar de eerste positie
- 5VoorbeeldVoltooi de verwisseling
- 6UitdagingLever twee uitvoerwaarden
- 7UitlegZet const op het bedoelde niveau
- 8VoorbeeldGeef een geldige positie terug
- 9UitdagingVerklaar het probleem met de levensduur
- 10UitdagingKies een zoekinterface met schrijftoegang
- 11SamenvattingControleer de schrijfbewerking en de levensduur
3Doorloop een array zonder de grens te overschrijden
15 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieBepaal begin, stap en stop
- 2UitlegVerplaats met elementen tegelijk
- 3VoorbeeldTrek twee posities van elkaar af
- 4KernpuntGebruik een grens die geen element is
- 5UitdagingBepaal het midden
- 6UitlegEen arrayexpressie levert een beginpointer
- 7VoorbeeldGebruik sizeof op de juiste plek
- 8UitdagingGeef een kleiner bereik door
- 9VoorbeeldLees en schuif dan op, of schuif op en lees dan
- 10UitlegZoek het einde van een string
- 11VoorbeeldKopieer één teken tegelijk
- 12TipReserveer de positie voor het afsluitende nulteken
- 13UitdagingHerstel het laatste lusbezoek
- 14UitdagingKies een veilige voorbereiding voor het omkeren
- 15SamenvattingMaak elke bezochte positie zichtbaar
4Wijzig een pointer en lees een array van pointers
12 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieMaak de pointer zelf tot doelobject
- 2VoorbeeldTeken twee pijlen
- 3UitlegKies welk niveau verandert
- 4VoorbeeldGeef de positie van de aanroepende functie via een adres door
- 5UitdagingVoorspel een wijziging van alleen de pointer
- 6VerbandLees de stoppositie van een conversie
- 7UitlegEen array kan pointerposities bevatten
- 8VoorbeeldDoorloop adressen
- 9TipControleer het aantal argumenten vóór toegang tot strings
- 10UitdagingLees een type voor een hele rij
- 11UitdagingStem een functie af op de aangeboden opslag
- 12SamenvattingBenoem het object op elk niveau
5Kies een bewerking en sorteer via een callbackfunctie
15 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieHoud een algemene taak en de gekozen bewerking uit elkaar
- 2UitlegLees het aanroepbare type
- 3VoorbeeldSelecteer een andere functie
- 4TipGeef een pointertype voor een tabel een naam
- 5UitdagingLees een verwante declaratie
- 6UitlegGeef een callbackfunctie voor een handeling door
- 7VoorbeeldGeef de vier sorteerargumenten door
- 8UitlegHaal het elementtype voor gehele getallen terug
- 9VoorbeeldGeef het teken van de volgorde terug
- 10UitdagingVermijd een verkorte aanpak die overloop veroorzaakt
- 11VerbandSorteer stringpointers, geen tekenrijen
- 12UitdagingKies de juiste grootte voor het sorteren
- 13KernpuntGelijkheid belooft geen stabiliteit
- 14UitdagingVolg een geselecteerde vergelijkingsfunctie als callback
- 15SamenvattingStem aanroep, indeling en volgorde op elkaar af
Pointers en geheugenadressen in C: oefenvragen
Set 1: Volg adressen en functieaanroepen10 Oefenvragen
Vraag 1 van 10
int neemt 4 bytes in. Een array int levels[7] begint op het voorbeeldadres 0x2400. Op welk adres begint levels[4]?Toon antwoordVerberg antwoord
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
int items = 16;, welke declaratie maakt een pointer naar items?Toon antwoordVerberg antwoord
&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;
Toon antwoordVerberg antwoord
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);
Toon antwoordVerberg antwoord
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);
Toon antwoordVerberg antwoord
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);
Toon antwoordVerberg antwoord
*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
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?Toon antwoordVerberg antwoord
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
int x = 6, y = 17; const int *p = &x;, welke toewijzing is toegestaan?Toon antwoordVerberg antwoord
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;
Toon antwoordVerberg antwoord
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);
Toon antwoordVerberg antwoord
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
double values[8]; de waarde van &values[6] - &values[2]?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
puts(message + 5) af voor const char *message = "blue sky";?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;
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};
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};
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)++;
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;
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;
}
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
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?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++;
}
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
int n = 31; int *p = &n; int **pp = &p; het type van *pp?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"};
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;
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
end na de conversie in char *end; long n = strtol("57kg", &end, 10);?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
./paint amber 6 in de gebruikelijke indeling bij een gehoste implementatie. Wat zijn argc en argv[2][0]?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
int board[2][5] van de aanroepende functie na de gebruikelijke arrayconversie?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);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
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?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';
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
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
int (*filter)(int);?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
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?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);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
int readings[8]; alle elementen met een compatibele callbackfunctie 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));
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
int werken in oplopende volgorde. Welke retour-expressie voldoet aan die eis nadat de elementen in x en y zijn gelezen?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
const char *labels[] de string op die als vergelijkingsparameter const void *a is doorgegeven, voordat strcmp wordt aangeroepen?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
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?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
{4, -4, 2, -2} met qsort. Welk resultaat is gegarandeerd?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;
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Kom je niet uit je eigen opgave? Huiswerkhulp
Kernbegrippen
A
Adresoperator
& vraagt het adres van een adresseerbaar object of een functie op en levert een passend pointertype op.&values[2] het adres van het derde element zonder de inhoud ervan te wijzigen.Aliasing
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.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../sum 8 13 bereikt argv[2] de string "13"; het argument is tekst vóór numerieke conversie.Array met ongelijke rijlengten
"red" en "violet" verwijzen zonder voor beide woorden tekenrijen van gelijke grootte te reserveren.Array van pointers
Arraygrenzen
a - 1 vormen ongeldig, ook als het programma er nooit via leest.Arraysegment
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
B
Bufferoverloop
Byte
sizeof meet; een char in C neemt altijd één byte in, maar de grootte van andere typen hangt af van het systeem.Bytevolgorde
0x12345678 van vier bytes heeft op een little-endiandoelsysteem de bytes 78 56 34 12 in volgorde van lage naar hoge adressen.C
Callbackfunctie
Constante pointer
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
int *p = values; initialiseert een pointer naar values[0]; de oorspronkelijke array bevat nog steeds al haar elementen.D
Derefereren
* 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.int *p = &score; verandert de toewijzing *p = 28; hetzelfde gehele getal dat later via score wordt gelezen.Doorgifte op waarde
int-parameter laat het gehele getal van de aanroepende functie ongewijzigd; schrijven via een gekopieerd adres kan het wel wijzigen.Doorgifte via verwijzing
&x en &y wordt aangeroepen, wijzigt variabelen van de aanroepende functie door via de twee lokale pointerparameters te schrijven.E
Eindpointer
"73ms" met strtol kan de functie 73 teruggeven en de eindpointer van de aanroepende functie op de m zetten.F
Functiepointer
int (*op)(int, int) = add; slaat een optelfunctie op die later als op(4, 7) kan worden aangeroepen.Functiepointertabel
Functiesignatuur
G
Geheugenadres
0x2000 begint, volgen de overige bytes tot en met 0x2003.Generieke objectpointer
void *, of een gekwalificeerde vorm daarvan, om een objectadres te bevatten dat vóór toegang weer een geschikt type moet krijgen.const void * naar const int * voordat ze het arrayelement op dat adres leest.I
Indexoperator
a[i] te definiëren als *(a + i), waarbij het elementtype van de pointer wordt gebruikt om de positie te schalen.p naar het oorspronkelijke element 2 wijst, benadert p[1] het oorspronkelijke element 3 van dezelfde array.Indirectieniveau
int **pp geeft de eerste dereferentie een int * en bereikt de tweede een object met een geheel getal.N
Nulpointer
NULL terug als er geen overeenkomst is, zodat de aanroepende functie het resultaat test voordat ze het derefereert.Nulteken
'\0', dat een C-string binnen de toegankelijke tekenopslag afsluit.O
Omzetting van een arrayparameter naar een pointerparameter
int a[10] is geschreven, is binnen de functie een pointer, dus sizeof a meet die pointer.Onbepaalde pointerwaarde
int *p; alleen laat het programma die pointer niet veilig testen, volgen of gebruiken om erdoorheen te schrijven.Ongedefinieerd gedrag
Ongeldige pointer naar een niet meer bestaand object
Operatorprioriteit
++ bindt sterker dan unaire *, dus *p++ wordt gegroepeerd als *(p++), niet als (*p)++.Opslagduur
Overloop bij gehele getallen met teken
x - y gebruikt kan overloop veroorzaken, ook als elke invoer afzonderlijk in een int past.P
Pointer
int *p = &count; slaat de pointervariabele een adres op, terwijl het gehele getal zijn eigen waarde behoudt.Pointer direct voorbij het laatste element
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
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
const int *p = &x; staat p = &y; toe, terwijl een toewijzing zoals *p = 3; wordt afgewezen.Pointer naar een pointer
int **pp = &p; laat de toewijzing *pp = &y; de pointer p naar iets anders wijzen, terwijl **pp = 8; het doelobject wijzigt.Pointerdeclarator
* bij een afzonderlijke gedeclareerde naam om aan te geven dat het benoemde object een pointertype heeft.int *left, right; is alleen left een pointer; om right als pointer te declareren is nog een ster nodig.Pointerparameter
&score via int *out en schrijft met *out nul naar het gehele getal van de aanroepende functie.Pointerrekenen
sizeof te schalen.Pointerverschil
&a[5] - &a[2] geeft 3 elementen, ongeacht hoeveel bytes één element op dat systeem inneemt.Postfixincrementatie
*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.<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.sizeof names[0] nodig voor de positiegrootte, niet het aantal tekens van een string.R
Rijgewijze opslagvolgorde
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
sizeof
sizeof geeft de opslaggrootte van een type of object in bytes en geeft een waarde terug van het geheelgetaltype zonder teken size_t.char code[10] = "go"; geeft sizeof code de waarde 10, ook al heeft de tekst maar twee tekens.Stabiele sortering
qsort biedt die garantie niet.Stackframe
Statische opslagduur
Stringliteral
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.&end door te geven kan strtol de tekenpointer van de aanroepende functie vervangen door het adres van het eerste ongebruikte teken.T
Tweedimensionale array
int grid[2][5] wordt geconverteerd naar int (*)[5]; de array doorgeven als int ** zou een andere indeling beschrijven.typedef
typedef-declaratie introduceert een naam voor een bestaand type, waardoor een complex functiepointertype gemakkelijker in declaraties opnieuw kan worden gebruikt.typedef int (*operation)(int); declareert operation chosen; een functiepointer, geen functie-inhoud.U
Uitlijning
Uitvoerparameter
V
Vergelijkingsfunctie
(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.