Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Destruction des exceptions

16 réponses
Avatar
Bastien Durel
Bonjour,

J'ai un problème dans une procédure qui utilise des exceptions : je lève
une exception dans une fonction, mais mon instance est détruite avant
d'être passée au gestionnaire d'exception.

voici l'appelant :
try
{
LOG("Init parser");
_Init(lOptions, mInitialized);
LOG("Init Done");
}
catch (const TDataParserException & e)
{
LOG("Exception !" << (int)(void*)(&e));
LOG(e.Where() << ": " << e.What());
mInitialized=false;
mFile.Rollback();
}

et ici, un bout de la fonction membre _Init :
void USER::Init(const TDataOptions& aOptions, bool& aInitialized)
{
//[...]
if (!(mInputFile.SubStr(0, 3) == "UNA"))
throw TDataParserException("File does not begins with UNA",
__FILE__, __LINE__);

Et voici ce que je trouve dans ma trace :
Wed Oct 03 14:27:35 2007: Entering in
TDataParserException::TDataParserException(const char_t*, const char*,
int) (1241792)
Wed Oct 03 14:27:35 2007: TDataParserException: File does not begins
with UNA, ./edifact/user_init.cpp: 48
Wed Oct 03 14:27:35 2007: Entering in virtual
TDataParserException::~TDataParserException() (1241792)
Wed Oct 03 14:27:35 2007: Exception !76741664

le chiffre entre parenthèse est l'addresse *this
le e.Where() provoque une violation d'accès.

Pour être complet, je dois ajouter que le code appelant est dans une
bibliothèque statique, et fait partie d'une classe abstraite, _Init
étant implémentée dans une classe qui en dérive.

pseudocode :
// dans la bibliothèque
class Base {
void Init() {
try { _Init(); }
catch (const TDataParserException& e) { e.What; }
}
virtual void _Init() throw (TDataParserException)=0;
};

// Dans le source
class USER : public Base {
virtual void _Init() throw (TDataParserException) {
throw TDataParserException("");
}
};

Le tout est compilé avec g++ (GCC) 3.4.2 (mingw-special) et embarqué
dans une dll rendant l'utilisation d'un débogueur assez contraignante.

*On dirait* que l'exception est copiée avant d'être passée au
gestionnaire, mais nulle part dans ma trace n'apparait l'appel au
constructeur de copie de TDataParserException.
Avez-vous une idée de la raison de ce comportement ? Ça ne me semble pas
cohérent ...

Merci,

--
Bastien

6 réponses

1 2
Avatar
Bastien Durel
On 04/10/2007 17:57, Christophe Lephay wrote:
"Christophe Lephay" a écrit dans le message
de news: 47050bb7$0$27381$
"James Kanze" a écrit dans le message de news:

On Oct 4, 5:20 am, "Christophe Lephay"
wrote:
Peut-être le constructeur TDataParserException( const
TDataParserException& ) génère-t-il lui-même une exception...
Si c'est le cas, on ne doit pas en appeler le destructeur. On

n'appelle le destructeur que sur des objets (ou sous-objets)
dont le constructeur a fini.
Si j'ai bien compris (rien n'est moins sur), une première exception est

correctement créée dans le throw( ... ) par un premier constructeur, puis
l'exception est recopiée via le constructeur TDataParserException( const
TDataParserException& ).

Si le second constructeur lève une exception, le destructeur est tout de
même appelé sur l'exception construite initialement.


En fait non : en relisant, le destructeur est appelé avant la recopie...


Exact. Et il n'y a pas de recopie si je catch par référence


--
Bastien




Avatar
Bastien Durel
On 04/10/2007 09:44, James Kanze wrote:
On Oct 4, 5:20 am, "Christophe Lephay"
wrote:
"James Kanze" a écrit dans le message de news:

On Oct 3, 6:24 pm, Bastien Durel wrote:

Si je récupère l'exception par copie plutôt que par référence, il
commence tout de même par détruire l'instance sur le tas avant de
reconstruire par recopie à partir d'une référence invalide, ce qui
provoque une violation d'accès :
Wed Oct 03 18:11:14 2007: Entering in
TDataParserException::TDataParserException(const TDataParserException&)
(1243152)
De même si je fais un affreux "throw *(new TDataParserException());"

C'est difficile à dire. J'ai essayé une version simplifiée de ce

que j'ai compris de ton code, et ça marchait bien (g++ 4.1.0,
sur Solaris/Sparc). Est-ce qu'il peut y avoir un problème dans
l'hiérarchie de TDataParserException ? Ou dans une fonction que
tu appelles dans le constructeur ? Encore que le destructeur de
TDataParserException ne doit être appelé que si le constructeur
a fini. Ou est-ce qu'il y aurait un destructeur d'un objet sur
la pile qui lève une exception ?


Peut-être le constructeur TDataParserException( const
TDataParserException& ) génère-t-il lui-même une exception...


Si c'est le cas, on ne doit pas en appeler le destructeur. On
n'appelle le destructeur que sur des objets (ou sous-objets)
dont le constructeur a fini.
J'ai réussi à le reproduire sur Linux, compilateur g++ 4.1.2

J'ai aussi ajouté la trace de la sortie des fonctions :

Entering in TDataParserException::TDataParserException(const char_t*,
const char*, int) (-1073744268)
Entering in const char_t* unicodeFromChar(const char*, const char*)
Entering in const char_t* unicodeFromChar(const char*, const char*, int)
TDataParserException: un petit test comme ca, unit.cpp: 72
mMessage is 134655352 mWhere is 134655608
Exiting from TDataParserException::TDataParserException(const char_t*,
const char*, int) (-1073744268)
Entering in virtual TDataParserException::~TDataParserException()
(-1073744268)
Exiting from virtual TDataParserException::~TDataParserException()
(-1073744268)
Exiting from TDataParserException::TDataParserException(const
TDataBuffer&, const char*, int) (134655336)
Entering in TDataBuffer::~TDataBuffer() (-1073744192)
here
Entering in const char_t* unicodeFromChar(const char*, const char*)
Entering in const char_t* unicodeFromChar(const char*, const char*, int)
make: *** [unit] Segmentation fault (core dumped)

On dirait donc qu'il passe par le destructeur avant de sortir du
constructeur ...
Plus ça va, moins je comprends.

--
Bastien





Avatar
Christophe Lephay
"Bastien Durel" a écrit dans le message de news:
47051a50$0$21148$
Entering in TDataParserException::TDataParserException(const char_t*,
const char*, int) (-1073744268)
[...]

Exiting from TDataParserException::TDataParserException(const char_t*,
const char*, int) (-1073744268)
Entering in virtual TDataParserException::~TDataParserException()
(-1073744268)
Exiting from virtual TDataParserException::~TDataParserException()
(-1073744268)
Exiting from TDataParserException::TDataParserException(const
TDataBuffer&, const char*, int) (134655336)


L'entrée dans TDataParserException::TDataParserException(const TDataBuffer&,
const char*, int) n'apparait pas dans ton log.

Ce constructeur ne tente-t-il pas de créer un une nouvelle exception via le
constructeur TDataParserException(const char_t*, const char*, int) ?

Avatar
James Kanze
On Oct 4, 6:52 pm, Bastien Durel wrote:
On 04/10/2007 09:44, James Kanze wrote:


[...]
J'ai réussi à le reproduire sur Linux, compilateur g++ 4.1.2
J'ai aussi ajouté la trace de la sortie des fonctions :


C'est assez bizarre, parce que...

Entering in TDataParserException::TDataParserException(const char_t*,
const char*, int) (-1073744268)


Que sont les valeurs dans les parenthèses à la fin ? (Et si
c'est le cas, pourquoi apparaissent-elles comme des int ? Quand
je sors une adresse avec mon g++, sous Linux sur PC, les
adresses sont affichées en hex, comme si elles étaient des
unsigned de la taille correspondant.)

Enfin, s'il s'agit des adresses converties en int, et que tu te
trouvent sur un PC de 32 bits, ce constructeur-ci est appelé
pour un objet sur la pile : une variable locale ou un
temporaire. Elle ne correspond *pas* à un objet qu'on throw (qui
de toutes apparences se trouve dans les mêmes adresses, plus ou
moins, que des objets alloués dynamiquement).

Entering in const char_t* unicodeFromChar(const char*, const char*)
Entering in const char_t* unicodeFromChar(const char*, const char*, int)
TDataParserException: un petit test comme ca, unit.cpp: 72
mMessage is 134655352 mWhere is 134655608
Exiting from TDataParserException::TDataParserException(const char_t*,
const char*, int) (-1073744268)


Là, tu as fini la construction (a priori, si les messages ne
mentent pas) invoqué ci-dessus.

Entering in virtual TDataParserException::~TDataParserException()
(-1073744268)
Exiting from virtual TDataParserException::~TDataParserException()
(-1073744268)


Et là, tu as détruit l'objet construit ci-dessus. (Pour
l'instant, rien dans les adresses où g++ met les exceptions, au
moins si tu es sous Linux, sur un PC 32 bits.)

Exiting from TDataParserException::TDataParserException(const
TDataBuffer&, const char*, int) (134655336)


Et là, tu en sors une deuxième fois, mais d'un autre
constructeur, dont tu n'as pas tracé l'entrée. Et cette fois-ci,
l'adresse est bien plausible comme adresse de l'objet qui est
levé comme exception.

Entering in TDataBuffer::~TDataBuffer() (-1073744192)
here


Ici non plus, je ne vois pas où tu as construit l'objet. Mais au
pif, vue que c'est sur la pile, je supposerais un objet
temporaire qui as servi de paramètre au constructeur du deuxième
TDataParserException, celui qui est réelement l'exception.

Entering in const char_t* unicodeFromChar(const char*, const char*)
Entering in const char_t* unicodeFromChar(const char*, const char*, int)
make: *** [unit] Segmentation fault (core dumped)

On dirait donc qu'il passe par le destructeur avant de sortir du
constructeur ...
Plus ça va, moins je comprends.


Ce qui est clair, c'est que tu ne traces pas tout ce qu'il faut,
qu'il y a des constructeurs (de TDataParserException et de
TDataBuffer), par exemple, dont tu ne traces pas l'entrée.
Ce que je vois me suggère plusieurs possiblités : que tu crées
un TDataParserException temporaire pour en obtenir un
TDataBuffer, qui sert dans l'objet que tu lèves (le deuxième
TDataParserException), mais qui n'as pas la durée de vie
réquise (erreur de conception : il faudrait le copier, et non
en stocker une référence ou un pointeur ?), ou que tu as créé
un temporaire de type TDataParserException dans le constructeur
du TDataParserException, quelque chose du genre :

TDataParserException::TDataParserException(...)
{
TDataParserException(...) ;
}

Et que là aussi, tu comptes sur des initialisations qui ont eu
lieu dans le temporaire pour initialiser quelque chose dans
l'objet même.

Pour commencer, je te conseille d'ajouter les traces qui
manquent, c-à-d d'instrumenter l'entrée et la sortie de *tous*
les constructeurs de TDataParserException, et non seulement
quelques uns, et de sortir l'adresse des this chaque fois, comme
un pointeur. Ensuite, *si* tu es sous Linux sur PC : les
adresses tout en haut de la mémoire sont sur la pile, des
variables locales ou des temporaires ; les adresses plus bas
sont soient des statics (tout en bas), soit des objets sur le
tas (vers le milieu). Et comme j'ai dis, g++ a l'air de mettre
les objets qu'on lève dans le tas.

--
James Kanze (GABI Software) email:
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
Bastien Durel
On 05/10/2007 09:37, James Kanze wrote:
On Oct 4, 6:52 pm, Bastien Durel wrote:
On 04/10/2007 09:44, James Kanze wrote:


[...]
J'ai réussi à le reproduire sur Linux, compilateur g++ 4.1.2
J'ai aussi ajouté la trace de la sortie des fonctions :


C'est assez bizarre, parce que...

Entering in TDataParserException::TDataParserException(const char_t*,
const char*, int) (-1073744268)


Que sont les valeurs dans les parenthèses à la fin ? (Et si
c'est le cas, pourquoi apparaissent-elles comme des int ? Quand
je sors une adresse avec mon g++, sous Linux sur PC, les
adresses sont affichées en hex, comme si elles étaient des
unsigned de la taille correspondant.)

c'est this, casté en int vu que mon g++ 3.2/win me sortait juste 1 sinon.


Enfin, s'il s'agit des adresses converties en int, et que tu te
trouvent sur un PC de 32 bits, ce constructeur-ci est appelé
pour un objet sur la pile : une variable locale ou un
temporaire. Elle ne correspond *pas* à un objet qu'on throw (qui
de toutes apparences se trouve dans les mêmes adresses, plus ou
moins, que des objets alloués dynamiquement).

[...]

Ce que je vois me suggère plusieurs possiblités : que tu crées
un TDataParserException temporaire pour en obtenir un
TDataBuffer, qui sert dans l'objet que tu lèves (le deuxième
TDataParserException), mais qui n'as pas la durée de vie
réquise (erreur de conception : il faudrait le copier, et non
en stocker une référence ou un pointeur ?), ou que tu as créé
un temporaire de type TDataParserException dans le constructeur
du TDataParserException, quelque chose du genre :

TDataParserException::TDataParserException(...)
{
TDataParserException(...) ;
}

Et que là aussi, tu comptes sur des initialisations qui ont eu
lieu dans le temporaire pour initialiser quelque chose dans
l'objet même.

En fait, c'était ça le problème une bête tentative d'économie de code

dans un de mes nombreux constructeurs, compilé seulement avec le support
d'ICU. C'est ça qui m'a mis sur la piste au final, je ne reproduisais ça
qu'avec ICU, pas avec les autres supports de chaines de caractère.
Je devais être fatigué, le jour ou j'ai écrit ça en pensant que ça
allait invoquer un autre constructeur pour le même objet :/

Merci de m'avoir poussé dans la bonne direction (comme la trace de la
sortie des méthodes), et bonne journée.

--
Bastien


Avatar
James Kanze
On Oct 5, 10:55 am, Bastien Durel wrote:
On 05/10/2007 09:37, James Kanze wrote:


[...]
Que sont les valeurs dans les parenthèses à la fin ? (Et si
c'est le cas, pourquoi apparaissent-elles comme des int ? Quand
je sors une adresse avec mon g++, sous Linux sur PC, les
adresses sont affichées en hex, comme si elles étaient des
unsigned de la taille correspondant.)


c'est this, casté en int vu que mon g++ 3.2/win me sortait
juste 1 sinon.


Il sortait un 1. On dirait qu'il y a une conversion en bool qui
s'est fait quelque part -- un pointeur *peut* se convertir en
bool, mais normalement, la conversion en void* doit avoir
précédence.

Devant un problème, j'aurais commencé avec une conversion en
void*. Mais il me semble qu'il y a eu des versions (assez
anciennes, et peut-être pas de g++, mais d'un autre compilateur)
où l'opérateur était déclaré prendre void*, et non void const*.
Du coup, si tu avais un pointeur à const, la conversion en bool
était la seule possible. Et effectivement, l'affichage disait
"1", ce qui ne t'avançait pas beaucoup. (Mais ça ne doit pas
être un problème pour le this dans un constructeur.)

[...]
En fait, c'était ça le problème une bête tentative d'économie
de code dans un de mes nombreux constructeurs,


Tu n'es pas le premier à le faire. Le besoin d'une telle chose
est réele, et il y aurait une syntaxe pour le supporter dans la
prochaine version de la norme, je crois. En attendant, il n'y a
que des fonctions. Ou... ce n'est pas une solution qui me plaît
des masses, mais tu peux parfois t'en tirer avec un constructeur
templaté, quelque chose du genre :

template< typename T >
MaClasse::MaClasse( T const& arg )
{
// partie commune...
partiePropreAuTypeDeLArg( arg ) ;
}

--
James Kanze (GABI Software) email:
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


1 2