C++
Zarządzanie pamięcią
Szukaj…
Składnia
- : :( opt ) new ( lista-wyrażeń ) ( opt ) new-type-id new-initializer ( opt )
- : :( opt ) new ( lista-wyrażeń ) ( opt ) ( type-id ) new-initializer ( opt )
- : :( opt ) usuń wyrażenie cast
- : :( opt ) delete [] cast-expression
- std :: unikalny_ptr < identyfikator-typu > nazwa_wariatu (nowy identyfikator-typu ( opt )); // C ++ 11
- std :: shared_ptr < typ-id > nazwa_wariatu (nowy typ-id ( opt )); // C ++ 11
- std :: shared_ptr < typ-id > nazwa_wariatu = std :: make_shared < typ-id > ( opt ); // C ++ 11
- std :: unikalny_ptr < typ-id > nazwa_wariatu = std :: make_unique < typ-id > ( opt ); // C ++ 14
Uwagi
Wiodące ::
zmusza operatora nowego lub delete do wyszukiwania w zakresie globalnym, zastępując przeciążone specyficzne dla klasy nowe lub operatora usuwania.
Opcjonalne argumenty występujące po new
słowie kluczowym są zwykle używane do wywołania umieszczenia nowego , ale mogą również służyć do przekazywania dodatkowych informacji do alokatora, takich jak znacznik żądający alokacji pamięci z wybranej puli.
Przydzielony typ jest zwykle jawnie określony, np. new Foo
, ale można go również zapisać jako auto
(od C ++ 11) lub jako decltype(auto)
(od C ++ 14), aby wydedukować go z inicjalizatora.
Inicjalizacja przydzielonego obiektu odbywa się zgodnie z tymi samymi regułami, co inicjalizacja zmiennych lokalnych. W szczególności obiekt zostanie zainicjowany domyślnie, jeśli inicjator nie zostanie pominięty, a podczas dynamicznego przydzielania typu skalarnego lub tablicy typu skalarnego nie ma gwarancji, że pamięć zostanie wyzerowana.
Obiekt tablicy utworzony przez nowe wyrażenie musi zostać zniszczony przy użyciu delete[]
, niezależnie od tego, czy nowe wyrażenie zostało napisane za pomocą []
czy nie. Na przykład:
using IA = int[4];
int* pIA = new IA;
delete[] pIA; // right
// delete pIA; // wrong
Stos
Stos jest małym obszarem pamięci, w którym tymczasowe wartości są umieszczane podczas wykonywania. Przydzielanie danych do stosu jest bardzo szybkie w porównaniu do przydzielania sterty, ponieważ cała pamięć została już w tym celu przypisana.
int main() {
int a = 0; //Stored on the stack
return a;
}
Stos jest nazywany, ponieważ łańcuchy wywołań funkcji będą miały „tymczasowo” ułożone w stosy pamięci tymczasowej, przy czym każda z nich będzie korzystać z oddzielnej małej części pamięci.
float bar() {
//f will be placed on the stack after anything else
float f = 2;
return f;
}
double foo() {
//d will be placed just after anything within main()
double d = bar();
return d;
}
int main() {
//The stack has no user variables stored in it until foo() is called
return (int)foo();
}
Dane przechowywane na stosie są ważne tylko tak długo, jak długo zakres przydzielający zmienną jest nadal aktywny.
int* pA = nullptr;
void foo() {
int b = *pA;
pA = &b;
}
int main() {
int a = 5;
pA = &a;
foo();
//Undefined behavior, the value pointed to by pA is no longer in scope
a = *pA;
}
Bezpłatne miejsce do przechowywania (sterty, alokacja dynamiczna ...)
Termin „sterta” jest ogólnym terminem obliczeniowym, oznaczającym obszar pamięci, z którego części można alokować i zwalniać niezależnie od pamięci zapewnianej przez stos .
W C++
Standard określa ten obszar jako Free Store, który jest uważany za dokładniejszy termin.
Obszary pamięci przydzielone z Free Store mogą żyć dłużej niż pierwotny zakres, w którym zostały przydzielone. Dane zbyt duże, aby można je było zapisać na stosie, można również przydzielić z Free Store .
Surowa pamięć może być przydzielana i zwalniana przez nowe i usuwane słowa kluczowe.
float *foo = nullptr;
{
*foo = new float; // Allocates memory for a float
float bar; // Stack allocated
} // End lifetime of bar, while foo still alive
delete foo; // Deletes the memory for the float at pF, invalidating the pointer
foo = nullptr; // Setting the pointer to nullptr after delete is often considered good practice
Możliwe jest również przydzielanie tablic o stałym rozmiarze z nowymi i usuwanymi , z nieco inną składnią. Alokacja tablic nie jest kompatybilna z alokacją macierzy, a ich mieszanie doprowadzi do uszkodzenia sterty. Przydzielenie tablicy alokuje również pamięć do śledzenia rozmiaru tablicy do późniejszego usunięcia w sposób zdefiniowany w implementacji.
// Allocates memory for an array of 256 ints
int *foo = new int[256];
// Deletes an array of 256 ints at foo
delete[] foo;
Podczas korzystania z new i delete zamiast malloc i free , konstruktor i destruktor zostaną wykonane (podobnie do obiektów opartych na stosie). To dlatego nowe i usuwane są preferowane zamiast malloc i bezpłatne .
struct ComplexType {
int a = 0;
ComplexType() { std::cout << "Ctor" << std::endl; }
~ComplexType() { std::cout << "Dtor" << std::endl; }
};
// Allocates memory for a ComplexType, and calls its constructor
ComplexType *foo = new ComplexType();
//Calls the destructor for ComplexType() and deletes memory for a Complextype at pC
delete foo;
Począwszy od C ++ 11 zaleca się stosowanie inteligentnych wskaźników do wskazywania własności.
C ++ 14 dodał std::make_unique
do STL, zmieniając zalecenie, aby faworyzować std::make_unique
lub std::make_shared
zamiast używania „ new” i „ delete” .
Umieszczenie nowe
Są sytuacje, w których nie chcemy polegać na Free Store przy przydzielaniu pamięci i chcemy używać niestandardowych przydziałów pamięci przy użyciu new
.
W takich sytuacjach możemy użyć funkcji Placement New
, w której możemy powiedzieć operatorowi „nowy”, aby przydzielił pamięć z wcześniej przydzielonego miejsca w pamięci
Na przykład
int a4byteInteger;
char *a4byteChar = new (&a4byteInteger) char[4];
W tym przykładzie pamięć wskazywana przez a4byteChar
to 4 bajty przydzielone do „stosu” za pomocą zmiennej całkowitej a4byteInteger
.
Zaletą tego rodzaju alokacji pamięci jest fakt, że programiści kontrolują alokację. W powyższym przykładzie, ponieważ a4byteInteger
jest alokowane na stosie, nie musimy wykonywać jawnego wywołania, aby „usunąć a4byteChar”.
Takie samo zachowanie można również osiągnąć w przypadku dynamicznej pamięci przydzielonej. Na przykład
int *a8byteDynamicInteger = new int[2];
char *a8byteChar = new (a8byteDynamicInteger) char[8];
W tym przypadku wskaźnik pamięci a8byteChar
będzie odnosił się do pamięci dynamicznej przydzielonej przez a8byteDynamicInteger
. W takim przypadku musimy jednak jawnie wywołać delete a8byteDynamicInteger
aby zwolnić pamięć
Kolejny przykład dla klasy C ++
struct ComplexType {
int a;
ComplexType() : a(0) {}
~ComplexType() {}
};
int main() {
char* dynArray = new char[256];
//Calls ComplexType's constructor to initialize memory as a ComplexType
new((void*)dynArray) ComplexType();
//Clean up memory once we're done
reinterpret_cast<ComplexType*>(dynArray)->~ComplexType();
delete[] dynArray;
//Stack memory can also be used with placement new
alignas(ComplexType) char localArray[256]; //alignas() available since C++11
new((void*)localArray) ComplexType();
//Only need to call the destructor for stack memory
reinterpret_cast<ComplexType*>(localArray)->~ComplexType();
return 0;
}