De er gamle pointere fra 16-bit verdnen. Da 16-bit kun kan adressere 64k hukommelse er der et problem med pointer (og derved benyttelse) og hukommelse ud over de 64k. Derfor indførtes begrebet near eller far pointere. Near pointere kan bruges indefor de først 64k hukommelse alt udover skal bruge far pointere. Far pointere deklareres som regel ved brug af en addresse sammen med et 'off-set', men det er meget compiler afhængits. Nogle compilere gør det endda automatisk for dig, andre ikke...Så mit råd er at konsultere din compiler manual. Hvis det TC og DOS der snakkes om findes der nok en hel oplysninger rundt omkring på nettet, eller prøv eventuel med en avanceret DOS programmeringsbog...
Pointere er en helt speciel type variable som benyttes til at "pege" (deraf navnet) på en anden (eller andre) variabel af en bestemt type vha. deres fysiske adresse i programmet. Pointere optager altid 4 bytes plads i dit program, dvs. 32 bit - men i DOS miljø er det kun de sidste 20 bit (svarende til 1MB) som benyttes. I windows miljøer bruges alle 32 bit derimod.
Lad os tage en 'int' variabel, som i et DOS miljø fylder 2 bytes, svarende til 2*8 bit, med værdien 123:
Adresse Værdi 0040:0017 0x7B (LSB) Mindst betydende byte 0040:0018 0x00 (MSB) Mest betydende byte
Ponteren til denne variabel vil så indeholde 0x0417 (ja det er sandt - læs videre), hvilket altså betyder at pointeren peger på start-byten til 'int' variablen. Pointeren er egentlig delt i to, sådan som ':' indikerer - nemlig en segment adresse (første del) og et offset. Da et segment kun kan indeholde 64 KB (svarende til 16 bit) kan flere forskellige segment/offset kombinationer betyde samme fysiske adresse. Således er adressen '0x0040:0x0017' og '0x0000:0x0417' den samme adresse. (prøv selv - adressen giver dig keyboard flagene - altså Caps, Numlock, Scrolllock, shift, ctrl, alt etc.)
Forskellen på en 'near' og en 'far' pointer er antallet af de 20 bit der rent faktisk benyttes til at adressere variablen. Ved 'near' pointere (som alle pointere bliver, hvis du ikke skriver 'far') er det kun segment-delen der benyttes (de '0017' i mit eksempel ovenfor) fra selve pointeren - resten tages fra programmets data-segment pointer. Ved 'far' pointere derimod, tages segment adressen også fra pointeren.
For begge gælder at evt. additioner (altså flytning af pointeren f.eks. 3 bytes frem) kun foretages på offset delen. Man kan derfor risikere at offsettet "løber rundt" fra næsten 64 KB til 0 KB. Der findes derfor faktisk en pointer-type mere der hedder 'huge'. Den er magen til 'far' i brug, men compileren sørger for at alle pointer additioner tager højde for segmenteringen. (Dvs. at pointeren "regnes om" til en long, additionen laves, og derefter "regnes tilbage" til en pointer.)
I TC++ bestemmes hvilken pointer typer der bruges som default (dvs. hvis du ikke explicit skriver andet) af den såkaldte memory-model. Jeg har altid brug modellerne 'large' eller 'huge', hvorved alle pointere pr. definition er 'far'. Med mindre du selv laver variabler der er større end 64KB vil dette være fuldt ud tilstrækkeligt.
Du kan bruge pointere hvis du vil lave en underfunktion der skal manipulere generelt på en bestemt type variable, f.eks. et char array:
char * far tilstorebogstaver(const far char *s) // eller (const far char s[]) { char near *streng = s; while (*streng != '\x0') { switch (*streng) { case 'æ' : *streng = 'Æ'; break; case 'ø' : *streng = 'Ø'; break; case 'å' : *streng = 'Å'; break; default : if (*streng >= 'a' && *streng <= 'z') *streng -= 'a' - 'A'; break; }; streng++; }; return s; } : : tilstorebogstaver(fornavn); tilstorebogstaver(efternavn); :
Det specielle ved pointere er tydeligt i dette eksempel. Normalt kan man ikke ændre på variabler der er overført med 'const' til en funktion - her 's'. MEN ... det er jo heller ikke selve pointerens værdi vi vil ændre på, men derimod indholdet af den variabel pointeren PEGER på. Du kan ændre ovenstående kode fra '*streng =' til 's[idx] =' og lave et 'int' index i stedet for, men pointer-løsningen ovenfor er faktisk hurtigere fordi 's[idx] =' oversættes af compileren til:
tmpptr = s; tmpptr += idx * sizeof(*s); tmpptr =
og det er jo netop det jeg selv skriver. Pointere og memory modeller er beskrevet i udførligt i manualen til TC ++.
Tak for et vældig godt svar, men du slipper ikke helt endnu, for jeg har en del supplerende spørgsmål (ting jeg ikke helt forstår):
>Således er adressen '0x0040:0x0017' og '0x0000:0x0417' den samme >adresse.
Jeg forstår ikke _hvorfor_ de to adresser er de samme.
>...pointere bliver, hvis du ikke skriver 'far') er det kun segment-delen der >benyttes (de '0017' i mit eksempel ovenfor) fra selve pointeren...
Mener du ikke, at det kun er offset-delen der benyttes? Var de 0017 ikke offset-delen?
Hvorfor bruger man ikke altid huge-pointere? Er det ikke (i hvert fald normalt) det smarteste?
>mindre du selv laver variabler der er større end 64KB vil dette være fuldt ud >tilstrækkeligt.
Hvorfor variable større end 64K? Var det fordi at offset'et så "løb rundt"?
HER BLIVER DET INTERESSANT, MEN TILGENGÆLD FORSTÅR JEG OGSÅ MINDRE AF DET: >Du kan bruge pointere hvis du vil lave en underfunktion der skal manipulere >generelt på en bestemt type variable, f.eks. et char array:
Jeg forstår godt din funktion, men slet ikke hvorfor den ikke bare kan defineres som:
char * tilstorebogstaver(char *s)
og hvad mener du med at "manipulere generelt på en bestemt type variable"?
A) De gamle 80x86 Intel CPU'er kører med segmenteret hukommelsesarkitektur, dvs. den kan styre op til 1MB, men gør det i blokke på 64KB. Disse klumper kaldes et segment - deraf navnet. Der opereres med fire forskellige segmenttyper på et hvilket som helst tidspunkt under afviklingen af et program, nemlig code (CS), data (DS), stack (SS) og extra (ES). (Men ikke nødvendigvis samme code eller data segment for hele programmet - derfor kan både kode og data overstige 64KB.)
Hukommelsen addresseres vha. 2 16 bit værdier: segment adressen og et offset derindenfor. Som jeg tidligere skrev, benyttes ialt kun 20 bit (hvorfor ved kun Intel), så da segmentadressen kun er på 16 bit, antages de sidste 4 bit blot at være nul. Derfor vil et givent segment altid starte på et helt multiplum af 16, og een segmentadresse dermed svare til 16 offset adresser.)
Da offsettet jo stadig kan bevæge sig fra 0-65535, kan man altså konstruere to forskellige pointere til samme adresse: Lad os antage at vort datasegment starter på adresse 64 decimalt, og at jeg skal have fat i nogle data så ligger på offset 256 decimalt derindenfor. Så vil adressen 64:256 pege på den, men det vil 64+1 (*16):256-16 = 65:240 også gøre, og 64+2 (*16):256-32 = 65:224 og 64+4(*16):256-48 = 68:208 osv.
B) Klart - tyrkfjel. Du har naturligvis ret i at det kun er offsettet der benyttes på near pointere - segmentet antages jo netop at være det samme.
C) Jeg kan godt følge din betragtning omkring HUGE pointere - det er jo nok i sidste ende et spørgsmål om at spare maskin-instruktioner. HUGE pointerne bliver som jeg skrev "normaliseret" inden evt. additioner/subtraktioner og så "denormaliseret" igen. (Normalisering vil sige at så meget af offset værdien som muligt konverteres til segment adresse, altså i klumper af 16. Derved vil ovenstående eksempel på 64:256, bliver normaliseret til 64+16(*16=256):256-16*16 = 80:0.)
D) Ja, grænse for hvornår man SKAL bruge HUGE pointere er netop de 64KB, fordi offsettet så "løber" rundt. Når du bruge HUGE-pointere vil compileren sørge for at segmentadressen hele tiden følger med (alle datasegmenter ligger fysisk i rækkefølge i hukommelsen) når pointeren flyttes med p++ eller ved at du skriver a[++idx].
Normliseringen af HUGE pointere laves i øvrigt også sammenligninger, hvilket jvnf. ovenstående eksempel jo er vigtigt idet FAR pointerne 64:256 og 68:192 jo ikke er det samme - men hvis de var HUGE ville compileren sørge for at de blev normaliseret før sammenligningen, hvorefter det begge så ville være 80:0 og dermed ens.
E) Der er to måder hvorpå en given funktion kan manipulere med indholdet af den overførte parameter: ved overførsel via pointer eller (kun C++) ved overførsel via reference:
Pointere: void bytvaerdi(int *v1, int *v2) Reference: void bytvaerdi(int &v1, int &v2)
Personligt foretrækker jeg pointer versionen, fordi kaldet så er forskelligt fra en funktion, hvor værdierne IKKE kan manipuleres:
KAN ændres: bytvaerdi(&hoejde, &bredde) IKKE ændres: bytvaerdi(hoejde, bredde)
hvori man ikke i kaldet kan skelne hvis man bruger reference:
KAN ændres: bytvaerdi(hoejde, bredde); IKKE ændres: bytvaerdi(hoejde, bredde);
Man kunne selvfølgelig brug CONST i sidste situation, men det virker på en eller anden måde bagvendt. Derfor ser man også oftest at reference (&) kun bruges i member functions til en klasse - f.eks. overloadede operatorer osv.
F) Med det mener jeg funktioner som strcpy, strcat, strupr og alle de andre standard funktioner til at manipulere streng variabler med for eksempel.
Fordi ellers kan du ikke benytte funktionen samme med andre dele af dit program, med mindre at de har samme data segment (DS) - og det har de kun i de tre "små" modeller compact, small og medium.
Husk på at segmentadressen IKKE overføres ved near, men antages at være den samme som for den aktuelle funktion. Det betyder at hvis du deler dit program i flere "modules" (altså et projekt med flere forskellige .cpp sources) så er data segment adressen typisk forskellig for hvert af modulerne - og så rækker offsettet jo ikke.
Tak for hjælpen. Jeg tror, at jeg forstår det nogenlunde (i hvert fald hvis min konklusion i sidste kommentar er rigtig).
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.