OVH Cloud OVH Cloud

destructeur et exit récursif

5 réponses
Avatar
bernard tatin
Je savais bien que l'on pouvait faire de l'exit récursif

Voici un exemple de programme qui fait dans la récursion imprévue :

class Ex {
public:
Ex(int a) : val(a) {}
virtual ~Ex() {
printme();
exit(1);
}
virtual void printme() {
std::cout << "Ex(" << val << ")" << std::endl;
}
int val;
};
Ex *e1 = new Ex(1);
Ex *e2 = new Ex(2);
Ex *e3 = new Ex(3);

#define DEL_PTR(p) if (p) { delete p; p=0; }

extern "C" void onexit(void) {
DEL_PTR(e1);
DEL_PTR(e2);
DEL_PTR(e3);
}
int main () {
atexit(onexit);
{
Ex e0(0);
std::cout << "attention à l'exit ! " << std::endl;
}
std::cout << "exit(0)" << std::endl;
exit(0);
return 0;
}


En fait, il suffit de faire ceci - plus besoin de classes ou de
destructeurs -

extern "C" void onexit2(void) {
std::cout << "je veux sortir !" << std::endl;
exit(255);
}
int main () {
atexit(onexit2);
exit (0);
}

Ma page de man pour atexit donne ceci :
These functions must not call exit(); if it should be necessary to termi
nate the process while in such a function, the _exit(2) function
should be used. (Alternatively, the function may cause abnormal
process termi nation, for example by calling abort(3).)

Bernard

PS : je suis sous MacOS X, donc d'autres BSD, voire Linux ou Windows
devraient avoir un comportement identique.

5 réponses

Avatar
drkm
bernard tatin writes:

#define DEL_PTR(p) if (p) { delete p; p=0; }


Le test est inutile.

extern "C" void onexit(void) {
DEL_PTR(e1);


Obfuscation.

DEL_PTR(e2);
DEL_PTR(e3);
}
int main () {
atexit(onexit);


Pourquoi déclarer un handler de atexit « extern "C" » ?

extern "C" void onexit2(void) {
std::cout << "je veux sortir !" << std::endl;
exit(255);


Comportement indéfini, non ?

}
int main () {
atexit(onexit2);
exit (0);
}


--drkm

Avatar
bernard tatin
Il y a quelques jours dans le thread exceptions multiples,
André Heinen écrivait :
Si tu n'as pas de récursion infinie, ça fonctionne sans problème,
mais sans appeler les destructeurs des objets automatiques.

Quant à la récursion infinie, je sais que c'est possible, mais je
n'en ai jamais vu. Je n'ai encore jamais vu de destructeur qui
appelle exit(). Et quand bien même ce serait le cas, encore
faudrait-il que ce soient justement des instances de cette
classe-là qui soient détruites par exit().
Et j'avais répondu que j'étais déjà tombé sur ce phénomène. J'ai donc

fini par retrouver un vieux bout de source qui le provoque. Avec du
copier coller et en simplifiant, cela donne la première partie de code.
La deuxième partie est la simplification complète du phénomène.

Certes, le DEL_PTR n'est pas judicieux dans l'exemple. Le extern "C",
c'est bien ce que j'avais dans mon code. Je pense qu'à l'époque cela
m'était indispensable, mais je ne me souviens plus pourquoi.

Je voulais démontrer que l'utilisation d'exit pouvait être délicate.
Lors de la discussion précédente, j'avais oublié que c'était
l'utilisation conjointe de exit et de atexit qui était explosive.

Je ne sais plus exactement de quand date ce code - tous les fichiers
sont au 01/01/1970 00:00 - mais il a plus de dix ans et c'était mes tous
premiers débuts en C++, avec Tubo C++ de Borland, sous MS/DOS. J'ai donc
appri ce jour là que l'utilisation d'exit dans les destructeurs était
dangereuse.

drkm wrote:
bernard tatin writes:


#define DEL_PTR(p) if (p) { delete p; p=0; }



Le test est inutile.


extern "C" void onexit(void) {
DEL_PTR(e1);



Obfuscation.



DEL_PTR(e2);
DEL_PTR(e3);
}
int main () {
atexit(onexit);



Pourquoi déclarer un handler de atexit « extern "C" » ?


extern "C" void onexit2(void) {
std::cout << "je veux sortir !" << std::endl;
exit(255);



Comportement indéfini, non ?


}
int main () {
atexit(onexit2);
exit (0);
}



--drkm



Avatar
kanze
drkm wrote in message
news:...
bernard tatin writes:

int main () {
atexit(onexit);


Pourquoi déclarer un handler de atexit « extern "C" » ?


Parce que c'est peut-être nécessaire ?

Selon §17.4.2.2/2 « It is unspecified whether a name from the Standard C
library declared with external linkage has either extern "C" or extern
"C++" linkage. Si l'en-tête déclare :

extern "C" int atexit( void (*func)() ) ;

la fonction que tu passes doit obligatoirement être extern "C" -- sinon,
c'est une erreur, et le compilateur doit gueuler. Si on revanche,
l'en-tête déclare simplement :

extern int atexit( void (*func)() ) ;

(extern "C++" étant le défaut), la fonction que tu passe ne doit en
aucun cas être extern "C" -- sinon, c'est une erreur, et le compilateur
doit gueuler.

En fait, je ne suis pas sûr. Dans §18.3, il y a deux signatures données.
Mais ce n'est pas clair pour moi si c'est simplement pour montrer les
deux signatures possibles, ou c'est pour dire qu'une implémentation doit
obligatoirement en fournir les deux. (Dans d'autres cas où la norme
exige le remplacement d'une fonction C par deux en C++, par exemple,
dans §21.4/4, elle le dit explicitement.)

À vrai dire, je crois que ça vaut la peine de soulever la question dans
comp.std.c++.

extern "C" void onexit2(void) {
std::cout << "je veux sortir !" << std::endl;
exit(255);


Comportement indéfini, non ?


De la norme C, §7.20.4.3/2 : « The exit function causes normal program
termination to occur. If more than one call to the exit function is
executed by a program, the behavior is undefined. » (La norme C++
spécifie « additional behavior » pour atexit, par rapport à C.
J'interprète « additional behavior » à signifier que les mêmes
restrictions s'appliquent qu'en C.)

Dans l'ensemble, je dirais que atexit n'a aucun intérêt en C++ -- des
destructeurs des objets statiques sont plus idiomatique, et convient
plus. Heureusement, parce que je crois qu'elle est à peu près
inutilisable.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
bernard tatin
wrote:
Parce que c'est peut-être nécessaire ?

Selon §17.4.2.2/2 « It is unspecified whether a name from the Standard C
library declared with external linkage has either extern "C" or extern
"C++" linkage. Si l'en-tête déclare :

extern "C" int atexit( void (*func)() ) ;

la fonction que tu passes doit obligatoirement être extern "C" -- sinon,
c'est une erreur, et le compilateur doit gueuler.


Lorsque j'ai écrit ce code, il y a longtemps, cela devait être
nécessaire. Avec les environnements d'aujourd'hui, ce n'est peut-être
plus le cas.

Si j'utilisais atexit à l'époque : ce code détournait des interruptions.
Cette partie très bas niveau n'utilisait pas de classes et le atexit
faisait le nettoyage. Mon erreur a été de méler du C pur - avec
assembleur en ligne - et du C++ sans trop de discernement. Je débutais
en C++, aussi.



Bernard.

Avatar
drkm
writes:

drkm wrote in message
news:...

Pourquoi déclarer un handler de atexit « extern "C" » ?


En fait, je ne suis pas sûr. Dans §18.3, il y a deux signatures données.


C'est en effet ce que j'avais trouvé (je n'ai pas vérifié qu'il
s'agit du même paragraphe, mais j'avais effectivement vu les deux
signatures).

Mais ce n'est pas clair pour moi si c'est simplement pour montrer les
deux signatures possibles, ou c'est pour dire qu'une implémentation doit
obligatoirement en fournir les deux. (Dans d'autres cas où la norme
exige le remplacement d'une fonction C par deux en C++, par exemple,
dans §21.4/4, elle le dit explicitement.)

À vrai dire, je crois que ça vaut la peine de soulever la question dans
comp.std.c++.


Sans doute. Mais je ne me sens pas l'étoffe « standardeeze ».

--drkm