[C++] Przepełnienie zarezerwowanej pamięci


(Quentin) #1

Witam!

Mam kilka pytań odnośnie zarezerwowanej pamięci przeznaczonej do dalszego użytku (np. korzystają z niej operatory new i delete).

Pytanie 1.

Na początek pytanie czy dobrze to rozumiem: jeżeli mamy zwykłą pamięć, np. 2 GB. To jeżeli programy uruchomione w systemie używają ok. 500 MB, to na program zostanie przeznaczona prawie cała wolna czyli ok. 1,5 GB (na program nasz + dla kompilatora itp.). To tak w uproszczeniu. I teraz ta rezerwacja pamięci odbywa się w ramach tej całej wolnej ? Jak to z tym dokładnie bywa ?

Pytanie 2.

Możemy przepełnić tą pamięć zarezerwowaną. Są trzy sposoby, żeby nad tym zapanować:

* zwrócenie adresu zero do wskaźnika ( std::nothrow )

* rzucenie wyjątku std::bad_alloc

* przygotowanie specjalnej funkcji na awarię - set_new_handler

Pytanie 2 brzmi: jak nic nie określimy to co się stanie w najczęściej - tzn. jak zareaguje kompilator ? Żeby wszystko było jasne to nie mogę sprawdzić wszystkich tych pytań sam, bo pracuję na VM, a ona ma jakby inne komunikaty o braku pamięci niż normalny Windows...


Przykładowo mamy taki kod:

#include 

using namespace std;


int main()

{


try

{

    double *wsk;

    for( ; ; ) //w końcu nastąpi przepełnienie...

    {

        wsk = new double;

    }

}

catch(bad_alloc) // <---tak naprawdę to nazywa się ona: std::bad_alloc

{

    cout << "Przechwycona nieudana rezerwacja pamieci !";

}



cout << "\n\n\n\n\n\n---------------------------------------------------------------\n";

system("pause");

}[/code]




Wiem co on oznacza - po przepełnieniu wykona się blok [b]catch[/b]. 



A teraz 3 opcja zareagowania na brak pamięci:

[code]#include #include #include using namespace std; //----------------------- void funkcja_alarmowa(); //nasza funkcja do obsługi wyjątków long k; //globalna zmienna 'k' int main() { set_new_handler(funkcja_alarmowa); for(k = 0 ; ; k++) { new int; //rezerwacja miejsca na obiekt } cout << "\n\n\n\n\n\n---------------------------------------------------------------\n"; system("pause"); } //***************************************** void funkcja_alarmowa() { cout << "\nZabraklo pamieci przy k = " << k << " !"; exit(1); }
Biblioteka new :

#include

Odpowiada za deklarację set_new_handler. Natomiast:

#include

żeby można było użyć:exit(1);****Pytanie 3. **Co oznacza to exit(1);:?: Nie jest to wytłumaczone wcale... Tutaj: :arrow: http://publib.boulder.ibm.com/infocente ... newhnd.htmJest napisane, że może być zamiast tegoabort(); też. Ale co oznaczają te instrukcje i liczba w nawiasie ? Pytanie 4.**W książce jest napisane:

I wcześniej był taki komentarz w programie pod exit():

exit(1);

// throw bad_alloc();

Tu chodziło autorowi o coś takiego :?:

#include 

#include 

#include 

using namespace std;

//-----------------------

void funkcja_alarmowa(); //nasza funkcja do obsługi wyjątków

long k; //globalna zmienna 'k'

int main()

{

    set_new_handler(funkcja_alarmowa);


    try //do wyjątku bad_alloc

    {

        for(k = 0 ; ; k++)

        {

            new int; //rezerwacja miejsca na obiekt

        }

    }

    catch(bad_alloc) //"Łapiemy wyjątek"

    {

        cout << "\"Lapiemy wyjatek\"";

    }


cout << "\n\n\n\n\n\n---------------------------------------------------------------\n";

system("pause");

}

// *****************************************

void funkcja_alarmowa()

{

    cout << "\nZabraklo pamieci przy k = "

         << k << " !";

    exit(1);

}

O ile ten kod zadziała to co się pierwsze wykona - funkcja specjalna - tzn. ustalona przez set_new_handler czy blok catch(bad_alloc) :?:

Pytanie 5.

Pierwsza opcja, czyli zwracanie NULLa do wskaźnika to stary sposób. Dwa nowe to właśnie te ostatnie sposoby. Czym one się różnią tak naprawdę ? Przecież tu i tu "akcja alarmowa" jest przekazywana do bloku, do którego chcieliśmy ją przekazać. Raz jest przekazywana do bloku catch a drugi raz do bloku ustalonej przez nas funkcji.


([alex]) #2

Teoretycznie windows'y tworzą wirtualną przestrzeń adresową więc nawet jak masz 2 GB pamięci to każdy program ma mocno ponad 2 GB do przydzielenia, z tym że zaczyna się "swapowanie" a to strasznie spowalnia.

* zwrócenie adresu zero do wskaźnika - przy przedzieleniu za pomocą malloc(), realloc()

* rzucenie wyjątku - przy przedzieleniu za pomocą new

O ile to exit() wywołano z funkcji main() to oznacza tyle co return 1; zaś exit(2); to samo co return 2; i tp. Różnica jest istotna kiedy exit() jest wywołana nie w funkcji main(), efekt - natychmiastowe zakończenie programu z zwróceniem kodu błędu do systemu, no chyba że użyto exit(0); - zakończenie programu w "trybie normalnym".

Tak jak napisałeś wywoła się tylko funkcja alarmowa. Jeżeli ją poprawisz na:

void funkcja_alarmowa()  {   cout  "\nZabraklo pamieci przy k = "  k  " !";   throw bad_alloc;  } [/code]To wywoła się najpierw funkcja alarmowa a potem blok [b]catch[/b].




[quote="Quentin"]

[color=#4040FF][b]Pytanie 5.[/b][/color]Pierwsza opcja, czyli zwracanie NULLa do wskaźnika to stary sposób. Dwa nowe to właśnie te ostatnie sposoby. Czym one się różnią tak naprawdę ? Przecież tu i tu "akcja alarmowa" jest przekazywana do bloku, do którego chcieliśmy ją przekazać. Raz jest przekazywana do bloku [b]catch[/b] a drugi raz do bloku ustalonej przez nas funkcji.
[/quote]
Wg mnie wyjaśniłem przy okazji odpowiedzi na [color=#4040FF][b]Pytanie 4.[/b][/color]

(Quentin) #3


(Fiołek) #4

Wykonuje się ta funkcja i zostaje rzucony wyjątek z TEJ FUNKCJI i łapiemy rzucony przed chwilą w bloku catch, czyli tak jak napisałeś.

Nie, to o czym mówisz to aseracje. exit jest równoznaczne z "normalnym" zamknięciem programu zwracając numer który przekazałeś do funkcji, czyli tak jakbyś zrobił "return 1;"(gdzie 1 to liczba przekazywana do exit) które zwracałoby tą wartość z main.

EDIT:

Ale to jest tylko przestrzeń adresowa. Jeśli nie zaalokujesz więcej niż dostępna pamięć RAM, to system raczej nie przejdzie do swapu(chyba, że w tym czasie jakiś inny proces zaalokuje, to tą pamięć "zepchnie" do swapa AFAIK). Zresztą można wyłączyć go i wtedy zostanie rzucony wyjątek.


([alex]) #5

Fiołek Ze mną walczysz o dokładność wypowiedzi a sam co? :lol:

To co napisałeś jest prawdą dopóki ten return/exit "wywołany" bezpośrednio w funkcji main. Jeżeli "wywołano" z innej funkcji niż main to są rzeczy zupełnie nie porównywalne, return może nawet się nie skompilować. "wywołany" - w cudzysłowach, ponieważ nie da się nazwać instrukcję return 0; wywołaniem.

O właśnie tak, dokładniej niż napisałem to ja =D> Ale nadał nie do końca, ponieważ jedna aplikacja może mieć przydzielony mały kawałek pamięci akurat w środku pamięci fizycznej, zaś druga próbuje przydzielić kawałek wielkości ponad połowę pamięci fizycznej, w sumie przydzielono trochę ponad połowę ale swap już będzie, chyba że został wyłączony.


(Fiołek) #6

return 0;"wywołane" z maina jest tym samym co exit(0) wywołane z maina czy z innej funkcji. Oczywiście to co się ma/miało wykonać po wywołaniu "exit(0)" z którejś tam funkcji się nie wykona bo zamknęliśmy aplikacje.

Wydawało mi się, że napisałem jasno :wink:

"dostępna" - dostępna nie w komputerze, ale dostępna dla danego procesu/aplikacji.


([alex]) #7

(Fiołek) #8

Aha, o to Ci chodziło! Masz rację, zmęczony jestem(niedziela) :wink:

EOT.