/ Forside / Teknologi / Udvikling / C/C++ / Nyhedsindlæg
Login
Glemt dit kodeord?
Brugernavn

Kodeord


Reklame
Top 10 brugere
C/C++
#NavnPoint
BertelBra.. 2425
pmbruun 695
Master_of.. 501
jdjespers.. 500
kyllekylle 500
Bech_bb 500
scootergr.. 300
gibson 300
molokyle 287
10  strarup 270
Hvor allokeres et objekts metode ?
Fra : Torben W. Hansen


Dato : 03-05-03 23:00

Hej,

Hvor allokeres et objekts metode - så vidt jeg ved, allokeres de da ikke
sammen med data'ene vel ?

Og lige et spørgsmål til - Når der instantieres flere objekter af samme
klasse som nedenfor - eksister der så kun een metode for Horn() ?

#include<iostream>

class Bil
{
public:
void Horn();
int data;
};

void Bil::Horn(){std::cout << "dyt, dyt...\n";}

int main()
{
Bil skoda, mercedes;

skoda.Horn();
mercedes.Horn();
system("pause");
return 0;
}



Med venlig hilsen
Torben W. Hansen



 
 
Mogens Hansen (04-05-2003)
Kommentar
Fra : Mogens Hansen


Dato : 04-05-03 06:15


"Torben W. Hansen" <mail@ins-intersoft.com> wrote

[8<8<8<]
> Hvor allokeres et objekts metode - så vidt jeg ved, allokeres de da ikke
> sammen med data'ene vel ?

Formelt set ved man det ikke, så vidt jeg husker.
I praksis allokeres den samme sted som almindelige globale funktioner.
Det kan f.eks. for embeddede systemer sige i en Flash PROM og for f.eks.
Windows på IA32 maskiner sige i kode segmentet (det segment som CPU register
CS angiver).

>
> Og lige et spørgsmål til - Når der instantieres flere objekter af samme
> klasse som nedenfor - eksister der så kun een metode for Horn() ?

Ja, der eksisterer så afgjort kun een metode (hvis den altså ikke er blevet
inlinet).
Der er endda et krav om at ikke inlinede funktions templates kun må
eksistere een gang i det færdige program.

Tag følgende (ikke compilerede) eksempel:
<foo.cpp>
#include <algorithm>

void foo_func(int* begin, int* end)
{
std::sort(begin, end);
}
<foo.cpp/>

<bar.cpp>
#include <algorithm>

void bar_func(int* begin, int* end)
{
std::sort(begin, end);
}
<bar.cpp/>

Når man compilerer disse to filer, vil begge objekt filer indeholder koden
til funktionen "std::sort<int>" (for compilere med greedy instantiation -
hvilket er det mest almindelige). Det er så et krav fra C++ Standarden at en
del af de genererede funktioner bliver sorteret fra, så der kun er een
tilbage. Det vil typisk være linkerens opgave at sikre dette.


Det eneste der vokser med antallet af objekter og som er relateret til
member-funktioner, antallet af vptr (ikke vtable).
Hvert objekt af en type, der har mindst een virtuel metode, vil have et
skjult datamedlem - vptr.

De nævnte implementerings detajler er ikke krævet af C++ Standarden, men
blot et udtryk for hvordan det typisk forholder sig i praksis.

Venlig hilsen

Mogens Hansen



Torben W. Hansen (04-05-2003)
Kommentar
Fra : Torben W. Hansen


Dato : 04-05-03 16:44

----- Original Message -----
From: "Mogens Hansen" <mogens_h@dk-online.dk>

Det er så et krav fra C++ Standarden at en
> del af de genererede funktioner bliver sorteret fra, så der kun er een
> tilbage. Det vil typisk være linkerens opgave at sikre dette.
Ok sådan - tak...

> Hvert objekt af en type, der har mindst een virtuel metode, vil have et
> skjult datamedlem - vptr.
Er der ikke noget med, at vptr kan ses hvis man bruger "size_of" operatoren
på objektet/klassen der indeholder en/flere virtuelle funktioner ?

> De nævnte implementerings detajler er ikke krævet af C++ Standarden, men
> blot et udtryk for hvordan det typisk forholder sig i praksis.
Ja - det husker jeg fra din forklaring af vptr og vtable.

Iøvrigt når vi alligevel er ved de virtuelle funktioner, så kommer jeg i
tanker om et spørgsmål. Se eksemplet nedenfor (undskyld hvis du har en
Skoda):

<eksempel>
#include <iostream>

class Bil
{
public:
virtual ~Bil(){};
virtual void Horn() = 0;
};

class Skoda:public Bil
{
public:
~Skoda(){};
void Horn(){std::cout << "skrat, skrat...\n";}
};

class Mercedes:public Bil
{
public:
~Mercedes(){};
void Horn(){std::cout << "dyt, dyt...\n";}
};


int main()
{
Bil* pegepind = new Skoda;
pegepind->Horn();
pegepind = new Mercedes;
pegepind->Horn();
return 0; // Skoda og Mercedes destrueres
}
<eksempel/>


I eksemplet bør den abstrakte datatype (class Bil) have en virtuel
destructer, da mindst een funktion i klassen er virtuel - i dette tilfælde
Horn(). Sådan som jeg har forstået det, så er det for at sikre det afledte
objekt bliver rigtigt destrueret, således at destructorer for både
basisdelen og den afledte del af objektet kaldes.

Jeg kan ikke gennemskue, hvordan den _virtuelle_ destructor løser dette -
hvad er lige forklaringen ?

Med venlig hilsen
Torben W. Hansen




Mogens Hansen (04-05-2003)
Kommentar
Fra : Mogens Hansen


Dato : 04-05-03 21:11


"Torben W. Hansen" <mail@ins-intersoft.com> wrote

[8<8<8<]
> Er der ikke noget med, at vptr kan ses hvis man bruger "size_of"
operatoren
> på objektet/klassen der indeholder en/flere virtuelle funktioner ?

Nej man kan ikke se vptr, ikke bortset fra at den naturligvis bidrager til
størrelsen.
Men det er der også andre "usynlige" ting der kan gøre. F.eks. ekstra bytes
af hensyn til alignment.

> (undskyld hvis du har en
> Skoda):

Jeg har ikke nogen Skoda - men jeg ville ikke have noget imod at have en.

[8<8<8<]
> int main()
> {
> Bil* pegepind = new Skoda;
> pegepind->Horn();
> pegepind = new Mercedes;
> pegepind->Horn();
> return 0; // Skoda og Mercedes destrueres

Nej, Skoda og Mercedes destrueres ikke.
Prøv at kigge på det med en debugger.

Når du allokerer med "new" skal man altid frigive med "delete" et eller
andet sted.

> }
> <eksempel/>
>
>
> I eksemplet bør den abstrakte datatype (class Bil) have en virtuel
> destructer, da mindst een funktion i klassen er virtuel - i dette tilfælde
> Horn().

Det er så afgjort rigtigt.
Jeg vil endda ikke blive overrasket hvis næste version af C++ vil sikre at
destructoren er virtuel når en klasse har mindst en virtuel metode -
tværtimod.
Det er lidt dumt at man kan lave en sådan fejl.

Der var f.eks. præcis sådan en fejl i "Accelerated C++" før 4. oplag.
Jeg fandt den med Borland CodeGuard - jeg så det ikke da jeg læste koden.
Værktøjer som CodeGuard er utroligt nyttige.

> Sådan som jeg har forstået det, så er det for at sikre det afledte
> objekt bliver rigtigt destrueret, således at destructorer for både
> basisdelen og den afledte del af objektet kaldes.

Det er rigtigt forstået.
Men det er kun et problem hvis nedlæggelsen foregår med delete via en
pointer til basisklassen.
Altså
int main()
{
Skoda favorit; // no problem

Skoda* superb = new Skoda();
delete superb; // no problem

Bil* felicia = new Skoda();
delete felica; // _must_ have virtuel destructor
}

> Jeg kan ikke gennemskue, hvordan den _virtuelle_ destructor løser dette -
> hvad er lige forklaringen ?

Præcist som andre virtuelle metoder.
Adressen på destructoren bliver slået op i den virtuelle tabel og derefter
kaldt.
Inden destructoren for den for den meste afledte klasse returnerer, kalder
destructoren fra sine umiddelbare basisklasser. Det er præcist som
destructorer altid gør, uanset om de er virtuelle eller ej.

Der er lidt ekstra overvejelser omkring hvad der sker under motorhjelmen
(for at holde os i bilverdenen), når man frigiver via en pointer til basis
klassen, og den afledte klasse har overskrevet "operator new" og "operator
delete".
Jeg antager at det ikke var det du tænkte på.
Iøvrigt virker det som man ville forvente; "operator delete" for den afledte
klasse bliver kaldt.

Venlig hilsen

Mogens Hansen




Torben W. Hansen (06-05-2003)
Kommentar
Fra : Torben W. Hansen


Dato : 06-05-03 00:08

Jeg starter med det vigtigste !

> > Jeg kan ikke gennemskue, hvordan den _virtuelle_ destructor løser
dette -
> Præcist som andre virtuelle metoder.
Jeg tvivler ikke på det du siger, men jeg kan bare ikke lige få øje på, at
det er helt det samme. Se de to eksempler - et med en virtuel metode samt et
andet eksempel med virtuel destructor.

Her eksempel 1:

#include <iostream>

class Basis
{
public:
virtual void Metode(){std::cout << "Basis Metode\n";};
};

class Afledt:public Basis
{
public:
void Metode(){std::cout << "Afledt Metode\n";};
};

int main()
{
Basis* bptr = new Afledt;
bptr->Metode();
delete bptr;
return 0;
}

Metode() findes i begge klasser. Da Basis::Metode() er virtuel og da der
eksisterer en Afledt::Metode(), medfører dette at
indholdet af vtabellen for "Afledt klassen" bliver:
edt::Metode() - og at denne metode kaldes.
Ovenstående er logisk at forstå,

men nu til eksempel 2:

#include <iostream>

class Basis
{
public:
virtual ~Basis(){std::cout << "Basis destructor\n";};
};

class Afledt:public Basis
{
public:
~Afledt(){std::cout << "Afledt destructor\n";};
};

int main()
{
Basis* bptr = new Afledt;
delete bptr;
return 0;
}

~Basis() findes _ikke_ i begge klasser. Selvom Basis:Basis() er virtuel,
så eksisterer der _ikke_ en Afledt:Basis(), men derimod en
Afledt:Afledt(). Så skulle man jo tro at indholdet af vtabellen for
"Afledt klassen" bliver: &Basis:Basis() - og at denne så bliver kaldt.

Klø'en i nakken - hvor er det, at jeg fejler ?

///////////////////////////////////// MINDRE VIGTIGT
///////////////////////////////////////

> Nej man kan ikke se vptr, ikke bortset fra at den naturligvis bidrager til
> størrelsen.
Jeg var nok lidt uklar - det var sådan set det jeg mente med at "se vptr"
med "size_of".

> Jeg har ikke nogen Skoda - men jeg ville ikke have noget imod at have en.
Nej - Skodaeksemplet hører nok nærmere fortiden til.

> Når du allokerer med "new" skal man altid frigive med "delete" et eller
> andet sted.
Ja det har du sørme ret i - men hvis man instantiere et objekt uden "new"
altså:

int main()
{
Bil skoda;
skoda.Horn();
return 0;
}

så kaldes destructoren; Men det gør den åbentbart ikke med brug af "new".
Selvom man bør
at rydde op med "delete" troede jeg faktisk at objekter automatisk blev
"deleted" når man returnerede til operativsystemet - ups... det vil så
sige, at jeg lavede memory leak !

> Værktøjer som CodeGuard er utroligt nyttige.
Uden at jeg ved det, så lyder det som en form intelligent regelchecker

> ... klassen, og den afledte klasse har overskrevet "operator new" og
"operator
> delete".
> Jeg antager at det ikke var det du tænkte på.
Nej - nu skal jeg nok lade være med at fylde mere på...

Med venlig hilsen
Torben W. Hansen












Torben W. Hansen (06-05-2003)
Kommentar
Fra : Torben W. Hansen


Dato : 06-05-03 00:22

Pokkers også !!! - jeg havde åbentbart fået slettet noget af teksten, så jeg
prøver lige igen...

Jeg starter med det vigtigste !

> > Jeg kan ikke gennemskue, hvordan den _virtuelle_ destructor løser
dette -
> Præcist som andre virtuelle metoder.
Jeg tvivler ikke på det du siger, men jeg kan bare ikke lige få øje på, at
det er helt det samme. Se de to eksempler - et med en virtuel metode samt et
andet eksempel med virtuel destructor.

Her eksempel 1:

#include <iostream>

class Basis
{
public:
virtual void Metode(){std::cout << "Basis Metode\n";};
};

class Afledt:public Basis
{
public:
void Metode(){std::cout << "Afledt Metode\n";};
};

int main()
{
Basis* bptr = new Afledt;
bptr->Metode();
delete bptr;
return 0;
}

Metode() findes i begge klasser. Da Basis::Metode() er virtuel og da der
eksisterer en Afledt::Metode(), medfører dette at
indholdet af vtabellen for "Afledt klassen" bliver: &Afledt::Metode() - og
at denne metode kaldes.
Ovenstående er logisk at forstå,

men nu til eksempel 2:

#include <iostream>

class Basis
{
public:
virtual ~Basis(){std::cout << "Basis destructor\n";};
};

class Afledt:public Basis
{
public:
~Afledt(){std::cout << "Afledt destructor\n";};
};

int main()
{
Basis* bptr = new Afledt;
delete bptr;
return 0;
}

~Basis() findes _ikke_ i begge klasser. Selvom Basis:Basis() er virtuel,
så eksisterer der _ikke_ en Afledt:Basis(), men derimod en
Afledt:Afledt(). Så skulle man jo tro at indholdet af vtabellen for
"Afledt klassen" bliver: &Basis:Basis() - og at denne så bliver kaldt.

Klø'en i nakken - hvor er det, at jeg fejler ?


// MINDRE VIGTIGT

> Nej man kan ikke se vptr, ikke bortset fra at den naturligvis bidrager til
> størrelsen.
Jeg var nok lidt uklar - det var sådan set det jeg mente med at "se vptr"
med "size_of".

> Jeg har ikke nogen Skoda - men jeg ville ikke have noget imod at have en.
Nej - Skodaeksemplet hører nok nærmere fortiden til.

> Når du allokerer med "new" skal man altid frigive med "delete" et eller
> andet sted.
Ja det har du sørme ret i - men hvis man instantiere et objekt uden "new"
altså:

int main()
{
Bil skoda;
skoda.Horn();
return 0;
}

så kaldes destructoren; Men det gør den åbentbart ikke med brug af "new".
Selvom man bør
at rydde op med "delete" troede jeg faktisk at objekter automatisk blev
"deleted" når man returnerede til operativsystemet - ups... det vil så
sige, at jeg lavede memory leak !

> Værktøjer som CodeGuard er utroligt nyttige.
Uden at jeg ved det, så lyder det som en form intelligent regelchecker

> ... klassen, og den afledte klasse har overskrevet "operator new" og
"operator
> delete".
> Jeg antager at det ikke var det du tænkte på.
Nej - nu skal jeg nok lade være med at fylde mere på...

Med venlig hilsen
Torben W. Hansen














Mogens Hansen (06-05-2003)
Kommentar
Fra : Mogens Hansen


Dato : 06-05-03 19:57


"Torben W. Hansen" <mail@ins-intersoft.com> wrote in message
news:b96rr2$1jhm$1@news.cybercity.dk...

[8<8<8<]
> int main()
> {
> Basis* bptr = new Afledt;
> delete bptr;
> return 0;
> }
>
> ~Basis() findes _ikke_ i begge klasser. Selvom Basis:Basis() er virtuel,
> så eksisterer der _ikke_ en Afledt:Basis(), men derimod en
> Afledt:Afledt(). Så skulle man jo tro at indholdet af vtabellen for
> "Afledt klassen" bliver: &Basis:Basis() - og at denne så bliver
kaldt.
>
> Klø'en i nakken - hvor er det, at jeg fejler ?

Destructorer hører til gruppen "Special member function".
Destructoren ikke har et navn, men erklæres med en speciel syntax.

Hvis man tager vtbl synvinklen, vil det gælde at en virtuel destructor har
et fast index i den.
F.eks. kunne index 0 i vtbl til "Basis" pege på "Basis:Basis", og så ville
index 0 i vtbl til "Afledt" på "Afledt:Afledt".
Når man så skriver
delete bptr;
vil der blive lavet et opslag i vtbl og funktionen, der er angivet på index
0 vil blive kaldt.

Man kan også bare tage en mere abstrakt synvinkel og sige at
* når destructoren er virtual, vil destructoren der hører til den
dynamiske type blive kaldt
* når destructoren ikke er virtual, vil destructoren der hører til den
statiske type blive kaldt
uden at bekymre sig om hvorfor og hvordan.
I dit eksempel er den statiske type "Basis" og den dynamiske type "Afledt".

[8<8<8<]
> int main()
> {
> Bil skoda;
> skoda.Horn();
> return 0;
> }
>
> så kaldes destructoren;

Ja

> Men det gør den åbentbart ikke med brug af "new".

Nej

> Selvom man bør
> at rydde op med "delete" troede jeg faktisk at objekter automatisk blev
> "deleted" når man returnerede til operativsystemet - ups... det vil så
> sige, at jeg lavede memory leak !


Ja, du lavede en memory leak.
Hukommelse der allokeres med "malloc" bliver frigivet at programmets runtime
system når det terminerer.
Hukommelse der allokeres med "operator new" bliver ikke frigivet af
programmets runtime system når det terminerer.
Og hvad der er endnu vigtigere så er der ikke noget der sikrer at de
nødvendige destructorer bliver kørt - det almindelige er at de ikke bliver
kørt.
Derfor kan der forekomme resource leak, som operativsystemet ikke har
mulighed for at rydde op i.

På trods af operativsystemet kan klare nogle af problemerne, vil jeg
kraftigt anbefale at man pænt frigiver alt hvad man har allokeret.

> > Værktøjer som CodeGuard er utroligt nyttige.
> Uden at jeg ved det, så lyder det som en form intelligent regelchecker

Der findes 2 typer automatiske værktøjer til at finde fejl i C++ kode:
* statiske analysatorer
* dynamiske analysatorer

Selvom de har et overlap i typen af problemer de 2 typer kan finde,
supplerer de hinanden.

De statiske analysatorer kigger source-koden igennem og laver en analyse
hvor der kan afsløres en række problemer.
F.eks. findes der værktøjer der kan undersøge om man overtræder nogle af
Scott Meyers regler fra "Effective C++" og "More Effective C++".
Eksempler på statiske analysatorer er Lint, værktøjer fra Progamming
Research og egentlig også warnings fra compilererne.

F.eks. beskriver artiklen
http://www.stanford.edu/~engler/p401-xie.pdf
en statisk analysator, der fandt i en række problemer i Linux.
Microsoft siger at de har anvendt nogle statiske analysatorer i forbindelse
med at forbedre kvaliteten i Windows Server 2003.

De dynamiske analysatorer holder øje med et program mens det kører.
De kan f.eks. de holde styr på hvilke hukommelsesblokke der er allokeret, om
man frigiver dem og om man bruger hukommelse man ikke allokeret.
Eksempler på dynamiske analysatorer er BoundsChekcer, Purify og Valgrind.

CodeGuard er en dynamisk analysator.
Egentlig er CodeGuard blot en compiler-option til Borland C++ compileren i
henholdsvis Professional og Enterprise udgaven.
Den indsætter noget ekstra kode f.eks. hver gang man bruger en pointer.
F.eks. vil den i forbindelse med "strcpy" undersøge om den buffer der skal
kopieres over i er allokeret og er stor nok.

Venlig hilsen

Mogens Hansen



Torben W. Hansen (06-05-2003)
Kommentar
Fra : Torben W. Hansen


Dato : 06-05-03 22:34

"Mogens Hansen" <mogens_h@dk-online.dk> skrev i en meddelelse
news:b9906k$stu$1@news.cybercity.dk...

> Destructorer hører til gruppen "Special member function".
> Destructoren ikke har et navn, men erklæres med en speciel syntax.
> Hvis man tager vtbl synvinklen, vil det gælde at en virtuel destructor har
> et fast index i den.
> F.eks. kunne index 0 i vtbl til "Basis" pege på "Basis:Basis", og så
ville
> index 0 i vtbl til "Afledt" på "Afledt:Afledt".
> Når man så skriver
> delete bptr;
> vil der blive lavet et opslag i vtbl og funktionen, der er angivet på
index
> 0 vil blive kaldt.

Så faldt det endelig på plads (tror jeg da).
Dvs. at selvom man kan referere til destructor navne, så konverteres disse
via den "specielle syntax" til en form for absolut CALL (statisk) eller -
for virtuelle metoders (destructor inklusive) vedkommende måske til -
indexeret CALL (dynamisk). Som du tidligere har forklaret er vtabel-udlægget
ens for basisklassen og den afledte klasses basisdel. Når et afledt objekt
instantieres gætter jeg på, at basisdelens constructor initialiserer vtabel
med adresserne på basisklassens virtuelle metoder inklusive destructoren, og
at den afledte dels constructor efterfølgende justerer adresserne i
vtabellen til at pege på metode-tilsidesættelserne inklusive destructor.

> Man kan også bare tage en mere abstrakt synvinkel og sige at
> * når destructoren er virtual, vil destructoren der hører til den
> dynamiske type blive kaldt
> * når destructoren ikke er virtual, vil destructoren der hører til den
> statiske type blive kaldt
> uden at bekymre sig om hvorfor og hvordan.

Det havde jeg faktisk også slået mig til tåls med, men bøgerne jeg har læst
(Kris Jamsa C++, C++ Grundbog, uddrag af Thinking in C++) kommer mere eller
mindre indpå "vtbl forklaringen", som egentlig også tiltaler mig, da den
afmystificerer "magien" bag polymorfi. Jeg er næsten færdig med "C++
Grundbog" og vil gerne være helt færdig med denne, før jeg går igang med
"Accelerated C++".

Tusind tak for hjælpen...

Med venlig hilsen
Torben W. Hansen



Søg
Reklame
Statistik
Spørgsmål : 177459
Tips : 31964
Nyheder : 719565
Indlæg : 6408193
Brugere : 218881

Månedens bedste
Årets bedste
Sidste års bedste