Sziasztok!
C++-ban kezdő vagyok, ezért is a kezdő kérdés. Ha a programom kilép, úgy, hogy a main-ből return-ölök, akkor szépen a statikus objektumok destruktora is meghívódik.
Azonban, ha a konzol ablakot "ikszelem ki", vagy SIGINT-et köldök neki ez nem történik meg. A destruktorok nem hívódnak meg.
Mini példa kód:
#include
using namespace std;
// Foo egy Meyer singleton
class Foo{
private:
Foo()
{
cout << "Foo constructed" << endl;
}
~Foo()
{
cout << "Foo destructed" << endl;
}
public:
static Foo& getInstance()
{
static Foo f;
return f;
}
};
int main() {
Foo::getInstance();
char a;
std::cout << "Now exit with the X or press CTRL + C" << endl;
cin >> a;
return 0;
}
Ha hagyom kilépni a konzol kimenet a következő: (az a konzolon, amit a cin-nek adtam)
user@bithunter:~/workspace/asd/Release$ ./asd
Foo constructed
Now exit with the X or press CTRL + C
a
Foo destructed
Ha CTRL+C-zek (SIGINT)
user@bithunter:~/workspace/asd/Release$ ./asd
Foo constructed
Now exit with the X or press CTRL + C
^C
Mindkét esetben Foo a stack-en került létrehozásra, a második esetben mégsem törlődik? Ez memória folyást eredményez? Hogyan kezelik ezt értelmesen?
- 1267 megtekintés
Hozzászólások
Ha kilep a program, akkor az altala foglalt memoria is felszabadul, igy leaked nem lesz. Ha mas dolgokat is csinalsz a destructorban, amiket SIGINT-nel is szeretnel futtatni, akkor erdemes egy signal handlert raaggatni, beallitani egy flaget mondjuk, es utana szepen mainbol kilepni, ahogy egyebkent tenne.
--
|8]
- A hozzászóláshoz be kell jelentkezni
Magyarázatként a kérdezőnek: a processz minden erőforrását a kerneltől kapja:
* memória címterületek (pl stack, heap)
* fájl elérések (nyitott fájlok)
* hálózati elérések (nyitott portok)
* stb.
A programon belüli C++ konstruktor viszont már nem a kernelhez fordul, hanem vagy a stacken vagy a heapen foglal helyet (ami a programban le van írva) úgy hogy a program magán belül menedzseli, hogy mi hova kerül a saját memóriaterületein belül.
Tehát ha nem szabadítasz fel egy objektumot, az a programon belüli szinten leakel, oprendszer szinten nem.
Oprendszer szinten csak annyi látszik, hogy a processz RAM használata nő. (Van egy trükk az oprendszerben, hogy a lefoglalt memória címterületek mögé csak akkor tesz valódi RAM-ot az oprendszer, amikor egy lapba 0-tól eltérőt ír az adott program. A foglalt címterület a VIRT, és a valóban mögétett RAM A RES a top program nézetben.)
Amikor leállítod a programot, akkor az oprendszer minden allokált erőforrást magától felszabadít. Ez Linuxon teljesen jól működik, lehet építeni rá. Ezért ha tudod, hogy a programod le fog állni nem feltétlenül kell még a fájlok bezárásával sem foglalkozni (nem szép gyakorlat, de működik). A programbeli objektumokat szintén felesleges felszabadítani. Persze ez sem feltétlenül szép. Ha például a programodat később egy másik program alrendszereként akarod használni, akkor már gond lehet ha egy lefutása után felszabadítatlan objektumok maradnak utána.
(Még a régi szép DOS-os időkben lehetett olyan, hogy egy program az oprendszert kikerülve közvetlen BIOS vagy egyéb hívásokkal foglalt tárterületet, azok be tudtak ragadni a program leállása után. A modern oprendszerekben ilyen már nincs. Vagy ha van, akkor az kernel bug :-)
Amit mindenképpen érdemes leállás előtt elvarrni, az az írt fájlok flusholása és lezárása. Ha nem történt flush a program kényszer-bezárása előtt, akkor félig írt fájlok maradhatnak a diszken. Ez az ami miatt érdemes lehet SIGINT handlert csinálni, de valójában ez is csak speciális programoknál érdekes, a legtöbb esetben a signallal leállított program esetén nem elvárt a konzisztens kimenet.
Ha normál leállás történik, akkor szerintem az összes nyitott fájlra lefut egy flush. Például az stdout-ra írt dolgok mindenképpen megjelennek a kimeneten legkésőbb a leálláskor, akkor is ha nincs flush a programban.
- A hozzászóláshoz be kell jelentkezni
Nagyon tetszik ez az informatív leírás és nagyon sajnálom, hogy ez legtöbbször semennyire nem jön elő egyetemen.
- A hozzászóláshoz be kell jelentkezni
+1 az első felére, a másodikról nem tudok nyilatkozni.
- A hozzászóláshoz be kell jelentkezni
Ez mind nagyon szep elmeleti szinten, csak nem korrekt. Mert mi van, ha pl. egy lockfile-t hozol letre? Azt kilepesnel nem fogja az os "felszabaditani". Vagy letrehozol egy domain socket-et, bind-olsz hozza es utana lovod ki a programot? Ott marad egy file a filerendszerben, es kovetkezo indulasnal a bind() el fog hasalni, ha nem elozi meg egy unlink() (felteve, hogy az uds neve nem \0-val kezdodik). Es mivel c++-ban a RAII egy nagyon szeles korben hasznalt eroforras kezelesi paradigma, igazabol rengeteg fele resource beragadhat es be is ragad ilyen esetekben.
Szoval az nem igaz, hogy az os "minden" eroforrast felszabadit, van egy csomo eroforras, ami felett nincs is kontrollja. A memoria es a megnyitott fd-k valoban lezarasra kerulnek, de ez egy komplexebb programban nem minden.
Igazabol annyit kell itt tudni, hogyha a programbol az main-bol valo return-nel tersz vissza, akkor termeszetesen a stack-en foglalt valtozok es az atexit handlerekhez kotott (statikus) valtozok is felszabadulnak (a szabvany garantalja, hogy milyen sorrendben). Ugyanez igaz akkor is, ha az exit() hivasaval lepsz ki a programbol. Ellenben ha a processz egy olyan signalt kap, amire az aktualis beallitas "Term", akkor az ugynevezett "abnormal program termination-t" okoz. Ilyen alapbol a SIGINT, illetve megvaltoztathatatlanul pl. a SIGKILL es SIGSEGV. Abnormal program termination eseten nincs stack unwind es nincs atexit handler vegrehajtas sem, tehat sem a stack-en levo valtozok, sem a globalisok nem szabadulnak fel kilepesnel. Erre fel kell keszulnod, ha robosztus programot akarsz kesziteni.
- A hozzászóláshoz be kell jelentkezni
Nem tudom hogyan megy a SIGINT kezelés, és miért nem hívódik meg a desturoktorod, de azt azért megjegyezném, hogy a változódnak (f) nem sok köze van a stack -hez. A "static" miatt ez egy globális változó lesz, aminek a láthatósága korlátozódik a getInstance függvényre.
- A hozzászóláshoz be kell jelentkezni
#include <iostream>
#include <csignal>
using namespace std;
// Foo egy Meyer singleton
class Foo {
private:
Foo() {
cout << "Foo constructed" << endl;
}
~Foo() {
cout << "Foo destructed" << endl;
}
public:
static Foo& getInstance() {
static Foo f;
return f;
}
};
void signalHandler(int signum) {
cout << "Interrupt signal (" << signum << ") received.\n";
exit(signum);
}
int main() {
signal(SIGINT, signalHandler);
Foo::getInstance();
char a;
std::cout << "Now exit with the X or press CTRL + C" << endl;
cin >> a;
return 0;
}
- A hozzászóláshoz be kell jelentkezni
Nagyon szép így indentálva, de ettol még:
static Foo f;
egy függvényen belüli statikus változó amit a compiler a .data szegmensbe fog allokálni, azaz nem lesz a stack -en. Ha ott lenne az csúnya lenne mivel a getInstance(0 -ból való kilépéskor szépen felszabadulna.
- A hozzászóláshoz be kell jelentkezni
Gondolom a kerdezo azt hitte, hogy valahol a stack aljan jonnek letre a statikus valtozok, meg a main kezdete elott. Ez nem igy van. A fuggveny scope-ban levo (mint amit a Meyers singleton hasznal) statikusok eseteben ez kivitelezhetetlen is lenne, mivel azok akkor jonnek letre, amikor a vegrehajtas eloszor eleri az adott sort.
- A hozzászóláshoz be kell jelentkezni
FYI kivancsisagbol windowson is lefuttattam (VS2017 community -std=c++14), es ott a destruktorba rakott szoveg elso ~4 karaktere kiirodott, csak kozben a progi veget ert. Szoval szerintem lefut az a destruktor, bar oprendszer utemezesben annyira en sem vagyok penge. :)
--
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live." John F. Woods
- A hozzászóláshoz be kell jelentkezni
No itten a válasz: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf
3.6.3 Termination
[basic.start.term] 1
Destructors (12.4) for initialized objects (that is, objects whose lifetime (3.8) has begun) with static storage
duration are called as a result of returning from main and as a result of calling std::exit (18.5).
...
3.6.3 /5
Calling the function std::abort() declared in <cstdlib> terminates the program without executing any destructors and without calling the functions passed to std::atexit() or std::at_quick_exit()
Valamint:
18.8.3 Abnormal termination
18.8.3.1 Type terminate_handler[terminate.handler]
typedef void (*terminate_handler)();
1 - The type of a handler function to be called by std::terminate() when terminating exception pro-
cessing.
2 - Required behavior:
A terminate_handler shall terminate execution of the program without returning to the caller.
3 - Default behavior:
The implementation’s default terminate_handler calls abort().
- A hozzászóláshoz be kell jelentkezni