Placeringssyntax
I programmeringsspråket C++ tillåter placeringssyntax " programmerare att explicit specificera minneshanteringen för individuella objekt - dvs deras "placering i minnet . Normalt, när ett objekt skapas dynamiskt, anropas en allokeringsfunktion på ett sådant sätt att den både allokerar minne för objektet och initialiserar objektet inom det nyligen allokerade minnet. Placeringssyntaxen tillåter programmeraren att tillhandahålla ytterligare argument till allokeringsfunktionen. En vanlig användning är att tillhandahålla en pekare till ett lämpligt lagringsområde där objektet kan initieras, och på så sätt separera minnesallokering från objektkonstruktion. [ citat behövs ]
"placering"-versionerna av de nya
och ta bort
operatorerna och funktionerna är kända som placering ny
och placering radera
. Ett nytt
uttryck , placering eller annat, anropar en ny
funktion , även känd som en allokeringsfunktion, vars namn är operator new
. På liknande sätt anropar ett delete-
uttryck en delete
-funktion , även känd som en deallocator-funktion, vars namn är operator delete
.
Varje nytt
uttryck som använder placeringssyntaxen är ett nytt placeringsuttryck
, och alla operatorer new
eller operator delete
-funktioner som tar mer än den obligatoriska första parametern ( std :: size_t
) är en placeringsny- eller placeringsborttagningsfunktion. En ny placeringsfunktion tar två indataparametrar: std :: size_t
och void *
.
Historia
I tidigare versioner av C++ fanns det inget som hette ny placering ; istället använde utvecklare explicit tilldelning till detta inom konstruktörer för att uppnå liknande effekt. Denna praxis har avskaffats och avskaffats senare, och den tredje upplagan av The C++ Programming Language nämner inte denna teknik.
Uttryck
Standard C++-syntaxen för ett nytt
uttryck utan placering är
new new-type-id ( optional-initializer-expression-list )
Placeringssyntaxen lägger till en uttryckslista direkt efter det nya
nyckelordet. Denna uttryckslista är placeringen. Den kan innehålla valfritt antal uttryck.
new ( expression-list ) new-type-id ( optional-initializer-expression-list )
Funktioner
Placeringens nya funktioner är överbelastningar av de nya funktionerna som inte placeras. Deklarationen av de nya funktionerna som inte är placerade, för icke-array- respektive array- nya
uttryck, är:
void * operator new ( std :: size_t ) throw ( std :: bad_alloc ); void * operator new []( std :: size_t ) throw ( std :: bad_alloc );
Standard C++-biblioteket tillhandahåller två placeringsöverbelastningar vardera för dessa funktioner. Deras förklaringar är:
void * operator new ( std :: size_t , const std :: nothrow_t & ) throw (); void * operator new ( std :: size_t , void * ) throw (); void * operator ny []( std :: size_t , const std :: nothrow_t & ) throw (); void * operator new []( std :: size_t , void * ) throw ();
I alla överbelastningar är den första parametern till operatörens nya
funktion av typen std :: size_t
, som när funktionen anropas kommer att skickas som ett argument som anger mängden minne, i byte, som ska allokeras. Alla funktioner måste returnera typen void *
, som är en pekare till lagringen som funktionen allokerar.
Det finns också funktioner för borttagning av placering. De är överbelastade versioner av borttagningsfunktionerna för icke-placering. De icke-placerade raderingsfunktionerna deklareras som:
void operator radera ( void * ) throw (); void operator radera []( void * ) throw ();
Standard C++-biblioteket tillhandahåller två placeringsöverbelastningar vardera för dessa funktioner. Deras förklaringar är:
void operator delete ( void * , const std :: nothrow_t & ) throw (); void operator delete ( void * , void * ) throw (); void operator delete []( void * , const std :: nothrow_t & ) throw (); void operator radera []( void * , void * ) throw ();
I alla överbelastningar är den första parametern till operatörens raderingsfunktion
av typen void *
, vilket är adressen till minnet som ska avallokeras.
För både de nya och raderingsfunktionerna är funktionerna globala, finns inte i något namnområde och har inte statisk länkning.
Använda sig av
Placeringssyntax har fyra huvudsakliga användningsområden: standardplacering, förhindrande av undantag, anpassade allokatorer och felsökning.
Standardplacering
Placeringsöverbelastningen av operatör ny
och operatörsborttagning
som använder en extra void *
-parameter används för standardplacering, även känd som pekarplacering . Deras definitioner av Standard C++-biblioteket, som det inte är tillåtet för ett C++-program att ersätta eller åsidosätta, är:
void * operator new ( std :: size_t , void * p ) throw () { return p ; } void * operator ny []( std :: size_t , void * p ) throw () { return p ; } void operator delete ( void * , void * ) throw () { } void operator delete []( void * , void * ) throw () { }
Det finns olika användningsområden för standardplacering.
Bjarne Stroustrup observerade ursprungligen, i sin bok The Design and Evolution of C++ , att ny pekareplacering är nödvändig för hårdvara som förväntar sig ett visst objekt på en specifik hårdvaruadress. Det krävs också för konstruktion av objekt som behöver ligga i ett visst minnesområde, till exempel ett område som delas mellan flera processorer i en multiprocessordator.
Andra användningar inkluderar dock att anropa en konstruktor direkt, något som C++-språket annars inte tillåter.
C++-språket tillåter ett program att anropa en destruktor direkt, och eftersom det inte är möjligt att förstöra objektet med ett delete
-uttryck, är det så man förstör ett objekt som konstruerats via ett nytt uttryck för pekarplacering. Till exempel:
p ->~ T ();
Användningsfall
Placering ny används när du inte vill att operator new ska allokera minne (du har förallokerat det och du vill placera objektet där), men du vill att objektet ska konstrueras. Exempel på typiska situationer där detta kan krävas är:
- Du vill skapa objekt i minnet som delas mellan två olika processer.
- Du vill att objekt ska skapas i ett minne som inte kan sökas.
- Du vill separera minnesallokering från konstruktion t.ex. när du implementerar en
std::vector<>
(sestd::vector<>::reserve
).
Grundproblemet är att konstruktorn är en speciell funktion; när den startar finns det inget objekt, bara råminne. Och när det är klart har du ett helt initialiserat objekt. Därför kan i) Konstruktorn inte anropas på ett objekt ii) Den behöver dock komma åt (och initiera) icke-statiska medlemmar. Detta gör att det blir ett fel att anropa konstruktorn direkt. Lösningen är placeringsformen av operatör ny.
Denna operatör är implementerad som:
void * operator new ( std :: size_t count , void * here ) { return here ; } void * operator ny []( std :: size_t count , void * here ) { return here ; }
Förhindra undantag
Normalt ger de (icke-placerade) nya funktionerna ett undantag, av typen std::bad_alloc
, om de stöter på ett fel, såsom uttömning av allt tillgängligt minne. Det var inte så funktionerna definierades av Stroustrups Annotated C++ Reference Manual , utan var en förändring som gjordes av standardiseringskommittén när C++-språket standardiserades. Det ursprungliga beteendet för funktionerna, vilket var att returnera en NULL-
pekare när ett fel inträffade, är tillgängligt via placeringssyntax.
Programmerare som vill göra detta i sina program måste inkludera standard C++-bibliotekshuvudet <new>
i källkoden. Den här rubriken deklarerar det globala std::nothrow
-objektet, som är av typen std::nothrow_t
(deklareras också i rubriken), som används för att anropa de överbelastade nya funktionerna som deklareras som att ta const std :: nothrow_t &
som deras andra parameter. Till exempel:
0
#include <new> struct T {}; int main () { // Anrop funktionsoperatorn new(std::size_t, const std::nothrow_t &) och (om framgångsrikt) konstruera objektet. T * p = new ( std :: nothrow ) T ; if ( p ) { // Lagringen har allokerats och konstruktorn anropats. ta bort p ; } annat ; // Ett fel har uppstått. Inget lager har tilldelats och inget objekt konstruerats. återvända ; }
Anpassade fördelare
Placeringssyntax används också för anpassade allokatorer . Detta använder inte någon av allokerings- och deallokeringsfunktionerna från standard C++-bibliotekshuvudet <new>
, men kräver att programmerare skriver sina egna allokerings- och avallokeringsfunktioner, överbelastade för användardefinierade typer. Till exempel kan man definiera en minneshanteringsklass enligt följande:
#include <cstdlib> klass A { public : void * allocate ( std :: size_t ); void deallocate ( void * ); };
Och definiera anpassad placeringstilldelning och avallokeringsfunktioner enligt följande:
void * operator new ( std :: size_t size , A & arena ) { return arena . allokera ( storlek ); } void operator delete ( void * p , A & arena ) { arena . deallokera ( p ); }
Programmet skulle använda placeringssyntaxen för att allokera objekt med olika instanser av A
-klassen enligt följande:
En första_arena , andra_arena ; T * p1 = ny ( första_arena ) T ; T * p2 = ny ( second_arena ) T ;
Att förstöra ett föremål vars förvaring är tilldelad på ett sådant sätt kräver viss försiktighet. Eftersom det inte finns något uttryck för borttagning av placering kan man inte använda det för att anropa den anpassade deallokatorn. Man måste antingen skriva en destruktionsfunktion som anropar den anpassade deallokatorn, eller anropa placeringsraderingsfunktionen direkt, som ett funktionsanrop.
Den förra skulle likna:
void förstöra ( T * p , A & arena ) { p ->~ T (); // Anropa först förstöraren explicit. arena . deallokera ( p ); // Anropa sedan deallokeringsfunktionen direkt. }
som skulle anropas från ett program som:
En arena ; T * p = ny ( arena ) T ; /* ... */ förstöra ( p , arena );
Det senare skulle innebära att helt enkelt skriva förstöraranropet och ta bort funktionsanrop i programmet:
En arena ; T * p = ny ( arena ) T ; /* ... */ p ->~ T (); // Anropa först förstöraren explicit. operatör radera ( p , arena ); // Anropa sedan deallokeringsfunktionen indirekt via operator delete(void*, A &).
Ett vanligt fel är att försöka använda ett delete-uttryck för att ta bort objektet. Detta resulterar i att fel operatörsraderingsfunktion
anropas. Dewhurst rekommenderar två strategier för att undvika detta fel. Den första är att säkerställa att alla anpassade allokatorer förlitar sig på Standard C++-bibliotekets globala, icke-placerade, nya operatör
, och är således inget annat än enkla omslag runt C++-bibliotekets minneshantering. Det andra är att skapa nya och ta bort funktioner för enskilda klasser och anpassa minneshantering via klassfunktionsmedlemmar snarare än genom att använda placeringssyntaxen.
Felsökning
Ny placering kan också användas som ett enkelt felsökningsverktyg för att göra det möjligt för program att skriva ut filnamnet och radnumret för källkoden där en minnesallokering har misslyckats. Detta kräver inte inkludering av standard C++-bibliotekshuvudet <new>
, men kräver inkludering av en rubrik som deklarerar fyra placeringsfunktioner och en makroersättning för det nya
nyckelordet som används i nya uttryck. Till exempel skulle en sådan rubrik innehålla:
#if defined(DEBUG_NEW) void * operator new ( std :: size_t size , const char * file , int line ); void * operator new []( std :: size_t size , const char * file , int line ); void operator delete ( void * p , const char * file , int line ); void operator delete []( void * p , const char * file , int line ); #define New new(__FILE__, __LINE__) #else #define New new #endif
Detta skulle användas i ett program enligt följande:
T * p = Nytt T ;
Den specialskrivna placeringens nya funktioner skulle sedan hantera med hjälp av den medföljande fil- och radnummerinformationen i händelse av ett undantag. Till exempel:
#include <new> #include <cstdlib> class NewError { public : NewError ( const char * file , int line ) { /* ... */ } /* ... */ } ; void * operator new ( std :: size_t size , const char * file , int line ) { if ( void * p = :: operator new ( size , std :: nothrow )) return p ; kasta NewError ( fil , rad ); }
Ta bort placering
Som nämnts ovan finns det inget placeringsborttagningsuttryck. Det är inte möjligt att anropa någon placeringsoperator delete
-funktion med hjälp av ett delete-
uttryck.
Placeringsborttagningsfunktionerna anropas från placeringens nya
uttryck. I synnerhet anropas de om konstruktören av objektet ger ett undantag. I en sådan omständighet, för att säkerställa att programmet inte orsakar en minnesläcka , anropas placeringens raderingsfunktioner. Ett nytt uttryck för placering anropar först placeringsoperatorn ny
funktion och anropar sedan objektets konstruktor vid råminnet som returneras från allokeringsfunktionen. Om konstruktören kastar ett undantag är det nödvändigt att deallokera den lagringen innan undantaget sprids tillbaka till koden som exekverade placeringens nya uttryck, och det är syftet med placeringens raderingsfunktioner.
Placeringsborttagningsfunktionen som anropas matchar den nya placeringsfunktionen som anropades av det nya uttrycket för placeringen. Så, till exempel, om följande kod exekveras, kommer placeringsborttagningsfunktionen som anropas att vara operator delete(void *, const A &)
:
0
#include <cstdlib> #include <iostream> struktur A {}; struktur E {}; klass T { public : T () { kast E (); } }; void * operator new ( std :: size_t , const A & ) { std :: cout << "Placering ny anropad." << std :: endl ; } void operator delete ( void * , const A & ) { std :: cout << "Placeringsradering anropad." << std :: endl ; } int main (){ A a ; försök { T * p = ny ( a ) T ; } catch ( E exp ) { std :: cout << "Undantag fångat." << std :: endl ; } returnera ; }
Det är därför som borttagningsfunktionerna för pekarplacering definieras som inga operationer av Standard C++-biblioteket. Eftersom pekarplaceringen nya funktioner inte allokerar någon lagring, finns det ingen lagring som ska deallokeras i händelse av att objektets konstruktör kastar ett undantag.
Om det inte finns någon matchande borttagningsfunktion för placering, anropas ingen avallokeringsfunktion i händelse av att ett undantag kastas av en konstruktor i ett nytt
placeringsuttryck. Det finns också några (äldre) C++-implementeringar som inte alls stöder borttagning av placering (som, liksom de undantagsgivande allokeringsfunktionerna, var ett tillägg som gjordes till C++ när det standardiserades) alls. I båda sådana situationer kommer ett undantag som kastas av en konstruktör vid allokering med en anpassad allokator att resultera i en minnesläcka. (I fallet med de äldre C++-implementeringarna kommer en minnesläcka också att inträffa med nya
uttryck som inte är placerade .)
säkerhet
Placering av nya uttryck är sårbara för säkerhetsmissbruk. Under 2011 demonstrerade Kundu och Bertino några av bedrifterna med att placera nya. Några av attackerna är buffertspillsattacker, objektspill, selektiv överstyrning av stackguard, virtuell pekare, attacker med minnesfel. 2015 släppte GCC en patch baserad på resultaten i.
Anteckningar
- Anderson, Gail (1998a). "Objektlagringshantering". Navigera i C++ och objektorienterad design . Prentice Hall. ISBN 9780135327487 .
- Anderson, Gail (1998b). "Undantagshantering". Navigera i C++ och objektorienterad design . Prentice Hall. ISBN 9780135327487 .
- Buck, Joe (1997-05-12). "3.4. g++ accepterar inte placeringen ny syntax" . Vanliga frågor om GNU C++-kompilatorn . Hämtad 2008-11-26 .
- Dewhurst, Stephen C. (2003). "Gotcha #62: Ersätter globalt nytt och ta bort" . C++ Gotchas . Addison-Wesley . ISBN 978-0-321-12518-7 .
- Lischner, Ray (2003). C++ i ett nötskal . O'Reilly. ISBN 9780596002985 .
- Lippman, Stanley B. (1997). C++ ädelstenar . Cambridge University Press. ISBN 9780135705810 .
- Loudon, Kyle (2003). C++ Pocket Reference . O'Reilly. ISBN 9780596004965 .
- "Placering ny/ta bort" . C++ Språk och bibliotek . Glen McCluskey & Associates LLC. 2000-06-26. Arkiverad från originalet 2006-04-18 . Hämtad 2008-11-26 .
- Meyers, Scott (1998-04-01). "Placering ny och placering radera" . Dr Dobb's Journal . United Business Media LLC.
- Seed, Graham M.; Cooper, Barry J. (2001). En introduktion till objektorienterad programmering i C++ (andra upplagan). Springer. ISBN 1-85233-450-9 .
- Solter, Nicholas; Kleper, Scott (2005). Professionell C++ . Wiley. ISBN 9780764574849 .
- Stroustrup, Bjarne (juli 1991). Programmeringsspråket C++ (2:a upplagan). ISBN 978-0-201-53992-9 .
- Stroustrup, Bjarne (1994). "Minneshantering". Design och utveckling av C++ . Addison-Wesley . ISBN 978-0-201-54330-8 .
- Stroustrup, Bjarne (1997). Programmeringsspråket C++ (3:e upplagan). Addison-Wesley . ISBN 978-0-201-88954-3 .
- Vermeir, Dirk (2001). Multiparadigmprogrammering med C++ . Springer. ISBN 9781852334833 .
- Yongwei, Wu (2007-12-31). "En minnesläckagedetektor för flera plattformar" . Wu Yongweis programmeringssida . Hämtad 2008-11-26 .
Vidare läsning
- Franek, Frantisek (2004). Minne som programmeringskoncept i C och C++ . Cambridge University Press . ISBN 978-0-521-52043-0 .
- Cline, Marshall (2006-09-25). "11.10: Vad är "placering new" och varför skulle jag använda det?" . C++ FAQ Lite . Hämtad 2008-11-26 .
- "C++ ny operatör" . IBM:s Mac OS X-kompilatorer . IBM . 2003 . Hämtad 2008-11-27 .
- "Föraren ny funktion" . MSDN . Microsoft . Hämtad 2008-11-27 .
- "ny operatör (C++)" . MSDN . Microsoft . Hämtad 2008-11-27 .