/ 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
delete [];
Fra : Bertel Brander


Dato : 20-06-04 21:22

Hej NG,

I C++ skal man bruge "delete [] x;" hvis man har
brugt "x = new Type [size];" og "delete x;" hvis man
har brugt "x = new Type;" (medmindre Type er et
typedef'ed array).

Det er (normalt) ikke noget større problem, men jeg
kunne godt tænke mig at kende begrundelsen, ikke blot
at det skal man fordi C++ standarden siger at man skal.

Jeg kan godt se at kompileren ikke behøver at gemme
antal elementer for et ikke-array, men er det hele
begrundelsen?

/b

 
 
Anders Bo Rasmussen (21-06-2004)
Kommentar
Fra : Anders Bo Rasmussen


Dato : 21-06-04 00:30

On Sun, 20 Jun 2004 22:21:33 +0200 Bertel Brander wrote:

> Hej NG,
>
> I C++ skal man bruge "delete [] x;" hvis man har
> brugt "x = new Type [size];" og "delete x;" hvis man
> har brugt "x = new Type;" (medmindre Type er et
> typedef'ed array).
>
> Det er (normalt) ikke noget større problem, men jeg
> kunne godt tænke mig at kende begrundelsen, ikke blot
> at det skal man fordi C++ standarden siger at man skal.
>
> Jeg kan godt se at kompileren ikke behøver at gemme
> antal elementer for et ikke-array, men er det hele
> begrundelsen?

Destruktoren kaldes vel 1 gang ved delete og size gange ved delete[]?

--
41 6E 64 65 72 73

Kasper Laudrup (21-06-2004)
Kommentar
Fra : Kasper Laudrup


Dato : 21-06-04 13:30

On Mon, 21 Jun 2004 01:30:15 +0200, Anders Bo Rasmussen wrote:

> On Sun, 20 Jun 2004 22:21:33 +0200 Bertel Brander wrote:
>
>> Hej NG,
>>
>> I C++ skal man bruge "delete [] x;" hvis man har
>> brugt "x = new Type [size];" og "delete x;" hvis man
>> har brugt "x = new Type;" (medmindre Type er et
>> typedef'ed array).
>>
>> Det er (normalt) ikke noget større problem, men jeg
>> kunne godt tænke mig at kende begrundelsen, ikke blot
>> at det skal man fordi C++ standarden siger at man skal.
>>
>> Jeg kan godt se at kompileren ikke behøver at gemme
>> antal elementer for et ikke-array, men er det hele
>> begrundelsen?
>
> Destruktoren kaldes vel 1 gang ved delete og size gange ved delete[]?

Lavede lige et lille test program og kan afsløre at du har fuldstændig
ret, eller det vil sige mit program segfaultede hvis jeg kalde delete på
et array, men kaldte destruktoren size antal gange ved delete[].

Mvh.
Kasper

--
"Copyright doesn't cover ideas; it's your expression of those ideas."
-- Richard Stallman


Bertel Brander (21-06-2004)
Kommentar
Fra : Bertel Brander


Dato : 21-06-04 18:40

Kasper Laudrup wrote:

>>Destruktoren kaldes vel 1 gang ved delete og size gange ved delete[]?
>
>
> Lavede lige et lille test program og kan afsløre at du har fuldstændig
> ret, eller det vil sige mit program segfaultede hvis jeg kalde delete på
> et array, men kaldte destruktoren size antal gange ved delete[].
>

Undskyld, jeg fik ikke stillet spørgsmålet klart nok.

Spørgsmålet var ikke hvad de to delete operatorer gør,
men hvorfor det er nødvendigt med to delete operatorer.

Jeg mener, det burde være muligt, uden den store
"performance penalty", at lave én delete operator
der klarer begge opgaver.

Derved ville man lette opgaven for programmøren og
man ville slippe for nogle fejl.

/b

Kent Friis (21-06-2004)
Kommentar
Fra : Kent Friis


Dato : 21-06-04 16:20

Den Sun, 20 Jun 2004 22:21:33 +0200 skrev Bertel Brander:
> Hej NG,
>
> I C++ skal man bruge "delete [] x;" hvis man har
> brugt "x = new Type [size];" og "delete x;" hvis man
> har brugt "x = new Type;" (medmindre Type er et
> typedef'ed array).
>
> Det er (normalt) ikke noget større problem, men jeg
> kunne godt tænke mig at kende begrundelsen, ikke blot
> at det skal man fordi C++ standarden siger at man skal.

For at gøre sproget symmetrisk?

new <-> delete
new[] <-> delete[]
open <-> close

Logisk, i modsætning til visse andre sprog der i nogle tilfælde kun har
new, og i andre tilfælde new <-> close.

f = new streamreader("hello.txt")
s = f.read()
f.close

Mvh
Kent
--
Help test this great MMORPG game - http://www.eternal-lands.com/

Mogens Hansen (21-06-2004)
Kommentar
Fra : Mogens Hansen


Dato : 21-06-04 19:09


"Bertel Brander" <bertel@post4.tele.dk> wrote:

[8<8<8<]
> Jeg kan godt se at kompileren ikke behøver at gemme
> antal elementer for et ikke-array, men er det hele
> begrundelsen?

Der er ikke nogen måde, hvor man afgøre for
void foo( T* t);
om t er allokeret med new eller new[] (eller noget andet), uden at
implementeringen gemmer ekstra information.
Det ville komplicere det almindelige tilfælde hvor et enkelt objekt
allokeres, hvis den ekstra information skulle gemmes.

Den mere præcise forklaring kan findes i
The Design and Evolution of C++
Bjarne Stroustrup
ISBN 0-201-54330-3
side 217-218

Bemærk iøvrigt også at malloc tilsvarende hører sammen med free (og ikke
delete eller delete [])

Venlig hilsen

Mogens Hansen



Bertel Brander (21-06-2004)
Kommentar
Fra : Bertel Brander


Dato : 21-06-04 19:51

Mogens Hansen wrote:

> "Bertel Brander" <bertel@post4.tele.dk> wrote:
>
> [8<8<8<]
>
>>Jeg kan godt se at kompileren ikke behøver at gemme
>>antal elementer for et ikke-array, men er det hele
>>begrundelsen?
>
>
> Der er ikke nogen måde, hvor man afgøre for
> void foo( T* t);
> om t er allokeret med new eller new[] (eller noget andet), uden at
> implementeringen gemmer ekstra information.

Det er just problemet.

> Det ville komplicere det almindelige tilfælde hvor et enkelt objekt
> allokeres, hvis den ekstra information skulle gemmes.

Spørgsmålet er så om ikke det ville være bedre at lave
denne lille komplicering af implementationen af kompileren,
for så at slippe for at komplicere opgaven for programmøren,
og slippe for at programmøren laver fejl.

Så vidt jeg ved er der nogen implementationer af delete der kan
tage højde for sådanne "user errors".

>
> Den mere præcise forklaring kan findes i
> The Design and Evolution of C++
> Bjarne Stroustrup
> ISBN 0-201-54330-3

Jeg bør vist få læst den bog...

/b

Mogens Hansen (21-06-2004)
Kommentar
Fra : Mogens Hansen


Dato : 21-06-04 20:45


"Bertel Brander" <bertel@post4.tele.dk> wrote_:

[8<8<8<]
> Spørgsmålet er så om ikke det ville være bedre at lave
> denne lille komplicering af implementationen af kompileren,

Ikke ubetinget - det må være et kompromis mellem hukommelsesforbrug og
bekvemmelighed.

C++ er jo i høj grad et "trust the programmer" sprog, som stiller krav til
programmøren med også giver fleksibilitet og stryke.

Det findes iøvrigt allerede (hvis man har brug for det og kan nøjes med
default constructoren):
new T[1];

> for så at slippe for at komplicere opgaven for programmøren,
> og slippe for at programmøren laver fejl.

Som oftest er man bedre tjent med at benytte std::vector - den gør
håndteringen af arrays med dynamisk størrelse meget simplere på mange måder.

> Så vidt jeg ved er der nogen implementationer af delete der kan
> tage højde for sådanne "user errors".

Givet at sproget er defineret som det er, så foretrækker jeg en
implementering der gør det modsatte - nemlig fortæller når man overtræder
reglen.
Så er man sikker på at man retter eventuelle fejl og koden dermed bliver
korrekt og portabel.
Et eksempel på en sådan implementering er Borland C++Builder med CodeGuard
slået til (under udvikling).

Venlig hilsen

Mogens Hansen



Ukendt (21-06-2004)
Kommentar
Fra : Ukendt


Dato : 21-06-04 20:52

"Bertel Brander" <bertel@post4.tele.dk> wrote in message
news:40d72db1$0$258$edfadb0f@dread12.news.tele.dk...
> Spørgsmålet er så om ikke det ville være bedre at lave
> denne lille komplicering af implementationen af kompileren,
> for så at slippe for at komplicere opgaven for programmøren,
> og slippe for at programmøren laver fejl.

Hvis en compiler skulle forbedres på dette punkt, så kunne det ske ved at
fange fejlen med run-time fejl i debug-builds (assert eller lignenede). Jeg
ville bestemt ikke betragte det som en forbedring, hvis den anstrengte sig
for at skjule fejl for mig.

mvh.
Martin



Niels Dybdahl (22-06-2004)
Kommentar
Fra : Niels Dybdahl


Dato : 22-06-04 15:04

> I C++ skal man bruge "delete [] x;" hvis man har
> brugt "x = new Type [size];" og "delete x;" hvis man
> har brugt "x = new Type;" (medmindre Type er et
> typedef'ed array).
>
> Det er (normalt) ikke noget større problem, men jeg
> kunne godt tænke mig at kende begrundelsen, ikke blot
> at det skal man fordi C++ standarden siger at man skal.
>
> Jeg kan godt se at kompileren ikke behøver at gemme
> antal elementer for et ikke-array, men er det hele
> begrundelsen?

Jeps. Hvis man har:

void f(char *p) {
delete p;
}

Så ved compileren ikke umiddelbart om char*p peger på noget der er allokeret
med new eller new[].
Ved new[] vil størrelsen typisk blive placeret umiddelbart før arrayet og
det vil typisk fylde 4 bytes.
Hvis man skulle gøre det samme med ting der blev allokeret med new, så ville
en new char fylde 5 bytes istedet for 1. Man skal selvfølgelig også gemme
pointeren for at kunne bruge det til noget, så i praksis ville man bruge 9
(eller 12 ved pæn alignment) bytes istedet for 5. Dvs størrelsen af nogle
datastrukturer ville øges med en faktor 2.4 og det ønsker man altså ikke i
C++.
Man kunne have valgt at have to forskellige semantikker til data allokeret
ved new og new[] og så bruge delete til begge, men så ville kompabiliteten
med C være røget.

Niels Dybdahl




Mogens Hansen (22-06-2004)
Kommentar
Fra : Mogens Hansen


Dato : 22-06-04 18:59


"Niels Dybdahl" <ndy@fjern.detteesko-graphics.com> wrote:

[8<8<8<]
> Man kunne have valgt at have to forskellige semantikker til data allokeret
> ved new og new[] og så bruge delete til begge, men så ville kompabiliteten
> med C være røget.

Hvilken indflydelse har det for kompatibiliteten med C ?
new, new[] og delete er ikke en del af C.

Der er ikke problemer med at bruge et char array allokeret med new [] i
forbindelse med C funktioner der forventer en "char*" (f.eks. strcpy) eller
en enkelt char allokeret med både new og new [1] i forbindelse med C
funktioner der forventer en "char" (f.eks. toupper *)

Venlig hilsen

Mogens Hansen

* toupper tager godt nok tager int - men det er en anden historie



Niels Dybdahl (25-06-2004)
Kommentar
Fra : Niels Dybdahl


Dato : 25-06-04 11:10

> > Man kunne have valgt at have to forskellige semantikker til data
allokeret
> > ved new og new[] og så bruge delete til begge, men så ville
kompabiliteten
> > med C være røget.
>
> Hvilken indflydelse har det for kompatibiliteten med C ?
> new, new[] og delete er ikke en del af C.

Hvis man f.eks havde vedtaget at data allokeret med new ikke kunne gemmes i
pointere men kun i referencer og at referencer ikke kunne laves om til
pointere, så kunne man have anvendt samme operator til hhv referencer og
pointere, men hvor operatoren på en reference svarede til delete og på en
pointer til delete[].
Men at man ikke kan lave en reference om til en pointer giver en del
problemer specielt i forbindelse med C. Man ville for eksempel ikke kunne
kalde en C funktion som forventer en pointer, hvis man kun har en reference
til objektet.

Niels Dybdahl



Mogens Hansen (25-06-2004)
Kommentar
Fra : Mogens Hansen


Dato : 25-06-04 14:57


"Niels Dybdahl" <ndy@fjern.detteesko-graphics.com> wrote in message
news:40dbfa10$0$172$edfadb0f@dtext02.news.tele.dk...
> > > Man kunne have valgt at have to forskellige semantikker til data
> allokeret
> > > ved new og new[] og så bruge delete til begge, men så ville
> kompabiliteten
> > > med C være røget.
> >
> > Hvilken indflydelse har det for kompatibiliteten med C ?
> > new, new[] og delete er ikke en del af C.
>
> Hvis man f.eks havde vedtaget at data allokeret med new ikke kunne gemmes
i
> pointere men kun i referencer og at referencer ikke kunne laves om til
> pointere, så kunne man have anvendt samme operator til hhv referencer og
> pointere, men hvor operatoren på en reference svarede til delete og på en
> pointer til delete[].

Jeg forstod Bertel Brander's oprindelige spørgsmål således at han undrede
sig over at C++ kræver at _programmøren_ skelner mellem delete af et array
og delete af et enkelt allokeret objekt, i stedet for at lade
_implementeringen_ skelne.

Jeg opfattede ikke at Bertel Brander var i tvivl om at man syntaktisk kunne
udtrykke denne skelnen på andre måder. Ligeledes opfattede jeg ikke at han
ville have en argumentation for den valgte syntaks frem for alternativer.

Det eneste jeg kan se at du skriver er at "hvis man havde defineret sproget
anderledes, så havde det været anderledes".
Det er naturligvis et indlysende rigtigt udsagn - men det gør ingen klogere.


> Men at man ikke kan lave en reference om til en pointer giver en del
> problemer specielt i forbindelse med C. Man ville for eksempel ikke kunne
> kalde en C funktion som forventer en pointer, hvis man kun har en
reference
> til objektet.

Undskyld, men jeg forstår ikke hvad du mener.

Snakker du om at man kan forestiller sig et programmeringssprog, der er
defineret således at man ikke kan konvertere en reference til en pointer,
eller mener du at det rent faktisk forholder sig sådan i C++ ?

I C++ kan man uden problemer konvertere frem og tilbage mellem en pointer,
der peger på et gyldigt objekt og en reference til et gyldigt objekt lige så
mange gange man har lyst til.

void foo(T& t)
{
T* ptr = &t;
T& ref = *ptr;
}

Der er intet problem i at stå med en reference til f.eks. en struktur i C++,
og konvertere den til en pointer der kan benyttes til at kalde en C
funktion.

Det er næppe en overraskelse for ret mange, at C++ _bevidst_ er defineret
således at der er en meget høj grad af kompatibilitet og interoperabilitet
med C.


Venlig hilsen

Mogens Hansen



Anders Borum (25-06-2004)
Kommentar
Fra : Anders Borum


Dato : 25-06-04 17:23

Mogens Hansen wrote:
[klip]
> Undskyld, men jeg forstår ikke hvad du mener.
>
> Snakker du om at man kan forestiller sig et programmeringssprog, der er
> defineret således at man ikke kan konvertere en reference til en pointer,
> eller mener du at det rent faktisk forholder sig sådan i C++ ?

Hej Mogens

Niels forestiller sig hvordan sproget ville have været, hvis referencer
ikke kunne konverteres til pointere. Det ses fx ved at han skriver:

>> Men at man ikke kan lave en reference om til en pointer giver en del
>> problemer specielt i forbindelse med C. Man ville for eksempel ikke
>> kunne kalde en C funktion som forventer en pointer, hvis man kun har
>> en reference til objektet.

Hilsen Anders

Mogens Hansen (26-06-2004)
Kommentar
Fra : Mogens Hansen


Dato : 26-06-04 16:03


"Anders Borum" <ander@diku.dk> wrote:

[8<8<8<]
> Niels forestiller sig hvordan sproget ville have været, hvis referencer
> ikke kunne konverteres til pointere. Det ses fx ved at han skriver:
>
> >> Men at man ikke kan lave en reference om til en pointer giver en del
> >> problemer specielt i forbindelse med C.

Hmm...
Ses det ?
Lad os bare sige det.

Skal Niels Dybdahl hypotetiske implementering forståes således at man kunne
have 2 forskellige logiske heaps - een til allokering af enkelt objekter og
een til allokering af objekt arrays, og man havde 2 forskellige "pointere"
(pointer *, reference &, handle ^ ...) til disse 2 heap. Ved hjælp af
overload af "delete" kunne skelne mellem disse 2 for at få frigivet rigtigt.
Som en del af hypotesen skulle det så gælde at der er vandtætte skoder
mellem disse 2 heaps, således at objekter allokeret på den ene heap ikke kan
bruges i sammenhænge hvor man forventer objekter allokeret på den anden
heap. Det således disse vandtætte skoder, der ville give problemer med
kompatibilitet med C.
Det lyder lidt analogt til hvad man ser i Microsoft Managed C++ og Microsoft
C++/CLI, hvor der er en skarp skelnen mellem "native heap" og "managed
heap".

Selv hvis det er argumentationen, er det vanskeligt at se hvordan det
bidrager med at svare på Bertel Brander's oprindelige spørgsmål og hvor det
bringer os hen.

Jeg har ikke set noget kildemateriale der blot antyder at der ligger sådanne
overvejelser til grund for skelnen mellem "delete" og "delete []".
Derimod har jeg, som jeg skrev ca. et døgn før Niels Dybdahl's indlæg, i
<citat>
The Design and Evolution of C++
Bjarne Stroustrup
ISBN 0-201-54330-3
side 217-218
</citat>
læst en beskrivelse fra designeren af sproget på hvorfor det forholder sig
som det gør.

Det kræver væsentlig autoritet (eller uvidenhed - det er lettere
tilgængeligt) at sige at den i bogen beskrevne forklaring ikke er den
rigtige i forhold til udviklingen og designovervejelerne om C++ - uanset om
man er enig i designvalget eller ej.

Som supplement kan man også læse
Inside the C++ Object Model
Stanley B. Lippman
ISBN 0-201-83454-5
side 218-224, der understøttet forklaringen i "The Design and Evolution of
C++".

Venlig hilsen

Mogens Hansen



Anders Borum (27-06-2004)
Kommentar
Fra : Anders Borum


Dato : 27-06-04 17:21

Mogens Hansen wrote:
[klip]
> Hmm...
> Ses det ?
> Lad os bare sige det.

Hej Mogens

Det ikke bare ses - det ses klart.

Alternativt må du tro, at Niels ikke aner at man kan kalde C funktioner
fra C++ programmer hvor man bruger referencer. Eftersom du vel har læst
hans andre indlæg i tråden, ville det være et mærkværdigt hul i hans viden.

> Skal Niels Dybdahl hypotetiske implementering forståes således at man kunne
> have 2 forskellige logiske heaps - een til allokering af enkelt objekter og
[klip]
Jeg tror Niels bedst selv forklarer og forsvarer sin hypotese. Men det
kan tænkes at han lufter en idé, selvom han ikke mener den er perfekt på
alle mulige punkter.

Anders

Igor V. Rafienko (22-06-2004)
Kommentar
Fra : Igor V. Rafienko


Dato : 22-06-04 20:36

[ Niels Dybdahl ]

[ ... ]

> Jeps. Hvis man har:
>
> void f(char *p) {
> delete p;
> }
>
> Så ved compileren ikke umiddelbart om char*p peger på noget der er
> allokeret med new eller new[].


Og hvorfor er det relevant?


> Ved new[] vil størrelsen typisk blive placeret umiddelbart før arrayet og
> det vil typisk fylde 4 bytes.


Ved en hvilken som helst allokering, må det finnes en datastruktur et
passende sted som tar vare på størrelsen til det objektet som
allokeres. Selv om man allokerer kun en char, så vil malloc/new ha en
datastruktur som registrerer denne allokeringen: det er ingen vei
utenom, med mindre man setter av et eget område å allokere objekter av
_en gitt størrelse_ i fra (noe man typisk vil gjøre, dersom man
trenger mange veldig små objekter).


> Hvis man skulle gøre det samme med ting der blev allokeret med new,
> så ville en new char fylde 5 bytes istedet for 1.


La oss se:

#include <stdlib.h>
#include <stdio.h>

int
main( int argc, char *argv[] )
{
size_t sizes[] = { 1, 2, 4, 8, 12, 16 };
size_t num_allocs = argc > 1 ? atoi(argv[1]) : 10;

for ( size_t i = 1; i != sizeof sizes / sizeof *sizes; ++i ) {
printf("Addresses of subsequent allocations of size %zu\n",
sizes[i]);
for ( size_t j = 0; j != num_allocs; ++j ) {
void *p = malloc(sizes[i]);
printf("p == %p; *(p-1) == %ul\n", p,
(unsigned long)(*((int*)p - 1)));
}
}

return 0;
}


En allokert char fyller faktisk 17 (16?) bytes på en implementasjon
jeg har adgang til (16 (15?) til administrasjon/alignment og 1 til
selve data). Ja, jeg er klar over at koden over bruker iallfall 1
ulovlig tricks, men det er ikke det jeg prøver å illustrere.


> Man skal selvfølgelig også gemme pointeren for at kunne bruge det
> til noget, så i praksis ville man bruge 9 (eller 12 ved pæn
> alignment) bytes istedet for 5. Dvs størrelsen af nogle
> datastrukturer ville øges med en faktor 2.4 og det ønsker man altså
> ikke i C++.


Men det er jo det som faktisk skjer i C++ (eksempelet over er stjålet
fra Scott Meyers). Og dette er også årsaken til at man har en mulighet
til å skrive sine egne minnealokeringsoperatorer, der det er aktuelt å
preallokere et digert område kun til objekter av en kjent størrelse,
for så dele plassen ut gradvis (beklager språkbruken).
Administrasjonen kan gjøre ved enkel pekersammenligning (dette er
spesielt aktuelt dersom datastrukturen er stor og lever omtrent like
lenge som programmet selv).


> Man kunne have valgt at have to forskellige semantikker til data
> allokeret ved new og new[] og så bruge delete til begge, men så
> ville kompabiliteten med C være røget.


Jeg ser fremdeles ikke hvor du vil hen med argumentet ditt. Hva
misforstår jeg?





ivr
--
<html><form><input type crash></form></html>

Niels Dybdahl (25-06-2004)
Kommentar
Fra : Niels Dybdahl


Dato : 25-06-04 11:19

> > Jeps. Hvis man har:
> >
> > void f(char *p) {
> > delete p;
> > }
> >
> > Så ved compileren ikke umiddelbart om char*p peger på noget der er
> > allokeret med new eller new[].
>
>
> Og hvorfor er det relevant?

Fordi nogle compilere gemmer ikke størrelsen på data allokeret med new. Den
størrelse er jo implicit givet ved typen.

> > Ved new[] vil størrelsen typisk blive placeret umiddelbart før arrayet
og
> > det vil typisk fylde 4 bytes.
>
>
> Ved en hvilken som helst allokering, må det finnes en datastruktur et
> passende sted som tar vare på størrelsen til det objektet som
> allokeres.

Ikke korrekt. Størrelsen er jo netop implicit givet ved new. Problemet er
ikke så stort ved allokering som ved deallokering. At holde styr på et
deallokeret område kræver en adresse og en størrelse, altså typisk 8 bytes.
Hvis man gerne vil placere dette i selve dataområdet, så vil runtimen bruge
mindst 8 bytes per allokering. Det kan dog implementeres på anden måde.

> > Hvis man skulle gøre det samme med ting der blev allokeret med new,
> > så ville en new char fylde 5 bytes istedet for 1.
>
> La oss se:
> ...
> En allokert char fyller faktisk 17 (16?) bytes på en implementasjon
> jeg har adgang til (16 (15?) til administrasjon/alignment og 1 til
> selve data). Ja, jeg er klar over at koden over bruker iallfall 1
> ulovlig tricks, men det er ikke det jeg prøver å illustrere.

Prøv på et system til embedded devices med meget lidt RAM og se om du får
samme resultat.

> > Man kunne have valgt at have to forskellige semantikker til data
> > allokeret ved new og new[] og så bruge delete til begge, men så
> > ville kompabiliteten med C være røget.
>
>
> Jeg ser fremdeles ikke hvor du vil hen med argumentet ditt. Hva
> misforstår jeg?

Se mit svar til Mogens Hansen.

Niels Dybdahl



Igor V. Rafienko (26-06-2004)
Kommentar
Fra : Igor V. Rafienko


Dato : 26-06-04 01:34

[ Niels Dybdahl ]

[ ... ]

> > > Så ved compileren ikke umiddelbart om char*p peger på noget der er
> > > allokeret med new eller new[].
> >
> > Og hvorfor er det relevant?
>
> Fordi nogle compilere gemmer ikke størrelsen på data allokeret med
> new. Den størrelse er jo implicit givet ved typen.


Er den det?

struct Base
{
virtual ~Base(){}
};

struct Derived : Base
{};

Derived * d = new Derived();
Base * b = d;
delete b;

Hvordan skal delete vite størrelsen på objektet uten at den står
lagret et passende sted (sannsynligvis i prototypeobjektet til Base og
Derived).

Jeg har dessverre ikke standarden for hånden for å kunne si noe om
hvorvidt noe slikt er tillatt. Dette er forutsetningen for
innsigelsen.

[ ... ]


> > Ved en hvilken som helst allokering, må det finnes en datastruktur
> > et passende sted som tar vare på størrelsen til det objektet som
> > allokeres.
>
> Ikke korrekt. Størrelsen er jo netop implicit givet ved new.


Greit nok, men det er vel ikke gitt at denne typeinformasjonen når
delete?


> Problemet er ikke så stort ved allokering som ved deallokering. At
> holde styr på et deallokeret område kræver en adresse og en
> størrelse, altså typisk 8 bytes. Hvis man gerne vil placere dette i
> selve dataområdet, så vil runtimen bruge mindst 8 bytes per
> allokering. Det kan dog implementeres på anden måde.


Adresse har man i pekeren selv. Hva størrelse angår, kunne man ha
sluppet å notere denne eksplisitt i spesialtilfeller, men jeg ser ikke
helt hvordan du vil at det skal virke i det generelle tilfellet.

[ ... ]


> > En allokert char fyller faktisk 17 (16?) bytes på en
> > implementasjon jeg har adgang til (16 (15?) til
> > administrasjon/alignment og 1 til selve data). Ja, jeg er klar
> > over at koden over bruker iallfall 1 ulovlig tricks, men det er
> > ikke det jeg prøver å illustrere.
>
> Prøv på et system til embedded devices med meget lidt RAM og se om
> du får samme resultat.


Har ikke adgang til slike. Hvordan ser resultatet der?

[ ... ]


> Se mit svar til Mogens Hansen.


Jeg er fortsatt ikke med.





ivr
--
<html><form><input type crash></form></html>

Mogens Hansen (26-06-2004)
Kommentar
Fra : Mogens Hansen


Dato : 26-06-04 16:03


"Igor V. Rafienko" <igorr@ifi.uio.no> wrote:
> [ Niels Dybdahl ]

[8<8<8<]
> > Fordi nogle compilere gemmer ikke størrelsen på data allokeret med
> > new. Den størrelse er jo implicit givet ved typen.

Hvilke C++ implementeringer tænker du har på ?
Fabrikat og version ?
Vær sikker på at du har checket den medfølgende heap-manager implementering
grundigt.

>
>
> Er den det?
>
> struct Base
> {
> virtual ~Base(){}
> };
>
> struct Derived : Base
> {};
>
> Derived * d = new Derived();
> Base * b = d;
> delete b;
>
> Hvordan skal delete vite størrelsen på objektet uten at den står
> lagret et passende sted (sannsynligvis i prototypeobjektet til Base og
> Derived).

Der sker 2 ting når "delete b;" udføres (ligesom ved "new").

1. destructoren bliver kørt.
Destructoren behøver ikke at kende størrelsen på objektet for at kunne
udføre sit arbejde.
Destructoren kender blot typen.

2. hukommelsesblokken bliver frigivet med "operator delete"
Den eneste information "operator delete" har at arbejde med er en
pointer-værdi som stammer fra et tidligere kald til "operator new". Der er
ingen størrelses eller type information.

Heap-manageren vil typisk have en datastruktur (linked list eller noget mere
avanceret) der beskriver hvilke blokke af hukommelsen der er ledige.
Datastrukturen vil typisk indeholde information om start adressen og
størrelsen på hver af disse blokke.
For at kunne indsætte information om en netop frigivet blok, identificeret
alene ved adressen, i denne datastruktur må heap-manageren på en eller anden
måde have gemt information om størrelsen på blokken.

Det er fuldstændigt som malloc/free i C.

[8<8<8<]
> > > Ved en hvilken som helst allokering, må det finnes en datastruktur
> > > et passende sted som tar vare på størrelsen til det objektet som
> > > allokeres.
> >
> > Ikke korrekt. Størrelsen er jo netop implicit givet ved new.

Heap manageren skal, som beskrevet ovenfor, bruge størrelsen igen ved
delete, for at kunne genbruge den frigivne hukommelsesblok.


[8<8<8<]
> > Prøv på et system til embedded devices med meget lidt RAM og se om
> > du får samme resultat.

Hvis systemet har dynamisk heap allokering er det nødt til at have de
nødvendige datastrukturer for at understøtte allokering og frigivelse af
hukommelse.
Den implementering kan naturligvis være optimeret efter forskellige
parametre, f.eks. minimal hukommelses overhead, mindst mulige kodestørrelse
eller hurtigst mulig allokering.

Man kan også lave en optimering der går på at man aldrig frigiver og kun
allokerer dynamisk under opstart hvor systemet konfigureres.
Der hvor jeg er stødt på sådan noget har det ikke været ud fra memory
overhead synspunkt men ud fra et sikkerhedssynpunkt: dangling pointere
undgås, dynamisk allokering under drift kan ikke fejle etc.

Hvis systemet er tilstrækkeligt lille og/eller statisk kan man fuldstændigt
fjerne heap-manageren og undlade at bruge new/delete og malloc/free.
Så er spørgsmålet om skelnen mellem "delete" og "delete []", samt størrelsen
på overhead ved allokering naturligvis et non-issue.

Det er de muligheder jeg er stødt på gennem tiden.

[8<8<8<]
> > Se mit svar til Mogens Hansen.
>
>
> Jeg er fortsatt ikke med.

Jeg er jeg stort set heller ikke
I særdeleshed har jeg vanskelligt ved at forstå hvordan Niels Dybdahl svarer
på Bertel Brander's oprindelige spørgsmål.


Venlig hilsen

Mogens Hansen



Niels Dybdahl (28-06-2004)
Kommentar
Fra : Niels Dybdahl


Dato : 28-06-04 10:21

> > > Fordi nogle compilere gemmer ikke størrelsen på data allokeret med
> > > new. Den størrelse er jo implicit givet ved typen.
>
> Hvilke C++ implementeringer tænker du har på ?
> Fabrikat og version ?

Borland havde en gang en heap, hvor de ikke gemte størrelsen på det
allokerede område ved simple allokeringer. Jeg kan ikke huske
versionsnummeret.

Niels Dybdahl



Igor V. Rafienko (29-06-2004)
Kommentar
Fra : Igor V. Rafienko


Dato : 29-06-04 03:26

[ Niels Dybdahl ]

[ ... ]

> Borland havde en gang en heap, hvor de ikke gemte størrelsen på det
> allokerede område ved simple allokeringer. Jeg kan ikke huske
> versionsnummeret.


Nå er jeg spent: hvordan virket denne koden da:

void
foo()
{
void * chunk = 0;

if ( <non-compile time deterministic event> )
chunk = new int(10);
else
chunk = new char(10);

char * delptr = static_cast< char* >( chunk );
delete delptr;
}

Dvs. hvordan vet delete hvor mange bytes skal frigjøres (Ja, jeg er
klar over at dette er 3. gang jeg nevner dette eksempelet, men hittil
har det ikke vært noen imvho klare svar på hvordan en implementasjon
skulle oppføre seg da).





ivr
--
<html><form><input type crash></form></html>

Niels Dybdahl (28-06-2004)
Kommentar
Fra : Niels Dybdahl


Dato : 28-06-04 10:29

> > Fordi nogle compilere gemmer ikke størrelsen på data allokeret med
> > new. Den størrelse er jo implicit givet ved typen.
>
>
> Er den det?
>
> struct Base
> {
> virtual ~Base(){}
> };
>
> struct Derived : Base
> {};
>
> Derived * d = new Derived();
> Base * b = d;
> delete b;
>
> Hvordan skal delete vite størrelsen på objektet uten at den står
> lagret et passende sted (sannsynligvis i prototypeobjektet til Base og
> Derived).

Ved objekter er det selvfølgelig implicit at størrelsen er variabel og
derfor må være gemt sammen med data. For simple typer (char, int etc) er
størrelsen fast. Det skulle jeg selvfølgelig have skrevet oprindeligt så
også du kan forstå tankegangen.

Men hele diskussionen er ret hypotetisk, da det kun er meget RAM-fattige
systemer som har behov for at man ikke gemmer størrelsen sammen med simple
typer. Og specielt der ville man normalt aldrig allokere en simpel type med
new, da pointeren så koster for meget.
Compilere som MSVC tillader da også at man kan bruge delete istedet for
delete[].
Jeg ville også ønske at man ved design af C++ ikke havde lavet den skelnen
mellem delete og delete[] fordi den er årsag til alt for mange fejl.

Niels Dybdahl



Anders Bo Rasmussen (28-06-2004)
Kommentar
Fra : Anders Bo Rasmussen


Dato : 28-06-04 11:44

On Mon, 28 Jun 2004 11:28:45 +0200 Niels Dybdahl wrote:

> Compilere som MSVC tillader da også at man kan bruge delete istedet for
> delete[].

Kalder den så destruktoren n gange eller 1 gang?

--
41 6E 64 65 72 73

Mogens Hansen (28-06-2004)
Kommentar
Fra : Mogens Hansen


Dato : 28-06-04 15:18

"Anders Bo Rasmussen" <fuzz01@spamfilter.dk> wrote:
> On Mon, 28 Jun 2004 11:28:45 +0200 Niels Dybdahl wrote:
>
> > Compilere som MSVC tillader da også at man kan bruge delete istedet
> > for delete[].
>
> Kalder den så destruktoren n gange eller 1 gang?

Prøv det - man skal ikke tro på alt hvad man læser


Venlig hilsen

Mogens Hansen



Anders Bo Rasmussen (28-06-2004)
Kommentar
Fra : Anders Bo Rasmussen


Dato : 28-06-04 18:42

On Mon, 28 Jun 2004 16:17:33 +0200 Mogens Hansen wrote:

>> > Compilere som MSVC tillader da også at man kan bruge delete istedet
>> > for delete[].
>>
>> Kalder den så destruktoren n gange eller 1 gang?
>
> Prøv det - man skal ikke tro på alt hvad man læser

Det har jeg lidt svært ved at gøre.

--
41 6E 64 65 72 73

Bertel Brander (28-06-2004)
Kommentar
Fra : Bertel Brander


Dato : 28-06-04 19:22

Anders Bo Rasmussen wrote:
> On Mon, 28 Jun 2004 16:17:33 +0200 Mogens Hansen wrote:
>
>
>>>>Compilere som MSVC tillader da også at man kan bruge delete istedet
>>>>for delete[].
>>>
>>>Kalder den så destruktoren n gange eller 1 gang?
>>
>>Prøv det - man skal ikke tro på alt hvad man læser
>
>
> Det har jeg lidt svært ved at gøre.
>

De fire C++ kompilere jeg har ved hånden kalder
destructoren én gang:

D:\Program\nph12>cat dd.cpp
#include <iostream>

class X
{
public:
~X()
{
std::cout << "Delete" << std::endl;
}
};

int main()
{
X *x = new X[2];

delete x;
}

D:\Program\nph12>dmc -Ig:\win32app\mars\stlport\stlport dd.cpp
link dd,,,user32+kernel32/noi;


D:\Program\nph12>dd
Delete

D:\Program\nph12>cl dd.cpp >null

D:\Program\nph12>dd
Delete

D:\Program\nph12>bcc32 dd.cpp
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
dd.cpp:
Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland

D:\Program\nph12>dd
Delete

D:\Program\nph12>g++ dd.cpp -o dd.exe

D:\Program\nph12>dd
Delete

/b

Mogens Hansen (28-06-2004)
Kommentar
Fra : Mogens Hansen


Dato : 28-06-04 21:20

Anders Bo Rasmussen wrote:
> On Mon, 28 Jun 2004 16:17:33 +0200 Mogens Hansen wrote:
>
>
>>>>Compilere som MSVC tillader da også at man kan bruge delete istedet
>>>>for delete[].
>>>
>>>Kalder den så destruktoren n gange eller 1 gang?
>>
>>Prøv det - man skal ikke tro på alt hvad man læser
>
>
> Det har jeg lidt svært ved at gøre.
>

Microsoft Visual C++ .NET 2003 (og V6.0) gør ikke een veldefineret ting.
Det afhænger af omstændighederne - f.eks. er det en POD type ?

Kort sagt lad være med at prøve, for det er "undefined behaviour", og
sådan opfører det sig også.
Lad være, selv om der findes særlige kombinationer af omstændigheder,
(kode, compiler version, compiler options etc.), hvor man ikke med
sikkerhed kan konstatere at det går galt.
Lad være selv om nogen mener at vide at compileren tillader det.

Som jeg viste i et andet indlæg, kalder den højest destructoren een gang
hvis det er et array af banale objekter (ikke POD), hvorefter der kommer
fejl dialogbokse eller "Unhandled exception".



Venlig hilsen

Mogens Hansen

Mogens Hansen (28-06-2004)
Kommentar
Fra : Mogens Hansen


Dato : 28-06-04 15:17

"Niels Dybdahl" <ndy@fjern.detteesko-graphics.com> wrote:

[8<8<8<]
> Compilere som MSVC tillader da også at man kan bruge delete istedet
> for delete[].

Hvilken version (og hvilken definition af "tillader") ?
Det er ikke umiddelbart hvad jeg ser med V6 og V7.1, hvis man med "tillade"
forstår at det udover at oversætte (hvilket det _skal_ ifølge sprog
specifikationen) også fører til eksekvering svarende til at have skrevet
"delete []".

Med Microsoft Visual C++ .NET 2003 (V7.1) giver følgende program:


#include <iostream>

using namespace std;

class base
{
public:
base();
virtual ~base();

private:
base(const base&);
base& operator=(const base&);
};

base::base()
{
cout << "base::base( this:" << this << ")" << endl;
}

base:base()
{
cout << "base:base(this:" << this << ")" << endl;
}

int main()
{
delete new base[10];
}


i debug build følgede fejl-dialog:

---------------------------
Microsoft Visual C++ Debug Library
---------------------------
Debug Assertion Failed!

Program: F:\cpp\fnyt\Debug\fnyt.exe
File: dbgdel.cpp
Line: 52

Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)

For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.

(Press Retry to debug the application)
---------------------------
Abort Retry Ignore
---------------------------


Noget tilsvarende ser jeg med Microsoft Visual C++ V6.0 SP4


I release giver det følgende output:
<output>
base::base( this:00371EDC)
base::base( this:00371EE0)
base::base( this:00371EE4)
base::base( this:00371EE8)
base::base( this:00371EEC)
base::base( this:00371EF0)
base::base( this:00371EF4)
base::base( this:00371EF8)
base::base( this:00371EFC)
base::base( this:00371F00)
base:base(this:00371EDC)
</output>

og hvis man kører det fra ide'et bliver det suppleret med "Unhandled
exception at 0x77f75a58 in fnyt.exe: User breakpoint."

Bemærk kun een destructor bliver kaldt.



En anden "sjov" måde at lave "undefined behaviour" i forbindelse med
frigivelse af arrays er ved at benytte en peger til en basis-klasse (C++
Standard §5.3.5 (og §16.2)):

#include

using namespace std;

class base
{
public:
base();
virtual ~base();

private:
base(const base&);
base& operator=(const base&);
};

class derived : public base
{
public:
derived();
virtual ~derived();

private:
char take_up_some_space[1024];

private:
derived(const derived&);
derived& operator=(const derived&);
};

base::base()
{
cout << "base::base( this:" << this << ")" << endl;
}

base:base()
{
cout << "base:base(this:" << this << ")" << endl;
}

derived::derived()
{
cout << "derived::derived( this:" << this << ")" << endl;
}

derived:derived()
{
cout << "derived:derived(this:" << this << ")" << endl;
}


int main()
{
base* b = new derived[10];
delete [] b; // undefined behaviour
}


Med Microsoft Visual C++ .NET 2003 giver det et fornuftigt resultat


base::base( this:00373C34)
derived::derived( this:00373C34)
base::base( this:00374038)
derived::derived( this:00374038)
base::base( this:0037443C)
derived::derived( this:0037443C)
base::base( this:00374840)
derived::derived( this:00374840)
base::base( this:00374C44)
derived::derived( this:00374C44)
base::base( this:00375048)
derived::derived( this:00375048)
base::base( this:0037544C)
derived::derived( this:0037544C)
base::base( this:00375850)
derived::derived( this:00375850)
base::base( this:00375C54)
derived::derived( this:00375C54)
base::base( this:00376058)
derived::derived( this:00376058)
derived:derived(this:00376058)
base:base(this:00376058)
derived:derived(this:00375C54)
base:base(this:00375C54)
derived:derived(this:00375850)
base:base(this:00375850)
derived:derived(this:0037544C)
base:base(this:0037544C)
derived:derived(this:00375048)
base:base(this:00375048)
derived:derived(this:00374C44)
base:base(this:00374C44)
derived:derived(this:00374840)
base:base(this:00374840)
derived:derived(this:0037443C)
base:base(this:0037443C)
derived:derived(this:00374038)
base:base(this:00374038)
derived:derived(this:00373C34)
base:base(this:00373C34)


Hvorimod Intel C++ V8.0 for MS-Windows og Borland C++Builder V6.0 Patch 4
giver et underligt, men fuldt lovligt, resultat

base::base( this:00373C34)
derived::derived( this:00373C34)
base::base( this:00374038)
derived::derived( this:00374038)
base::base( this:0037443C)
derived::derived( this:0037443C)
base::base( this:00374840)
derived::derived( this:00374840)
base::base( this:00374C44)
derived::derived( this:00374C44)
base::base( this:00375048)
derived::derived( this:00375048)
base::base( this:0037544C)
derived::derived( this:0037544C)
base::base( this:00375850)
derived::derived( this:00375850)
base::base( this:00375C54)
derived::derived( this:00375C54)
base::base( this:00376058)
derived::derived( this:00376058)
base:base(this:00373C58)
base:base(this:00373C54)
base:base(this:00373C50)
base:base(this:00373C4C)
base:base(this:00373C48)
base:base(this:00373C44)
base:base(this:00373C40)
base:base(this:00373C3C)
base:base(this:00373C38)
base:base(this:00373C34)



base::base( this:10054020)
derived::derived( this:10054020)
base::base( this:10055048)
derived::derived( this:10055048)
base::base( this:10056076)
derived::derived( this:10056076)
base::base( this:10057104)
derived::derived( this:10057104)
base::base( this:10058132)
derived::derived( this:10058132)
base::base( this:10059160)
derived::derived( this:10059160)
base::base( this:10060188)
derived::derived( this:10060188)
base::base( this:10061216)
derived::derived( this:10061216)
base::base( this:10062244)
derived::derived( this:10062244)
base::base( this:10063272)
derived::derived( this:10063272)
base:base(this:10054056)
base:base(this:10054052)
base:base(this:10054048)
base:base(this:10054044)
base:base(this:10054040)
base:base(this:10054036)
base:base(this:10054032)
base:base(this:10054028)
base:base(this:10054024)
base:base(this:10054020)


Bemærk at derived destructor ikke bliver kaldt selvom den er virtuel, og for
Borland C++Builder er pointerværdierne (forklarligt) helt hen i skoven


Min konklussion:
Brug std::vector i stedet for manuelt at allokere og frigive arrays af
objekter, hvis det overhovedet er muligt. Det er langt simplere, sikrere og
stadig meget effektivt.
Hvis det ikke er muligt så _kend_ reglerne og lad være med at basere sig på
at en eller anden "tilfældig" compiler måske gør hvad man ønskede - de kan
ændre det i næste version uden man er opmærksom på det.

Venlig hilsen

Mogens Hansen



Igor V. Rafienko (28-06-2004)
Kommentar
Fra : Igor V. Rafienko


Dato : 28-06-04 17:21

[ Niels Dybdahl ]

[ ... ]

> Ved objekter er det selvfølgelig implicit at størrelsen er variabel
> og derfor må være gemt sammen med data. For simple typer (char, int
> etc) er størrelsen fast.


Spørsmålet er -- når denne typeinformasjonen operator delete? Jeg har
dessverre ikke C++standarden med meg, men er denne konstruksjonen
eksplisitt forbudt:

int * p = new int(3);
char * q = static_cast< char* >( p );
delete q;

Hvis ikke, har operator delete ingen direkte mulighet til å vite om
typen til objektet og det blir litt vanskelig å frigi et visst antall
bytes uten at det antallet følger med pekeren på en passende måte
(jada, man kan ha separate allokeringsområder for hver eneste
basaltype, slik at en enkel pekersammenligning kan fortelle om typen,
men hvilke implementasjoner gjør det per i dag?).

[ ... ]





ivr
--
<html><form><input type crash></form></html>

Mogens Hansen (28-06-2004)
Kommentar
Fra : Mogens Hansen


Dato : 28-06-04 17:44


"Igor V. Rafienko" <igorr@ifi.uio.no> wrote:

[8<8<8<]
> Spørsmålet er -- når denne typeinformasjonen operator delete?

Nej.
Operator delete findes i 4 free-standing varianter
void operator delete[](void* ptr) throw();
void operator delete[](void* ptr, const std::nothrow_t&) throw();
void operator delete(void* ptr) throw();
void operator delete(void* ptr, const std::nothrow_t&) throw();

Og selv ikke hvis den er overskrevet i en klasse hjælper ikke, for det kan
være et specialiseret objekt man er ved at frigive.

Det der når "operator delete" har _ingen_ type.
Forinden er destructoren nemlig kaldt, og destructoren har netop til opgave
at konvertere et objekt til "raw memory". "operator delete" giver så den
"raw memory" tilbage til run-time systemet.

> Jeg har
> dessverre ikke C++standarden med meg

Jeg har kontrolleret det hurtigt.

Venlig hilsen

Mogens Hansen



Søg
Reklame
Statistik
Spørgsmål : 177580
Tips : 31968
Nyheder : 719565
Indlæg : 6409076
Brugere : 218888

Månedens bedste
Årets bedste
Sidste års bedste