OVH Cloud OVH Cloud

Destructeur scalaire

13 réponses
Avatar
superbabar51
Bonjour.

Lors de la destruction de la classe que vous trouverez ci-dessus, je me
retrouve avec une erreur de manipulation de m=E9moire. 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=E9e?

Pour info, la classe est allou=E9 ainsi:
CBSP *map =3D 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 =3D NULL;
}
}

Merci!

3 réponses

1 2
Avatar
Patrick 'Zener' Brunet
Bonjour.

Je réponds à qui a écrit
:
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>,std::allocator

<char>
assign(const
std::basic_string<char,std::char_traits<char>,std::allocator<char> > &



_Right={...}, unsigned int _Roff=0, unsigned int _CountB94967295)
Line 599 + 0x10 C++



Ca j'aime pas : unsigned int _CountB94967295
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
***************************************/




Avatar
Patrick 'Zener' Brunet
Bonjour.

Patrick 'Zener' Brunet wrote:

Patrick 'Zener' Brunet wrote:

Je réponds à





[...]
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, 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".

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.

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 !

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 !

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

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".

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.

[...]
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...

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
***************************************/




Avatar
kanze
Patrick 'Zener' Brunet wrote:

Patrick 'Zener' Brunet wrote:

Je réponds à qui a
écrit : > Patrick 'Zener' Brunet wrote:

Je réponds à




[...]
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.

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.

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.)

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.

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.)

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.

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.

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 ?

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).

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.

[...]
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





1 2