 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
superbabar51@hotmail.com Guest
|
Posted: Thu May 12, 2005 6:57 pm Post subject: Destructeur scalaire |
|
|
Bonjour.
Lors de la destruction de la classe que vous trouverez ci-dessus, je me
retrouve avec une erreur de manipulation de mémoire. VC++7 renvoie:
HEAP[prog.exe]: Heap block at 088B0788 modified at 088B07B5 past
requested size of 25
Une inspection du call stack informe que le pb se produit lors d'un
delete emit par le "scalar destructing constructor".
Une idée?
Pour info, la classe est alloué ainsi:
CBSP *map = new("afile.bsp");
Et est detruite explicitement par
if(map)
delete map
class CBSP
{
public:
CBSP(string sFileName);
~CBSP();
void Load();
private:
/* fonctions */
string m_sFileName;
HANDLE m_hMapFile;
};
CBSP::~CBSP()
{
if(m_hMapFile)
{
CloseHandle(m_hMapFile);
m_hMapFile = NULL;
}
}
Merci!
|
|
| Back to top |
|
 |
Patrick 'Zener' Brunet Guest
|
Posted: Thu May 12, 2005 8:17 pm Post subject: Re: Destructeur scalaire |
|
|
Bonjour.
Je réponds à [email]superbabar51 (AT) hotmail (DOT) com[/email] <superbabar51 (AT) hotmail (DOT) com> qui a écrit
:
| Quote: | Bonjour.
Lors de la destruction de la classe que vous trouverez ci-dessus, je
me retrouve avec une erreur de manipulation de mémoire. VC++7 renvoie:
HEAP[prog.exe]: Heap block at 088B0788 modified at 088B07B5 past
requested size of 25
Une inspection du call stack informe que le pb se produit lors d'un
delete emit par le "scalar destructing constructor".
Une idée?
Pour info, la classe est alloué ainsi:
CBSP *map = new("afile.bsp");
|
Il fait quoi ce new ? Vous avez essayé de rentrer dedans au debugger
(jusqu'au constructeur) ?
S'il essaie de construire (quoi au juste ?) sur l'espace correspondant à la
chaîne constante, ça ne doit pas être triste... Et en tout cas le delete
standard ne s'applique pas dans ce contexte.
Vosu vouliez problablement écrire :
CBSP * map = new CBSP( "afile.bsp");
Cordialement,
--
/***************************************
* Patrick BRUNET
* E-mail: lien sur http://zener131.free.fr/ContactMe
***************************************/
|
|
| Back to top |
|
 |
Christophe de Vienne Guest
|
Posted: Fri May 13, 2005 9:06 am Post subject: Re: Destructeur scalaire |
|
|
[email]superbabar51 (AT) hotmail (DOT) com[/email] wrote:
[...]
| Quote: | Pour info, la classe est alloué ainsi:
CBSP *map = new("afile.bsp");
|
Le problème vient, je suppose, de là.
Il faut écrire, pour instancier la classe CBSP :
CBSP *map = new CBSP("afile.bsp");
A+
Christophe
|
|
| Back to top |
|
 |
superbabar51@hotmail.com Guest
|
Posted: Fri May 13, 2005 1:38 pm Post subject: Re: Destructeur scalaire |
|
|
Oups.. Je suis allé un peu vite en écrivant le message.
Le code est bien CBSP *map = new CBSP("afile.bsp").
Par contre, j'ai remarqué que le pb n'apparaissait pas si je ne
déclanchais pas d'exception dans le code. Il me manque probablement de
la théorie sur les exceptions.
La classe envoyé est EBSPLoad, définie comme hérité de Exception:
class Exception /* Une classe de base */
{
public:
Exception(const char* lpMessage, char* lpFileName, int iLineNb);
Exception(DWORD dwError, char* lpFileName, int iLineNb);
void Display();
protected:
string m_sMessage;
string m_sFileName;
int m_iLineNb;
};
class EBSPLoad : public Exception
{
public:
EBSPLoad(const char* lpMessage, char* lpFileName, int iLineNb) :
Exception(lpMessage, lpFileName, iLineNb) {};
EBSPLoad(DWORD dwError, char* lpFileName, int iLineNb) :
Exception(dwError, lpFileName, iLineNb) {};
};
Quel est le piège à c.. pour débutant (c'es tt comme) que je ne vois
pas ??
|
|
| Back to top |
|
 |
superbabar51@hotmail.com Guest
|
Posted: Fri May 13, 2005 1:43 pm Post subject: Re: Destructeur scalaire |
|
|
Oups.. Le code est bien
CBSP *map = new CBSP("afile.bsp");
(juste une faute de copie).
Par contre, j'ai remarqué que le pb n'apparaissait que lorsqu'une
exception CBSPLoad (cf plus bas) est déclanchée. Il doit se passer un
truc au niveau des constructeurs de classes dérivées auquel je ne
pense pas..
class Exception /*classe de base */
{
public:
Exception(const char* lpMessage, char* lpFileName, int iLineNb);
Exception(DWORD dwError, char* lpFileName, int iLineNb);
void Display();
protected:
string m_sMessage;
string m_sFileName;
int m_iLineNb;
};
class EBSPLoad : public Exception
{
public:
EBSPLoad(const char* lpMessage, char* lpFileName, int iLineNb) :
Exception(lpMessage, lpFileName, iLineNb) {};
EBSPLoad(DWORD dwError, char* lpFileName, int iLineNb) :
Exception(dwError, lpFileName, iLineNb) {};
};
Le tout sous VC++7
using std::string;
handler d'exception:
try {
map->Load();
}
catch(EBSPLoad bl)
{
bl.Display();
}
if(map)
delete map;
|
|
| Back to top |
|
 |
Patrick 'Zener' Brunet Guest
|
Posted: Fri May 13, 2005 2:51 pm Post subject: Re: Destructeur scalaire |
|
|
Bonjour.
Je réponds à [email]superbabar51 (AT) hotmail (DOT) com[/email] <superbabar51 (AT) hotmail (DOT) com> qui a écrit
:
| Quote: | Oups.. Le code est bien
CBSP *map = new CBSP("afile.bsp");
(juste une faute de copie).
Par contre, j'ai remarqué que le pb n'apparaissait que lorsqu'une
exception CBSPLoad (cf plus bas) est déclanchée. Il doit se passer un
truc au niveau des constructeurs de classes dérivées auquel je ne
pense pas..
class Exception /*classe de base */
{
public:
Exception(const char* lpMessage, char* lpFileName, int iLineNb);
Exception(DWORD dwError, char* lpFileName, int iLineNb);
void Display();
protected:
string m_sMessage;
string m_sFileName;
int m_iLineNb;
};
class EBSPLoad : public Exception
{
public:
EBSPLoad(const char* lpMessage, char* lpFileName, int iLineNb) :
Exception(lpMessage, lpFileName, iLineNb) {};
EBSPLoad(DWORD dwError, char* lpFileName, int iLineNb) :
Exception(dwError, lpFileName, iLineNb) {};
};
Le tout sous VC++7
using std::string;
handler d'exception:
try {
map->Load();
}
catch(EBSPLoad bl)
{
bl.Display();
}
if(map)
delete map;
|
AMHA il y a quelques gros problèmes de conception là-dedans :
1) Vous utilisez une exception qui contient elle-même des composants qui
font de l'allocation dynamique, et donc qui sont susceptible de déclencher
avec un e des exceptions : vous jouez avec le feu.
Il serait plus sage, autant que possible, de disposer d'une exception (voire
d'un petit pool) qui soit (entièrement) allouées à l'avance, de manière à ne
pas introduire d'éventualité d'échec.
2) Votre exception est transmise par copie semble-t-il, et non par adresse.
Cela implique tout un tas de cycles de création/copie/destruction de
chaînes, ce qui est d'une part très inefficace, et d'autre part remultiplie
les cas d'échec.
3) Je ne vois pas bien dans votre code où se fait la destruction de
l'exception elle-même. Il faut bien pourtant qu'elle ait lieu.
4) Pour ce qui est de sa création correcte, ça n'apparaît pas dans le code
soumis... Comment écrivez-vous les constructeurs, et l'instruction throw ?
5) Quand vous suivez votre code pas à pas au debugger (en entrant bien
partout), où exactement dans le code obtenez-vous l'erreur ?
Cordialement,
--
/***************************************
* Patrick BRUNET
* E-mail: lien sur http://zener131.free.fr/ContactMe
***************************************/
|
|
| Back to top |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Fri May 13, 2005 4:29 pm Post subject: Re: Destructeur scalaire |
|
|
Patrick 'Zener' Brunet wrote:
| Quote: | Je réponds à [email]superbabar51 (AT) hotmail (DOT) com[/email]
[email]superbabar51 (AT) hotmail (DOT) com[/email]> qui a écrit :
Oups.. Le code est bien
CBSP *map = new CBSP("afile.bsp");
(juste une faute de copie).
Par contre, j'ai remarqué que le pb n'apparaissait que
lorsqu'une exception CBSPLoad (cf plus bas) est déclanchée.
Il doit se passer un truc au niveau des constructeurs de
classes dérivées auquel je ne pense pas..
class Exception /*classe de base */
{
public:
Exception(const char* lpMessage, char* lpFileName, int iLineNb);
Exception(DWORD dwError, char* lpFileName, int iLineNb);
void Display();
protected:
string m_sMessage;
string m_sFileName;
int m_iLineNb;
};
class EBSPLoad : public Exception
{
public:
EBSPLoad(const char* lpMessage, char* lpFileName, int iLineNb) :
Exception(lpMessage, lpFileName, iLineNb) {};
EBSPLoad(DWORD dwError, char* lpFileName, int iLineNb) :
Exception(dwError, lpFileName, iLineNb) {};
};
Le tout sous VC++7
using std::string;
handler d'exception:
try {
map->Load();
}
catch(EBSPLoad bl)
{
bl.Display();
}
if(map)
delete map;
AMHA il y a quelques gros problèmes de conception là-dedans :
1) Vous utilisez une exception qui contient elle-même des
composants qui font de l'allocation dynamique, et donc qui
sont susceptible de déclencher avec un e des exceptions : vous
jouez avec le feu. Il serait plus sage, autant que possible,
de disposer d'une exception (voire d'un petit pool) qui soit
(entièrement) allouées à l'avance, de manière à ne pas
introduire d'éventualité d'échec.
|
Oui et non. Tout dépend de l'application -- souvent, on peut
raisonablement supposer qu'on n'épuisera pas la mémoire, et
aborter si on le fait (d'autant plus que souvent, on n'a pas le
choix -- le système d'exploitation le fait pour nous). Alors, on
positionne le new_handler, et on s'occupe d'autres problèmes.
Sinon, il faut certainement des classes faites sur mésure dans
l'exception, pour gerer des chaînes sans allocation dynamique.
| Quote: | 2) Votre exception est transmise par copie semble-t-il, et non
par adresse.
|
Toute exception est transmise par copie. Il le faut bien,
puisqu'on va déballer la pile, et la mémoire où l'utilisateur le
construit (comme temporaire) va disparaître.
| Quote: | Cela implique tout un tas de cycles de
création/copie/destruction de chaînes, ce qui est d'une part
très inefficace, et d'autre part remultiplie les cas d'échec.
|
Ça implique *une* copie, c'est tout. Qu'il l'attrappe par copie
implique une deuxième. Rien devant le coût d'une exception en
général.
| Quote: | 3) Je ne vois pas bien dans votre code où se fait la
destruction de l'exception elle-même. Il faut bien pourtant
qu'elle ait lieu.
|
C'est le problème du compilateur, non le sien. Le programmeur
n'a pas le droit de detruire l'exception lui-même.
| Quote: | 4) Pour ce qui est de sa création correcte, ça n'apparaît pas
dans le code soumis... Comment écrivez-vous les constructeurs,
et l'instruction throw ?
|
Il y a beaucoup qui n'apparaît pas dans ce qu'il a posté. Tout
ce qu'il a posté semble correct, mais on ne peut pas exclure une
erreur dans les autres parties.
| Quote: | 5) Quand vous suivez votre code pas à pas au debugger (en
entrant bien partout), où exactement dans le code obtenez-vous
l'erreur ?
|
D'après ce qu'il a dit, il obtient l'erreur bien après ce
qu'elle a eu lieu. C'est le propre des erreurs de ce genre. Et
un déboggueur y est à peu près inutile.
Ce qu'il lui faut, évidemment, c'est quelque chose comme Purify.
Mais ce n'est pas donné, s'il est étudiant ou programmeur
amateur. (S'il est programmeur professionnel, évidemment, sa
boîte ferait bien de l'acheter, malgré le prix. Parce qu'elle
regagnera largement le prix en temps de programmeur gagné.)
--
James Kanze GABI Software
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
|
|
| Back to top |
|
 |
Patrick 'Zener' Brunet Guest
|
Posted: Fri May 13, 2005 8:46 pm Post subject: Re: Destructeur scalaire |
|
|
Bonjour.
Je réponds à [email]kanze (AT) gabi-soft (DOT) fr[/email] <kanze (AT) gabi-soft (DOT) fr> qui a écrit :
| Quote: | Patrick 'Zener' Brunet wrote:
Je réponds à [email]superbabar51 (AT) hotmail (DOT) com[/email]
[email]superbabar51 (AT) hotmail (DOT) com[/email]> qui a écrit :
[...] le code
AMHA il y a quelques gros problèmes de conception là-dedans :
1) Vous utilisez une exception qui contient elle-même des
composants qui font de l'allocation dynamique, et donc qui
sont susceptible de déclencher avec un e des exceptions : vous
jouez avec le feu. Il serait plus sage, autant que possible,
de disposer d'une exception (voire d'un petit pool) qui soit
(entièrement) allouées à l'avance, de manière à ne pas
introduire d'éventualité d'échec.
Oui et non. Tout dépend de l'application -- souvent, on peut
raisonablement supposer qu'on n'épuisera pas la mémoire, et
aborter si on le fait (d'autant plus que souvent, on n'a pas le
choix -- le système d'exploitation le fait pour nous). Alors, on
positionne le new_handler, et on s'occupe d'autres problèmes.
Sinon, il faut certainement des classes faites sur mésure dans
l'exception, pour gerer des chaînes sans allocation dynamique.
|
Il m'est arrivé de développer de telles classes : elles sont construites
comme des objets globaux, sans allocation de mémoire dynamique, et donc
elles ne sont plus jamais construites ni détruites : seulement
réquisitionnées puis restituées (elles fournissent une méthode static
Throw() ).
Pour ce qui est de la chaîne message, en fait j'avais défini un protocole
permettant de stocker dans le corps de l'exception les éléments variants (2
entiers et un tableau de 127 + 1 caractères), la classe Exception offrant
une fonction type sprintf() contrôlée pour utiliser ça.
Ca permet donc de justifier ma réponse à la suite...
Sinon pour ce qui est de l'abort(), étant plutôt orienté
informatique-industrielle-sans-renoncement-à-la-mémoire-dynamique,
évidemment c'est hors de question (et donc il faudra que je me remette un
peu au courant des évolutions de la gestion "optimiste" de mémoire sous
Linux depuis 2 ans).
| Quote: | 2) Votre exception est transmise par copie semble-t-il, et non
par adresse.
Toute exception est transmise par copie. Il le faut bien,
puisqu'on va déballer la pile, et la mémoire où l'utilisateur le
construit (comme temporaire) va disparaître.
|
Avec ma convention précédente, justement, c'était un pointeur vers
l'exception qui était transmis, lui donc par copie en effet.
| Quote: | Cela implique tout un tas de cycles de
création/copie/destruction de chaînes, ce qui est d'une part
très inefficace, et d'autre part remultiplie les cas d'échec.
Ça implique *une* copie, c'est tout. Qu'il l'attrappe par copie
implique une deuxième. Rien devant le coût d'une exception en
général.
|
Et s'il utilise throw; dans un bloc catch() pour la relancer ?
Je me souviens-là des tests que j'ai pu faire lors de la mise au point d'une
classe genre "string", au niveau de l'opérateur d'affectation et du
constructeur de copie (et en prenant en compte l'instance anonyme
transitoire lorsqu'on retourne un objet entier plutôt qu'un pointeur). Donc,
selon comment cette instance d'exception est construite, remplie puis
"lancée", j'hésite sur le nombre possible de copies.
| Quote: | 3) Je ne vois pas bien dans votre code où se fait la
destruction de l'exception elle-même. Il faut bien pourtant
qu'elle ait lieu.
C'est le problème du compilateur, non le sien. Le programmeur
n'a pas le droit de detruire l'exception lui-même.
|
Donc toujours avec la même convention, le compilo fait ce qu'il veut du
pointeur qui est transmis, mais si un bloc catch() ne relance pas
l'exception, elle doit s'arrêter là.
Dans mes classes, il y avait donc à ce niveau un pE->Release(), comme ça se
fait dans les classes d'exceptions des MFC par exemple (quoiqu'on puisse en
dire). Dès lors que l'exception n'est pas atomique et nécessite une
destruction (ou restitution dans mon cas) "évoluée", finalement je préfère.
| Quote: | 4) Pour ce qui est de sa création correcte, ça n'apparaît pas
dans le code soumis... Comment écrivez-vous les constructeurs,
et l'instruction throw ?
Il y a beaucoup qui n'apparaît pas dans ce qu'il a posté. Tout
ce qu'il a posté semble correct, mais on ne peut pas exclure une
erreur dans les autres parties.
|
Oui, je voudrais voir le throw...
| Quote: | 5) Quand vous suivez votre code pas à pas au debugger (en
entrant bien partout), où exactement dans le code obtenez-vous
l'erreur ?
D'après ce qu'il a dit, il obtient l'erreur bien après ce
qu'elle a eu lieu. C'est le propre des erreurs de ce genre. Et
un déboggueur y est à peu près inutile.
|
Je n'ai pas pu déterminer ce qui était en train d'être détruit, ni à quel
moment. Ca manque...
| Quote: | Ce qu'il lui faut, évidemment, c'est quelque chose comme Purify.
Mais ce n'est pas donné, s'il est étudiant ou programmeur
amateur. (S'il est programmeur professionnel, évidemment, sa
boîte ferait bien de l'acheter, malgré le prix. Parce qu'elle
regagnera largement le prix en temps de programmeur gagné.)
|
Bienheureux les concepteurs qui ont réussi à recruter un employeur sérieux !
Moi je suis à nouveau en train de chercher, et j'espère que je ne vais pas à
nouveau devoir rafistoler les moutons crevés laissés par le type d'avant, la
conception des produits d'avenir de la boîte étant dans le même temps
confiée à des bricolos pour raisons de budget :-@
Heureusement qu'on peut encore faire de la R&D sérieuse durant ses loisirs
:-D
Cordialement,
--
/***************************************
* Patrick BRUNET
* E-mail: lien sur http://zener131.free.fr/ContactMe
***************************************/
|
|
| Back to top |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Mon May 16, 2005 7:42 am Post subject: Re: Destructeur scalaire |
|
|
Patrick 'Zener' Brunet wrote:
| Quote: | Je réponds à [email]kanze (AT) gabi-soft (DOT) fr[/email] <kanze (AT) gabi-soft (DOT) fr> qui a écrit :
Patrick 'Zener' Brunet wrote:
Je réponds à [email]superbabar51 (AT) hotmail (DOT) com[/email]
[email]superbabar51 (AT) hotmail (DOT) com[/email]> qui a écrit :
[...] le code
AMHA il y a quelques gros problèmes de conception là-dedans :
1) Vous utilisez une exception qui contient elle-même des
composants qui font de l'allocation dynamique, et donc qui
sont susceptible de déclencher avec un e des exceptions :
vous jouez avec le feu. Il serait plus sage, autant que
possible, de disposer d'une exception (voire d'un petit
pool) qui soit (entièrement) allouées à l'avance, de
manière à ne pas introduire d'éventualité d'échec.
Oui et non. Tout dépend de l'application -- souvent, on peut
raisonablement supposer qu'on n'épuisera pas la mémoire, et
aborter si on le fait (d'autant plus que souvent, on n'a pas
le choix -- le système d'exploitation le fait pour nous).
Alors, on positionne le new_handler, et on s'occupe d'autres
problèmes.
Sinon, il faut certainement des classes faites sur mésure
dans l'exception, pour gerer des chaînes sans allocation
dynamique.
Il m'est arrivé de développer de telles classes : elles sont
construites comme des objets globaux, sans allocation de
mémoire dynamique, et donc elles ne sont plus jamais
construites ni détruites : seulement réquisitionnées puis
restituées (elles fournissent une méthode static Throw() ).
|
Je ne comprends pas trop cette association pas d'allocation
dynamique -- objet global. J'ai bien des objets globaux qui font
de l'allocation dynamique. (J'ai même parfois des std::string
globaux.)
| Quote: | Pour ce qui est de la chaîne message, en fait j'avais défini
un protocole permettant de stocker dans le corps de
l'exception les éléments variants (2 entiers et un tableau de
127 + 1 caractères), la classe Exception offrant une fonction
type sprintf() contrôlée pour utiliser ça.
|
Tu n'as pas besoin d'un protocol, parce que tu pourrais réserver
aussi un tableau de caractères dans n'importe quelle classe
dérivée. (Tableau de type, s'entend, pourqu'il n'y a pas
d'allocation dynamique.) Certaines implémentations des
std::exception le font, je crois ; si tu passes un chaîne plus
long que le tableau fixe, elles essaient d'obtenir de la mémoire
dynamique (avec new (nothrow)), et si ça échoue, elles tronquent
la chaîne.
Mais ça n'évite pas totallement le besoin des allocations
dynamique. Le problème, c'est qu'il faut que le système stocke
l'exception même quelque part. Et que ça ne peut pas être sur la
pile, parce qu'elle fait detruire la pile. Et que ça ne peut pas
être en mémoire statique, parce qu'il peut y avoir d'autres
exceptions lors du stack unwinding. (A propos : comment est-ce
qu'on dit « stack unwinding » en français ?) Alors,
l'implémentation est obligée à allouer la mémoire pour
l'exception dynamiquement. (En général, l'implémentation utilise
un tas ou une pile à part pour ça. Et certainement pas operator
new. Mais c'est dynamique quand même.)
| Quote: | Ca permet donc de justifier ma réponse à la suite...
Sinon pour ce qui est de l'abort(), étant plutôt orienté
informatique-industrielle-sans-renoncement-à-la-mémoire-dynamique,
évidemment c'est hors de question (et donc il faudra que je me
remette un peu au courant des évolutions de la gestion
"optimiste" de mémoire sous Linux depuis 2 ans).
|
Je suis sceptique en ce qui concerne les possibilités d'un
logiciel critique avec utilisation de la mémoire dynamique.
Mais il y a bien des programmes industriels qui ne sont pas
critiques, et qui peuvent supporter un abort, pourvu que la
probabilité ou la fréquence en soit assez faible. (À vrai dire,
tout système critique doit être secouru, aussi. De façon à ce
qu'un crash ne soit pas fatal pour le système entier.) De toute
façon, si tu te bases sur un système d'exploitation courante,
Windows, ou un Unix, il faut l'accepter, parce qu'ils abortent
tous le processus si l'épuisement de la mémoire se produit lors
de l'agrandissement de la pile.
Aussi, on accepte souvent à prendre la « risque », en s'assurant
qu'il n'y a que l'application en question qui tourne sur la
machine, et que la machine a assez de mémoire pour ne pas en
épuiser.
| Quote: | 2) Votre exception est transmise par copie semble-t-il, et
non par adresse.
Toute exception est transmise par copie. Il le faut bien,
puisqu'on va déballer la pile, et la mémoire où
l'utilisateur le construit (comme temporaire) va
disparaître.
Avec ma convention précédente, justement, c'était un pointeur
vers l'exception qui était transmis, lui donc par copie en
effet.
|
D'accord. Tu as donc un objet statique, et tu lèves une
exception du type pointeur à l'objet. Seulement, qu'est-ce qui
se passe si les exceptions s'imbriquent, et que tu veux lever
une deuxième exception du même type, alors que la première est
toujours active ? Ou est-ce que tu as conçu l'application pour
que le cas ne peut pas se produire ?
| Quote: | Cela implique tout un tas de cycles de
création/copie/destruction de chaînes, ce qui est d'une
part très inefficace, et d'autre part remultiplie les cas
d'échec.
Ça implique *une* copie, c'est tout. Qu'il l'attrappe par
copie implique une deuxième. Rien devant le coût d'une
exception en général.
Et s'il utilise throw; dans un bloc catch() pour la relancer ?
|
Pas de copie, je crois. Je ne suis pas sûr, mais je crois que le
throw sans paramètres est garantie de réutiliser l'objet
existant.
| Quote: | Je me souviens-là des tests que j'ai pu faire lors de la mise
au point d'une classe genre "string", au niveau de l'opérateur
d'affectation et du constructeur de copie (et en prenant en
compte l'instance anonyme transitoire lorsqu'on retourne un
objet entier plutôt qu'un pointeur). Donc, selon comment cette
instance d'exception est construite, remplie puis "lancée",
j'hésite sur le nombre possible de copies.
|
Le nombre de copies n'est de toute façon pas bien spécifié.
Conceptuellement, tu construis l'objet « sur la pile » avec une
expression du genre X(), et ensuite, cet objet est copié
ailleurs. Mais un compilateur a le droit de faire en sort que
l'objet temporaire que tu construis, et la copie « ailleurs »,
sont en fait le même objet, et supprimer l'action de copier.
Si tu attrappes l'exception par référence, en revanche,
l'implémentation n'a pas droit à une autre copie. Et si je ne me
trompe pas, c'est aussi vrai si tu relèves la même exception
avec un throw sans paramètres dans un bloc de catch.
| Quote: | 3) Je ne vois pas bien dans votre code où se fait la
destruction de l'exception elle-même. Il faut bien pourtant
qu'elle ait lieu.
C'est le problème du compilateur, non le sien. Le
programmeur n'a pas le droit de detruire l'exception
lui-même.
Donc toujours avec la même convention, le compilo fait ce
qu'il veut du pointeur qui est transmis, mais si un bloc
catch() ne relance pas l'exception, elle doit s'arrêter là.
|
Tout à fait. En fait, il est garantie que le destructeur de
l'objet levé soit appelé. Et pas avant la fin du dernier bloc de
catch qui le concerne. Je crois même que le moment exact de la
destruction est fixée, mais je ne suis pas 100% sûr.
En revanche, si tu lèves des pointeurs, c'est un pointeur qui
est detruit, et la destruction d'un pointeur est en fait un
no-op.
| Quote: | Dans mes classes, il y avait donc à ce niveau un
pE->Release(), comme ça se fait dans les classes d'exceptions
des MFC par exemple (quoiqu'on puisse en dire). Dès lors que
l'exception n'est pas atomique et nécessite une destruction
(ou restitution dans mon cas) "évoluée", finalement je
préfère.
4) Pour ce qui est de sa création correcte, ça n'apparaît
pas dans le code soumis... Comment écrivez-vous les
constructeurs, et l'instruction throw ?
Il y a beaucoup qui n'apparaît pas dans ce qu'il a posté.
Tout ce qu'il a posté semble correct, mais on ne peut pas
exclure une erreur dans les autres parties.
Oui, je voudrais voir le throw...
5) Quand vous suivez votre code pas à pas au debugger (en
entrant bien partout), où exactement dans le code
obtenez-vous l'erreur ?
D'après ce qu'il a dit, il obtient l'erreur bien après ce
qu'elle a eu lieu. C'est le propre des erreurs de ce genre.
Et un déboggueur y est à peu près inutile.
Je n'ai pas pu déterminer ce qui était en train d'être
détruit, ni à quel moment. Ca manque...
|
Un peu d'instrumentation du code, ça ne fait jamais mal.
| Quote: | Ce qu'il lui faut, évidemment, c'est quelque chose comme
Purify. Mais ce n'est pas donné, s'il est étudiant ou
programmeur amateur. (S'il est programmeur professionnel,
évidemment, sa boîte ferait bien de l'acheter, malgré le
prix. Parce qu'elle regagnera largement le prix en temps de
programmeur gagné.)
Bienheureux les concepteurs qui ont réussi à recruter un
employeur sérieux !
|
En effet. J'ai vu le problème plus d'une fois. On a une équipe
de programmeurs. Qui coûte, avec les charges et al., environ
500 000 Euros. Et on réfuse l'achat d'un outil à 10 000 Euros
qui leur ferait gagner 20% de temps -- c-à-d 100 000 Euros par
an.
Mais d'après ce que je vois, la situation s'améliore, et que
l'utilisation de Purify est quasi-universelle chez mes clients
aujourd'hui.
| Quote: | Moi je suis à nouveau en train de chercher, et j'espère que je
ne vais pas à nouveau devoir rafistoler les moutons crevés
laissés par le type d'avant, la conception des produits
d'avenir de la boîte étant dans le même temps confiée à des
bricolos pour raisons de budget :-@
Heureusement qu'on peut encore faire de la R&D sérieuse durant
ses loisirs
|
Figure-toi qu'il y a, aujourd'hui, des boîtes qui savent estîmer
un travail bien fait, et qui sont prêt à donner aux développeurs
ce qu'il faut pour le faire. (Évidemment, elles n'embauchent pas
tous les jours. Mais en cherchant assez longtemps, on finit par
les dénicher.)
--
James Kanze GABI Software
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
|
|
| Back to top |
|
 |
superbabar51@hotmail.com Guest
|
Posted: Mon May 16, 2005 7:06 pm Post subject: Re: Destructeur scalaire |
|
|
Bonsoir,
Je ne vous ferai pas perdre plus votre temps.
En effet, l'exception est maintenant déclanchée par le constructeur
et non plus par le destructeur alors que je n'ai pas changé le code
(moi rien comprendre). Le pb problème doit donc être plus général.
Va falloir revoir l'"architecture" du programme. (au fait, étudiant et
pas programmeur... Mais deux ans sans codé et on oublie vite la
théorie et les bases: analyser avant programmer)
Pour ceux que ça interesse.. (j'aimerai bien comprendre comme même en
fait )
CBSP::CBSP(string sFileName)
{
/* ... */
if(sFileName[0] != '\' && sFileName[1] != ':')
{
// then sFileName probably does NOT contain a full path to the file
m_sFileName = Config::GameDir;
m_sFileName = m_sFileName + "\Maps\";
/************ Exception déclanchée par cette ligne */
m_sFileName = m_sFileName + sFileName;
} else
m_sFileName = sFileName;
}
Voici le "Call Stack":
CrazyMarket.exe!strlen() Line 78 Asm
CrazyMarket.exe!exception::exception(const exception & that={...}) +
0x31 C++
CrazyMarket.exe!std::bad_alloc::bad_alloc(const std::bad_alloc &
__that={...}) + 0x13 C++
CrazyMarket.exe!std::_Nomemory() Line 9 + 0xd C++
CrazyMarket.exe!operator new(unsigned int size=21) Line 15 C++
| Quote: | CrazyMarket.exe!std::_Allocate<char>(unsigned int _Count=21, char *
__formal=0x00000000) Line 34 + 0x9 C++ |
CrazyMarket.exe!std::allocator<char>::allocate(unsigned int
_Count=21) Line 138 C++
CrazyMarket.exe!std::basic_string<char,std::char_traits
| Quote: | ::_Copy(unsigned int _Newsize=20, unsigned int _Oldlen=0) Line 1458 +
0xf C++ |
CrazyMarket.exe!std::basic_string<char,std::char_traits
| Quote: | ::_Grow(unsigned int _Newsize=20, bool _Trim=false) Line 1485 C++
|
CrazyMarket.exe!std::basic_string<char,std::char_traits
| Quote: | ::assign(const
std::basic_string<char,std::char_traits & |
_Right={...}, unsigned int _Roff=0, unsigned int _Count=4294967295)
Line 599 + 0x10 C++
CrazyMarket.exe!std::basic_string<char,std::char_traits
| Quote: | ::assign(const
std::basic_string<char,std::char_traits & |
_Right={...}) Line 586 C++
CrazyMarket.exe!std::basic_string<char,std::char_traits
| Quote: | ::operator=(const
std::basic_string<char,std::char_traits & |
_Right={...}) Line 468 C++
CrazyMarket.exe!CBSP::CBSP(std::basic_string<char,std::char_traits
| Quote: | sFileName={...}) Line 58 C++
CrazyMarket.exe!WinMain(HINSTANCE__ * hInstance=0x00400000, |
HINSTANCE__ * hPrevInstance=0x00000000, char * lpCmdLine=0x00141f07,
int nCmdShow=1) Line 29 + 0x4f C++
CrazyMarket.exe!WinMainCRTStartup() Line 251 + 0x30 C
kernel32.dll!7c816d4f()
ntdll.dll!7c925b4f()
kernel32.dll!7c8399f3()
exception nomemory? alors que le stack contient seulement d'une string
(et en plus, les chaines en elle même sont en mémoire dynamique,
corrigez si je me trompe)
Dans tous les cas:
Merci à tous pour vos postes!
|
|
| Back to top |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Tue May 17, 2005 9:30 am Post subject: Re: Destructeur scalaire |
|
|
[email]superbabar51 (AT) hotmail (DOT) com[/email] wrote:
| Quote: | En effet, l'exception est maintenant déclanchée par le
constructeur et non plus par le destructeur alors que je n'ai
pas changé le code (moi rien comprendre). Le pb problème doit
donc être plus général. Va falloir revoir l'"architecture" du
programme. (au fait, étudiant et pas programmeur... Mais deux
ans sans codé et on oublie vite la théorie et les bases:
analyser avant programmer)
Pour ceux que ça interesse.. (j'aimerai bien comprendre comme
même en fait )
CBSP::CBSP(string sFileName)
{
/* ... */
if(sFileName[0] != '\' && sFileName[1] != ':')
{
// then sFileName probably does NOT contain a full path to the file
m_sFileName = Config::GameDir;
m_sFileName = m_sFileName + "\Maps\";
/************ Exception déclanchée par cette ligne */
m_sFileName = m_sFileName + sFileName;
} else
m_sFileName = sFileName;
}
|
Qui ne comporte pas d'erreur visible. (Je suppose que
m_sFileName est une variable membre de type std::string.)
| Quote: | Voici le "Call Stack":
CrazyMarket.exe!strlen() Line 78 Asm
CrazyMarket.exe!exception::exception(const exception & that={...})
+
0x31 C++
CrazyMarket.exe!std::bad_alloc::bad_alloc(const std::bad_alloc &
__that={...}) + 0x13 C++
|
Il manque de mémoire.
| Quote: | CrazyMarket.exe!std::_Nomemory() Line 9 + 0xd C++
CrazyMarket.exe!operator new(unsigned int size=21) Line 15 C++
CrazyMarket.exe!std::_Allocate<char>(unsigned int _Count=21, char *
__formal=0x00000000) Line 34 + 0x9 C++
CrazyMarket.exe!std::allocator<char>::allocate(unsigned int
_Count=21) Line 138 C++
CrazyMarket.exe!std::basic_string<char,std::char_traits
::_Copy(unsigned int _Newsize=20, unsigned int _Oldlen=0) Line 1458
+
0xf C++
CrazyMarket.exe!std::basic_string
::_Grow(unsigned int _Newsize=20, bool _Trim=false) Line 1485 C++
CrazyMarket.exe!std::basic_string
::assign(const
std::basic_string
&
_Right={...}, unsigned int _Roff=0, unsigned int _Count=4294967295)
Line 599 + 0x10 C++
CrazyMarket.exe!std::basic_string
::assign(const
std::basic_string
&
_Right={...}) Line 586 C++
CrazyMarket.exe!std::basic_string
::operator=(const
std::basic_string
&
_Right={...}) Line 468 C++
CrazyMarket.exe!CBSP::CBSP(std::basic_string
sFileName={...}) Line 58 C++
CrazyMarket.exe!WinMain(HINSTANCE__ * hInstance=0x00400000,
HINSTANCE__ * hPrevInstance=0x00000000, char * lpCmdLine=0x00141f07,
int nCmdShow=1) Line 29 + 0x4f C++
CrazyMarket.exe!WinMainCRTStartup() Line 251 + 0x30 C
kernel32.dll!7c816d4f()
ntdll.dll!7c925b4f()
kernel32.dll!7c8399f3()
exception nomemory? alors que le stack contient seulement
d'une string (et en plus, les chaines en elle même sont en
mémoire dynamique, corrigez si je me trompe)
|
C'est effectivement une exception due à une manque de mémoire,
detecter lors d'une allocation pour créer une chaîne. Il peut y
avoir deux explications : ou bien, tu as quelque chose ailleurs
qui a bouffé toute la mémoire, ou bien, tu as fait quelque chose
avant qui a corrompu l'espace où la mémoire libre se trouve, de
façon à ce que l'allocateur n'en trouve plus.
Dans les deux cas, la cause réele du problème se trouve en
amont.
--
James Kanze GABI Software
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
|
|
| Back to top |
|
 |
Patrick 'Zener' Brunet Guest
|
Posted: Tue May 17, 2005 9:53 am Post subject: Re: Destructeur scalaire |
|
|
Bonjour.
Je réponds à [email]superbabar51 (AT) hotmail (DOT) com[/email] <superbabar51 (AT) hotmail (DOT) com> qui a écrit
:
| Quote: | Bonsoir,
Je ne vous ferai pas perdre plus votre temps.
En effet, l'exception est maintenant déclanchée par le constructeur
et non plus par le destructeur alors que je n'ai pas changé le code
(moi rien comprendre). Le pb problème doit donc être plus général.
Va falloir revoir l'"architecture" du programme. (au fait, étudiant et
pas programmeur... Mais deux ans sans codé et on oublie vite la
théorie et les bases: analyser avant programmer)
Pour ceux que ça interesse.. (j'aimerai bien comprendre comme même en
fait )
[..]
CrazyMarket.exe!std::basic_string<char,std::char_traits |
<char>
| Quote: | assign(const
std::basic_string<char,std::char_traits &
_Right={...}, unsigned int _Roff=0, unsigned int _Count=4294967295)
Line 599 + 0x10 C++
|
Ca j'aime pas : unsigned int _Count=4294967295
Je ne connais pas la classe, mais y'aurait pas un -1 dans cet argument pour
une raison non appropriée ?
Repassez donc la call stack en détail au debugger pour voir d'où il vient.
Parce que dans ce cas ça fait un peu gros à allouer ou à copier...
Cordialement,
--
/***************************************
* Patrick BRUNET
* E-mail: lien sur http://zener131.free.fr/ContactMe
***************************************/
|
|
| Back to top |
|
 |
Patrick 'Zener' Brunet Guest
|
Posted: Tue May 17, 2005 10:36 am Post subject: Re: Classes Exceptions (was Destructeur scalaire) |
|
|
Bonjour.
Je réponds à [email]kanze (AT) gabi-soft (DOT) fr[/email] <kanze (AT) gabi-soft (DOT) fr> qui a écrit :
| Quote: | Patrick 'Zener' Brunet wrote:
Je réponds à [email]kanze (AT) gabi-soft (DOT) fr[/email] <kanze (AT) gabi-soft (DOT) fr> qui a écrit :
Patrick 'Zener' Brunet wrote:
Je réponds à [email]superbabar51 (AT) hotmail (DOT) com[/email]
[email]superbabar51 (AT) hotmail (DOT) com[/email]> qui a écrit :
[...]
Sinon, il faut certainement des classes faites sur mésure
dans l'exception, pour gerer des chaînes sans allocation
dynamique.
Il m'est arrivé de développer de telles classes : elles sont
construites comme des objets globaux, sans allocation de
mémoire dynamique, et donc elles ne sont plus jamais
construites ni détruites : seulement réquisitionnées puis
restituées (elles fournissent une méthode static Throw() ).
Je ne comprends pas trop cette association pas d'allocation
dynamique -- objet global. J'ai bien des objets globaux qui font
de l'allocation dynamique. (J'ai même parfois des std::string
globaux.)
|
Je veux dire objets globaux **et** ne faisant pas d'allocation dynamique,
afin de n'avoir aucune possiblité d'échec à la construction.
| Quote: | Pour ce qui est de la chaîne message, en fait j'avais défini
un protocole permettant de stocker dans le corps de
l'exception les éléments variants (2 entiers et un tableau de
127 + 1 caractères), la classe Exception offrant une fonction
type sprintf() contrôlée pour utiliser ça.
Tu n'as pas besoin d'un protocol, parce que tu pourrais réserver
aussi un tableau de caractères dans n'importe quelle classe
dérivée. (Tableau de type, s'entend, pourqu'il n'y a pas
d'allocation dynamique.) Certaines implémentations des
std::exception le font, je crois ; si tu passes un chaîne plus
long que le tableau fixe, elles essaient d'obtenir de la mémoire
dynamique (avec new (nothrow)), et si ça échoue, elles tronquent
la chaîne.
|
J'ai pris en compte aussi le critère de vitesse, et par ailleurs j'ai voulu
totalement découpler ça de l'allocation dynamique parce que mes classes
travaillent avec toute une variété de heaps (positionnement et algorithme
d'allocation). Donc j'ai mis les exception à part.
Partant du principe qu'on n'a pas besoin d'une chaîne volumineuse pour cet
usage, j'ai utilisé une classe à moi qui permet de faire du CString sur un
tableau statique, avec une taille figée (constante de précompilation). Le
pool d'exceptions préallouées ne représente plus rien dans le contexte d'un
logiciel "moderne".
| Quote: | Mais ça n'évite pas totallement le besoin des allocations
dynamique. Le problème, c'est qu'il faut que le système stocke
l'exception même quelque part. Et que ça ne peut pas être sur la
pile, parce qu'elle fait detruire la pile. Et que ça ne peut pas
être en mémoire statique, parce qu'il peut y avoir d'autres
exceptions lors du stack unwinding. (A propos : comment est-ce
qu'on dit « stack unwinding » en français ?) Alors,
l'implémentation est obligée à allouer la mémoire pour
l'exception dynamiquement. (En général, l'implémentation utilise
un tas ou une pile à part pour ça. Et certainement pas operator
new. Mais c'est dynamique quand même.)
|
J'ai prévu un pool d'exceptions recyclables, et donc la fonction Throw en
réquisitionne une qui ne le soit pas déjà. Cela pose évidemment la question
de la taille de ce pool. J'ai opté pour un dimensionnement par constante de
précompilation bien sûr, et comme mes classes sont conçue pour minimiser les
cascades d'échecs (elles font de la récupération une priorité et donc
évitent d'agraver la situation), j'ai décidé qu'au-delà de 5 exceptions en
cours, on dépassait largement les bornes du raisonnable.
| Quote: | Ca permet donc de justifier ma réponse à la suite...
Sinon pour ce qui est de l'abort(), étant plutôt orienté
informatique-industrielle-sans-renoncement-à-la-mémoire-dynamique,
évidemment c'est hors de question (et donc il faudra que je me
remette un peu au courant des évolutions de la gestion
"optimiste" de mémoire sous Linux depuis 2 ans).
Je suis sceptique en ce qui concerne les possibilités d'un
logiciel critique avec utilisation de la mémoire dynamique.
|
Critique temps-réel, je pense aussi. Mais sans aller jusque là, compte tenu
du coût et de l'aspect spartiate des systèmes résolument industriels, il
n'est pas rare de confier à un PC la supervision d'un process avec des
contraintes temps-réel assez fortes. Nous l'avons fait dans des
installations de supervision de tri ou de préparation de commande par
exemple (avec des taux de l'ordre de 6 transactions par secondes, chacune
impliquant un moulinage de structures en mémoire, des transferts série vers
un automate, réseau vers un AS400, et une ou deux transactions en base de
données genre Sybase ou Oracle.
Ca marche très bien sur un PC correctement dimensionné, mais bien sûr il est
hors de question de jouer ou de surfer sur le net en même temps !
| Quote: | Mais il y a bien des programmes industriels qui ne sont pas
critiques, et qui peuvent supporter un abort, pourvu que la
probabilité ou la fréquence en soit assez faible. (À vrai dire,
tout système critique doit être secouru, aussi. De façon à ce
qu'un crash ne soit pas fatal pour le système entier.) De toute
façon, si tu te bases sur un système d'exploitation courante,
Windows, ou un Unix, il faut l'accepter, parce qu'ils abortent
tous le processus si l'épuisement de la mémoire se produit lors
de l'agrandissement de la pile.
|
Pas trop d'accord : s'il y a une exception prévue dans operator new, ça
suppose qu'on devrait avoir une chance d'en tirer parti, et je me suis donné
beaucoup de mal pour ça. Je sais écrire des applications qui sont
conscientes de leurs ressources et qui savent se récupérer sur des
situations dégradées.
Alors bien sûr quand j'ai appris que sous Linux il y avait un abort() dans
le new_handler parce que le système faisait le béni-oui-oui et tuait des
process quand il se trouvait au pied du mur, évidemment j'ai trouvé ça
plutôt ... NULL. J'espère que ça a évolué.
Il faut savoir aussi que certains process (et pas seulement dans le domaine
de l'I.A.) mettent en jeu des structures âvec un tel entrelacement de
pointeurs, qu'il est peu envisageable (ou au moins très laborieux) de les
reconstruire après un crash). J'ai dû inventer une technologie pour offrir
cette immunité à un logiciel de la gamme ci-dessus, dans le contexte d'une
éventuelle panne de courant. Ca marche bien même à plein régime. Mais
évidemment si le système se met à faire du sabordage aléatoire, on n'a plus
qu'à chercher un portage d'Oracle sur rack OS/9 !
| Quote: | Aussi, on accepte souvent à prendre la « risque », en s'assurant
qu'il n'y a que l'application en question qui tourne sur la
machine, et que la machine a assez de mémoire pour ne pas en
épuiser.
|
Su le genre de système évoqué, ça passe aussi par une bonne gestion de
l'émiettement, c'est ce qui me conduit à utiliser des heaps spéciaux - et
aussi des fichiers, l'expérience martienne l'a montré :-D
| Quote: | 2) Votre exception est transmise par copie semble-t-il, et
non par adresse.
Toute exception est transmise par copie. Il le faut bien,
puisqu'on va déballer la pile, et la mémoire où
l'utilisateur le construit (comme temporaire) va
disparaître.
Avec ma convention précédente, justement, c'était un pointeur
vers l'exception qui était transmis, lui donc par copie en
effet.
D'accord. Tu as donc un objet statique, et tu lèves une
exception du type pointeur à l'objet. Seulement, qu'est-ce qui
se passe si les exceptions s'imbriquent, et que tu veux lever
une deuxième exception du même type, alors que la première est
toujours active ? Ou est-ce que tu as conçu l'application pour
que le cas ne peut pas se produire ?
|
Il y a donc un pool d'exceptions de taille "raisonnable".
| Quote: | Cela implique tout un tas de cycles de
création/copie/destruction de chaînes, ce qui est d'une
part très inefficace, et d'autre part remultiplie les cas
d'échec.
Ça implique *une* copie, c'est tout. Qu'il l'attrappe par
copie implique une deuxième. Rien devant le coût d'une
exception en général.
Et s'il utilise throw; dans un bloc catch() pour la relancer ?
Pas de copie, je crois. Je ne suis pas sûr, mais je crois que le
throw sans paramètres est garantie de réutiliser l'objet
existant.
Je me souviens-là des tests que j'ai pu faire lors de la mise
au point d'une classe genre "string", au niveau de l'opérateur
d'affectation et du constructeur de copie (et en prenant en
compte l'instance anonyme transitoire lorsqu'on retourne un
objet entier plutôt qu'un pointeur). Donc, selon comment cette
instance d'exception est construite, remplie puis "lancée",
j'hésite sur le nombre possible de copies.
Le nombre de copies n'est de toute façon pas bien spécifié.
Conceptuellement, tu construis l'objet « sur la pile » avec une
expression du genre X(), et ensuite, cet objet est copié
ailleurs. Mais un compilateur a le droit de faire en sort que
l'objet temporaire que tu construis, et la copie « ailleurs »,
sont en fait le même objet, et supprimer l'action de copier.
|
La pire séquece que j'ai pu tracer au debugger était la suivante :
CString ab;
....
ab = CString( "aaaaa") + "bbbbb";
Tout le monde fait ça pour avoir la facilité de l'opérateur de
concaténation, mais bonjour la chaîne d'allocations, copies et destructions
avec libération de mémoire !
Notamment avec le compilo du VC++6.0, on voit bien la CString anonyme
retournée par operator +
C'a m'a tellement révolté que j'ai conçu un mécanisme permettant à une
chaîne de "donner son buffer" et de se laisser détruire vide, plutôt que de
demander à l'autre d'en prendre copie. Dans l'idiome a = b + c; le gain est
énorme.
| Quote: | [...]
Bienheureux les concepteurs qui ont réussi à recruter un
employeur sérieux !
En effet. J'ai vu le problème plus d'une fois. On a une équipe
de programmeurs. Qui coûte, avec les charges et al., environ
500 000 Euros. Et on réfuse l'achat d'un outil à 10 000 Euros
qui leur ferait gagner 20% de temps -- c-à-d 100 000 Euros par
an.
|
Toujours le court terme et la gestion purement comptable des projets...
| Quote: | Mais d'après ce que je vois, la situation s'améliore, et que
l'utilisation de Purify est quasi-universelle chez mes clients
aujourd'hui.
Moi je suis à nouveau en train de chercher, et j'espère que je
ne vais pas à nouveau devoir rafistoler les moutons crevés
laissés par le type d'avant, la conception des produits
d'avenir de la boîte étant dans le même temps confiée à des
bricolos pour raisons de budget :-@
Heureusement qu'on peut encore faire de la R&D sérieuse durant
ses loisirs :-D
Figure-toi qu'il y a, aujourd'hui, des boîtes qui savent estîmer
un travail bien fait, et qui sont prêt à donner aux développeurs
ce qu'il faut pour le faire. (Évidemment, elles n'embauchent pas
tous les jours. Mais en cherchant assez longtemps, on finit par
les dénicher.)
|
Oui, ça devient un métier en soi, surtout quand on n'est pas assez intégré
au réseau pour être connu. C'est pour ça que j'ai commencé à créer une
présence sur le web avec un ami plutôt commercial, mais ça met du temps à
démarrer... L'innovation fait peut plus qu'elle ne passionne.
Et donc finalement on a davantage envie de monter sa boîte que de retrouver
un employeur.
@ bientôt sans doute...
Cordialement,
--
/***************************************
* Patrick BRUNET
* E-mail: lien sur http://zener131.free.fr/ContactMe
***************************************/
|
|
| Back to top |
|
 |
kanze@gabi-soft.fr Guest
|
Posted: Wed May 18, 2005 7:55 am Post subject: Re: Classes Exceptions (was Destructeur scalaire) |
|
|
Patrick 'Zener' Brunet wrote:
| Quote: | Je réponds à [email]kanze (AT) gabi-soft (DOT) fr[/email] <kanze (AT) gabi-soft (DOT) fr> qui a écrit :
Patrick 'Zener' Brunet wrote:
Je réponds à [email]kanze (AT) gabi-soft (DOT) fr[/email] <kanze (AT) gabi-soft (DOT) fr> qui a
écrit : > Patrick 'Zener' Brunet wrote:
Je réponds à [email]superbabar51 (AT) hotmail (DOT) com[/email]
[email]superbabar51 (AT) hotmail (DOT) com[/email]> qui a écrit :
[...]
Sinon, il faut certainement des classes faites sur mésure
dans l'exception, pour gerer des chaînes sans allocation
dynamique.
Il m'est arrivé de développer de telles classes : elles
sont construites comme des objets globaux, sans allocation
de mémoire dynamique, et donc elles ne sont plus jamais
construites ni détruites : seulement réquisitionnées puis
restituées (elles fournissent une méthode static Throw() ).
Je ne comprends pas trop cette association pas d'allocation
dynamique -- objet global. J'ai bien des objets globaux qui
font de l'allocation dynamique. (J'ai même parfois des
std::string globaux.)
Je veux dire objets globaux **et** ne faisant pas d'allocation
dynamique, afin de n'avoir aucune possiblité d'échec à la
construction.
Pour ce qui est de la chaîne message, en fait j'avais
défini un protocole permettant de stocker dans le corps de
l'exception les éléments variants (2 entiers et un tableau
de 127 + 1 caractères), la classe Exception offrant une
fonction type sprintf() contrôlée pour utiliser ça.
Tu n'as pas besoin d'un protocol, parce que tu pourrais
réserver aussi un tableau de caractères dans n'importe
quelle classe dérivée. (Tableau de type, s'entend, pourqu'il
n'y a pas d'allocation dynamique.) Certaines implémentations
des std::exception le font, je crois ; si tu passes un
chaîne plus long que le tableau fixe, elles essaient
d'obtenir de la mémoire dynamique (avec new (nothrow)), et
si ça échoue, elles tronquent la chaîne.
J'ai pris en compte aussi le critère de vitesse,
|
Lever une exception est typiquement une opération assez chère en
soi. C'est peu probable que la construction d'un objet de plus
ou de moins, même quand l'objet contient des données complexes,
ait une influence mésurable sur la performance.
| Quote: | et par ailleurs j'ai voulu totalement découpler ça de
l'allocation dynamique parce que mes classes travaillent avec
toute une variété de heaps (positionnement et algorithme
d'allocation).
|
Si la classe même contient un tableau, disons un char[512], il
n'y a pas d'allocation dynamique non plus. Sauf, évidemment,
celui fait par le compilateur pour trouver où copier ton
exception.
| Quote: | Donc j'ai mis les exception à part. Partant du principe qu'on
n'a pas besoin d'une chaîne volumineuse pour cet usage, j'ai
utilisé une classe à moi qui permet de faire du CString sur un
tableau statique, avec une taille figée (constante de
précompilation). Le pool d'exceptions préallouées ne
représente plus rien dans le contexte d'un logiciel "moderne".
|
Tout à fait. Mais si tu lèves l'objet, tu n'y gagne rien, parce
que le compilateur va copier ton objet de toute façon. (Mais il
me semble que tu as dit que tu lèves en fait l'adresse de
l'objet. Un simple pointeur, donc. Il y aurait donc toujours
allocation dynamque, mais que pour un pointeur, qui n'est pas
bien grand, et il n'y aurait copie que du pointeur.)
| Quote: | Mais ça n'évite pas totallement le besoin des allocations
dynamique. Le problème, c'est qu'il faut que le système
stocke l'exception même quelque part. Et que ça ne peut pas
être sur la pile, parce qu'elle fait detruire la pile. Et
que ça ne peut pas être en mémoire statique, parce qu'il
peut y avoir d'autres exceptions lors du stack unwinding. (A
propos : comment est-ce qu'on dit « stack unwinding » en
français ?) Alors, l'implémentation est obligée à allouer la
mémoire pour l'exception dynamiquement. (En général,
l'implémentation utilise un tas ou une pile à part pour
ça. Et certainement pas operator new. Mais c'est dynamique
quand même.)
J'ai prévu un pool d'exceptions recyclables, et donc la
fonction Throw en réquisitionne une qui ne le soit pas
déjà. Cela pose évidemment la question de la taille de ce
pool. J'ai opté pour un dimensionnement par constante de
précompilation bien sûr, et comme mes classes sont conçue pour
minimiser les cascades d'échecs (elles font de la récupération
une priorité et donc évitent d'agraver la situation), j'ai
décidé qu'au-delà de 5 exceptions en cours, on dépassait
largement les bornes du raisonnable.
|
C'est à peu de chose près ce que font le runtime des
compilateurs. Sauf que leur pool ne contient que de la mémoire
brute, et non des objets pré-construit.
Je ne suis toujours pas convaincu de l'intérêt des objets
préconstruits. Beaucoup dépend du type de l'objet, évidemment,
mais si l'objet contient par exemple une chaîne de taille fixe,
plus quelques int, et rien de plus, je ne m'attendrais pas à une
grande différence entre le coût de la construction, et le coût
de positionner les paramètres dans un objet existant. Quant à la
gestion du pool, le runtime du compilateur a déjà cette
logique ; qu'est-ce qu'on gagne à la dupliquer ? Mais il y a
peut-être des cas où ça se justifie.
| Quote: | Ca permet donc de justifier ma réponse à la suite...
Sinon pour ce qui est de l'abort(), étant plutôt orienté
informatique-industrielle-sans-renoncement-à-la-mémoire-dynamique,
évidemment c'est hors de question (et donc il faudra que je
me remette un peu au courant des évolutions de la gestion
"optimiste" de mémoire sous Linux depuis 2 ans).
Je suis sceptique en ce qui concerne les possibilités d'un
logiciel critique avec utilisation de la mémoire dynamique.
Critique temps-réel, je pense aussi. Mais sans aller jusque
là, compte tenu du coût et de l'aspect spartiate des systèmes
résolument industriels, il n'est pas rare de confier à un PC
la supervision d'un process avec des contraintes temps-réel
assez fortes.
|
J'ai souvent travaillé sur des systèmes semblables. Sauf qu'à la
place du PC, on avait en général une machine Unix (Sun ou HP).
(Il faut dire qu'avant Windows 2000, la fiabilité de Windows
laissait à désirer.)
Mais du coup, il n'y a rien de critique dans ces parties du
système, et aucune raison de ne pas utiliser les exceptions
comme prévues par le langage.
J'ai même travaillé sur des systèmes temps-réel qui tournait
100% sur un Sparc, sous Solaris. Mais les contraintes temps-réel
étaient alors de l'ordre statistique -- il fallait pouvoir
traiter 140 requêtes par séconde. (Il y avait aussi des
contraintes dûres temps-réel, mais elles se mésuraient en
sécondes -- même pas la peine d'y penser, si on traitait 140
requêtes par séconde.) En l'occurance, on s'est servi des
exceptions, celles de g++ (2.95.2). Mais normalement, il n'y
avait une exception que si quelque chose ne tournait pas en
ronde, et qu'on ne pouvait pas effectuer le traitement pour
d'autres raisons. (C-à-d dans la pratique, jamais, parce qu'on
n'avait droit qu'à 24 sécondes de temps mort par an.)
| Quote: | Nous l'avons fait dans des installations de supervision de tri
ou de préparation de commande par exemple (avec des taux de
l'ordre de 6 transactions par secondes, chacune impliquant un
moulinage de structures en mémoire, des transferts série vers
un automate, réseau vers un AS400, et une ou deux transactions
en base de données genre Sybase ou Oracle. Ca marche très bien
sur un PC correctement dimensionné, mais bien sûr il est hors
de question de jouer ou de surfer sur le net en même temps !
|
Tout à fait. Ces machines-là sont des machines dédiées, qui ne
font que ça. Ce qui fait qu'on peut toujours les dimensionner de
façon à ce qu'elles aient assez de mémoire, et qu'il n'y en
manquerait qui s'il y a une erreur dans le programme (une fuite
de mémoire, par exemple). Du coup, dans toutes les applications
de ce genre que j'ai fait, on remplaçait le new_handler par une
fonction qui sortait un message vers le log et puis aborter le
programme. (Et on faisait des tests extensifs avec Purify avant
de livrer:-).)
Pas une raison de changer des exceptions.
| Quote: | Mais il y a bien des programmes industriels qui ne sont pas
critiques, et qui peuvent supporter un abort, pourvu que la
probabilité ou la fréquence en soit assez faible. (À vrai
dire, tout système critique doit être secouru, aussi. De
façon à ce qu'un crash ne soit pas fatal pour le système
entier.) De toute façon, si tu te bases sur un système
d'exploitation courante, Windows, ou un Unix, il faut
l'accepter, parce qu'ils abortent tous le processus si
l'épuisement de la mémoire se produit lors de
l'agrandissement de la pile.
Pas trop d'accord : s'il y a une exception prévue dans
operator new, ça suppose qu'on devrait avoir une chance d'en
tirer parti, et je me suis donné beaucoup de mal pour ça.
|
D'abord, ce n'est pas moi qui a créé l'exception dans l'operator
new ; j'imagine bien qu'il existe des applications où elle
serait même utile dans l'absolu. Mais je crois que la raison
principale de l'exception, c'est pour donner un comportement
défini à toutes les fonctions de la bibliothèque qui utilisent
la mémoire dynamique, sans qu'elles aient besoin d'un paramètre
en plus, ou d'un code de retour. C'est une réponse simple et
élégante au problème des auteurs de la bibliothèque (ou d'autres
bibliothèques). Elles ne peuvent pas gerer le problème
elles-même, parce qu'elles ne savent pas la contexte d'où elles
sont appelées. L'utilisation de l'exception repousse le problème
à l'utilisateur, sans préjudice sur ce qu'il doit faire.
Si ton logiciel tourne sur une machine dédiée, qui est dotée
d'assez de mémoire, qu'est-ce qui peut provoquer un échec d'une
allocation ? La seule chose, dans la plupart des cas, c'est une
fuite de mémoire dans l'application. Et comment est-ce que tu
récupères d'une fuite de mémoire ? Tu tues le processus, et tu
le rédémarres -- c'est la seule façon.
Note aussi que si le comité a prévu l'exception, certains
systèmes d'exploitation ont d'autres idées, et ce n'est pas sûr
que tu puisses attrapper l'erreur. Et qu'il y a des allocations
dont tu n'es pas maître, aussi -- et sous Windows et sous tous
les Unix que je connais, un débordement de la pile provoque
l'abort du processus, par exemple.
La question n'est pas simple. Je me suis déjà servi des features
non-documentés d'Unix (dans l'occurance, Sun OS 4 -- ce n'était
pas tout récent) pour me garantir contre les débordements de
pile. Ça ne m'a pas plu, mais je n'ai pas trouvé de meilleur
solution.
| Quote: | Je sais écrire des applications qui sont conscientes de leurs
ressources et qui savent se récupérer sur des situations
dégradées. Alors bien sûr quand j'ai appris que sous Linux il
y avait un abort() dans le new_handler parce que le système
faisait le béni-oui-oui et tuait des process quand il se
trouvait au pied du mur, évidemment j'ai trouvé ça plutôt ...
NULL. J'espère que ça a évolué.
|
Dans le cas de Linux, il s'agit d'une allocation paresseuse. Et
il y a des cas où il convient. Seulement, pas le tien (ni le
mien). Je crois que ce comportement est encore le défaut, mais
je sais que c'est configurable. Si j'avais à utiliser Linux dans
un tel contexte, je me renseignerais comment le configurer, et
je m'assurerais bien qu'il est désactivé.
Mais il y a un autre problème et dans Linux et dans Solaris (et
avant, dans Sun OS, et probablement dans beaucoup d'autres
systèmes). C'est que la pile est allouée du même pool que tout
la reste. Et que si une allocation de la pile échoue, le système
tue le processus. Est-ce que tu es sûr que sous Windows, toute
la mémoire dans la pile est allouée sûre avant le démarrage du
processus ?
| Quote: | Il faut savoir aussi que certains process (et pas seulement
dans le domaine de l'I.A.) mettent en jeu des structures âvec
un tel entrelacement de pointeurs, qu'il est peu envisageable
(ou au moins très laborieux) de les reconstruire après un
crash). J'ai dû inventer une technologie pour offrir cette
immunité à un logiciel de la gamme ci-dessus, dans le contexte
d'une éventuelle panne de courant. Ca marche bien même à plein
régime. Mais évidemment si le système se met à faire du
sabordage aléatoire, on n'a plus qu'à chercher un portage
d'Oracle sur rack OS/9 !
Aussi, on accepte souvent à prendre la « risque », en
s'assurant qu'il n'y a que l'application en question qui
tourne sur la machine, et que la machine a assez de mémoire
pour ne pas en épuiser.
Sur le genre de système évoqué, ça passe aussi par une bonne
gestion de l'émiettement, c'est ce qui me conduit à utiliser
des heaps spéciaux - et aussi des fichiers, l'expérience
martienne l'a montré :-D
2) Votre exception est transmise par copie semble-t-il, et
non par adresse.
Toute exception est transmise par copie. Il le faut bien,
puisqu'on va déballer la pile, et la mémoire où
l'utilisateur le construit (comme temporaire) va
disparaître.
Avec ma convention précédente, justement, c'était un
pointeur vers l'exception qui était transmis, lui donc par
copie en effet.
D'accord. Tu as donc un objet statique, et tu lèves une
exception du type pointeur à l'objet. Seulement, qu'est-ce
qui se passe si les exceptions s'imbriquent, et que tu veux
lever une deuxième exception du même type, alors que la
première est toujours active ? Ou est-ce que tu as conçu
l'application pour que le cas ne peut pas se produire ?
Il y a donc un pool d'exceptions de taille "raisonnable".
|
Compris. En somme, ce que fait le compilateur déjà. (Je ne
connais des détails que pour Sun CC : dans le runtime du
compilateur, dans la module de gestion des exceptions, il y a un
bloc de mémoire statique d'où on prend la mémoire pour
l'exception. En cas d'épuisemment de ce bloc, on aborte, mais
que fais-tu d'autre en cas d'épuisement de ton pool.)
Je ne veux pas critiquer ce que tu as fait. Dans l'ensemble, il
me semble bien pensé, et tu sembles connaître le sujet. Mais je
me pose la question en ce qui concerne l'utilité, étant donné
tout ce que fait le compilateur déjà. Par rapport aux systèmes
que je connais, évidemment -- avec Sun CC, le seul cas où il
pourrait se justifier, c'est si tes exceptions sont réelement
volumineux, et que tu en imbriques assez pour épuiser le bloc
pré-alloué par le compilateur (qui est d'un ordre de grandeur de
64 Ko, si je me rappelle bien).
| Quote: | Cela implique tout un tas de cycles de
création/copie/destruction de chaînes, ce qui est d'une
part très inefficace, et d'autre part remultiplie les cas
d'échec.
Ça implique *une* copie, c'est tout. Qu'il l'attrappe par
copie implique une deuxième. Rien devant le coût d'une
exception en général.
Et s'il utilise throw; dans un bloc catch() pour la
relancer ?
Pas de copie, je crois. Je ne suis pas sûr, mais je crois
que le throw sans paramètres est garantie de réutiliser
l'objet existant.
Je me souviens-là des tests que j'ai pu faire lors de la
mise au point d'une classe genre "string", au niveau de
l'opérateur d'affectation et du constructeur de copie (et
en prenant en compte l'instance anonyme transitoire
lorsqu'on retourne un objet entier plutôt qu'un pointeur).
Donc, selon comment cette instance d'exception est
construite, remplie puis "lancée", j'hésite sur le nombre
possible de copies.
Le nombre de copies n'est de toute façon pas bien spécifié.
Conceptuellement, tu construis l'objet « sur la pile » avec
une expression du genre X(), et ensuite, cet objet est copié
ailleurs. Mais un compilateur a le droit de faire en sort
que l'objet temporaire que tu construis, et la copie «
ailleurs », sont en fait le même objet, et supprimer
l'action de copier.
La pire séquece que j'ai pu tracer au debugger était la suivante :
CString ab;
...
ab = CString( "aaaaa") + "bbbbb";
Tout le monde fait ça pour avoir la facilité de l'opérateur de
concaténation, mais bonjour la chaîne d'allocations, copies et
destructions avec libération de mémoire ! Notamment avec le
compilo du VC++6.0, on voit bien la CString anonyme retournée
par operator + C'a m'a tellement révolté que j'ai conçu un
mécanisme permettant à une chaîne de "donner son buffer" et de
se laisser détruire vide, plutôt que de demander à l'autre
d'en prendre copie. Dans l'idiome a = b + c; le gain est
énorme.
|
La question est : est-ce que ça gène ? Si on utilise la chaîne
qui en résulte dans une exception, ce n'est pas à cause du temps
d'exécution que ça pourrait gener : le traitement d'une
exception coûte tellement plus cher. À peu près la seule raison
que je vois, c'est la risque d'un bad_alloc. Mais franchement,
si on est aussi près des limites de la mémoire, il doit y avoir
un problème ailleurs, je crois.
N'oublie pas que si c'est réelement un problème, avec les
chaînes standard, au moins, on peut toujours faire quelque chose
comme :
std::string ab ;
ab.reserve( tailleMaxAttendue ) ;
ab += "aaaaa" ;
ab += "bbbbb" ;
Avec une bonne implémentation de std::string, il doit pas y
avoir une allocation dynamique après l'appel à reserve (en
supposant qu'on en a réservé assez).
Mais dans la pratique, ça m'étonne que de telles pratiques
soient nécessaire, ou même utile, dans la génération des
exceptions.
[...]
| Quote: | Heureusement qu'on peut encore faire de la R&D sérieuse
durant ses loisirs :-D
Figure-toi qu'il y a, aujourd'hui, des boîtes qui savent
estîmer un travail bien fait, et qui sont prêt à donner aux
développeurs ce qu'il faut pour le faire. (Évidemment, elles
n'embauchent pas tous les jours. Mais en cherchant assez
longtemps, on finit par les dénicher.)
Oui, ça devient un métier en soi, surtout quand on n'est pas
assez intégré au réseau pour être connu. C'est pour ça que
j'ai commencé à créer une présence sur le web avec un ami
plutôt commercial, mais ça met du temps à démarrer...
L'innovation fait peut plus qu'elle ne passionne. Et donc
finalement on a davantage envie de monter sa boîte que de
retrouver un employeur.
|
Ce n'est peut-être pas le bon moment -- le marché est encore
assez mou. Nettement mieux qu'il y a trois ans, mais pas
vraiement brillant non plus.
Aussi, si le côté commercial ne t'intéresse pas
particulièrement : http://www.it.jobserve.com. C'est basé en
Angleterre, et la vaste majorité des offres sont bien en Grande
Brétagne, mais il y a aussi pas mal pour la reste de l'Europe
(mais bien moins pour la France que pour d'autres pays, comme
l'Allemagne ou la Suisse). (Si tu veux d'avantage de
renseignements sur cette filière, contacte-moi en privé à james
dot kanze at free dot fr ; ce n'est pas vraiment on topic ici.)
--
James Kanze GABI Software
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
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2006 |