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

Savoir si une classe a ete cree par new

28 réponses
Avatar
dug8C
Bonjour,

J'ai besoin de savoir si une classe est cree sur la pile ou sur le
tas.
J'ai definit un booleen qui porte cette indication, et qui est
renseign=E9 au moment de la creation par une focntoin new priv=E9e


// le fichier h
class CBase1
{

public:

CBase1(const std::string& name);
CBase1(const CBase1& autre);
CBase1& operator=3D(const CBase1& autre);
virtual ~CBase1();

void* operator new( unsigned bytes )
{
// std::cout << "operator new( unsigned bytes )" <<
std::endl;
void* result =3D malloc(bytes);
if (result =3D=3D NULL)
throw std::bad_alloc();
else
{
creation_dyn =3D true;
return result;
}
}

void operator delete (void* p )
{
free(p);
}

// -----
std::string Name() const ;
// renvoie la trace de la creation sur la pile ou sur le tas
bool Dyn_Created() const { return cree_dyn; }

//--- operator

virtual bool operator=3D=3D(const CBase1& autre);

// --
static bool creation_dyn;
protected:
// identifiant
std::string m_name;
// trace de la creation sur la pile ou sur le tas
bool cree_dyn;

virtual void Copy( const CBase1& autre);
};

// -- le fichier cpp
CBase1::CBase1(const std::string& name)
: m_name(name)
{

std::cout << "CBase1 CTOR (" << m_name << ", " << this << ")" <<
std::endl;
cree_dyn =3D creation_dyn;
creation_dyn =3D false;
}

CBase1::CBase1(const CBase1& autre)
{
Copy(autre);
}
CBase1& CBase1::operator=3D(const CBase1& autre)
{
Copy(autre);
return *this;
}
CBase1::~CBase1()
{
std::cout << "CBase1 DTOR (" << m_name << ", " << this << ")" <<
std::endl;
}


std::string CBase1::Name() const
{
return m_name;
}

bool CBase1::operator=3D=3D(const CBase1& autre)
{
// test d'=E9galit=E9 sur les noms pas sur la facon dont la ressource a
=E9t=E9 cr=E9=E9e
return ( autre.m_name =3D=3D m_name );
}

void CBase1::Copy( const CBase1& autre)
{
if( &autre !=3D this )
{
// ne pas copier cree_dyn !
m_name =3D autre.m_name;
}
}

Ca marche plutot bien en apparence, et je me sert de cette classe
comme base pour le polymorphisme.

Par contre j'ai une interrogation, y a t il une autre facon de faire?
J'ai utilis=E9 malloc et free, car la fonction globale new appelle une
2eme fois le constructeur ce qui est genant.

10 réponses

1 2 3
Avatar
James Kanze
On Mar 24, 10:54 am, wrote:
On 24 mar, 02:32, Fabien LE LEZ wrote:

On 23 Mar 2007 18:26:54 -0700, :

J'ai besoin de savoir si une classe est cree sur la pile ou sur le
tas.


Juste par curiosité, pourquoi donc as-tu besoin de le savoir ?


En fait j'ai crée une classe qui manipule la classe CBase1 et
qui gère la libération de la mémoire. J'ai besoin de savoir
si je dois appeller delete si CBase1 est créée sur le tas.


Alors, la solution la plus facile, c'est de toujours l'allouer
dynamiquement, et en être quitte. Si un autre objet doit en
décider la durée de vie, c'est ce qu'il faut de toute façon,
puisque rien ne dit que cette durée de vie va correspondre à
celle d'un objet local.

(Dans la pratique, dans mes programmes, il y a bien d'objets
qu'on n'alloue que dynamiquement. Néaumoins, je n'ai jamais
implémenté une protection pour en empêcher l'utilisation comme
variable locale. Typiquement, la sémantique du type est telle
qu'une variable locale n'aurait pas de sens.)

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



Avatar
James Kanze
On Mar 24, 11:08 am, wrote:
On 24 mar, 09:35, "James Kanze" wrote:

On Mar 24, 2:26 am, wrote:

J'ai besoin de savoir si une classe est cree sur la pile ou
sur le tas.


C'est impossible de façon portable. Sur beaucoup de systèmes, en
revanche, la pile se trouve à des adresses connues, et la
comparaison d'adresse marche : une solution fréquente sur Sparc
ou sur PC, c'est de mettre la pile tout en haut de l'espace
adresse ; puisque la pile croît vers le bas sur ces deux
processeurs, si l'adresse est supérieur à l'adresse d'une
variable locale, elle est sur la pile.


Merci pour ces explications, mais comment fait-on pour mettre la pile
en haut de l'espace adresse ?


« On » ne fait rien. C'est là où l'éditeur de liens le met. En
gros, si la pile croît vers le bas, la position la plus logique
de le mettre, c'est tout en haut (au moins qu'on se sert des
ségments, sur un 80x86). Quand on a affaire à un processeur où
la pile croît vers le bas sous Unix, elle est prèque
systèmatiquement en haut, parce que sur les PDP-11, le hardware
n'en laissait pas le choix (et comme tout le monde sait, il n'y
a que des PDP-11 comme processeurs:-)).

Je ne pensais pas qu'on pouvait le modifier


Tout dépend de l'éditeur de liens et le système. En général,
effectivement, on ne le modifie pas -- il s'avère que l'éditeur
de liens le met en haut, parce que faire autrement sur une
architecture comme l'Intel ou la Sparc serait franchement tordu.
(Mais évidemment, au coup de l'assembleur, on peut bien le
mettre où on veut:-). Et que dans un processus multi-thread, il
y a autant de piles qu'il y a de threads, et qu'évidemment,
elles ne sont pas toutes en haut.)

[...]

Ca marche plutot bien en apparence, et je me sert de cette classe
comme base pour le polymorphisme.


C'est que tu n'en as pas essayé les cas que j'ai énumérés
ci-dessus.


Effectivement je n'ai pas essayé les creations avec new[] ,
ni en multi-thread Dans ces cas là, existe -il une solution ?


Oui, mais elle n'est pas tout légère. À chaque allocation, tu en
mets l'adresse renvoyée dans un std::set, à chaque libération,
tu l'enlève, et pour savoir, tu la cherches dans le std::set.
(Tu peux remplacer std::set par n'importe quelle collection,
évidemment. Mais de préférence une qui supporte directement
l'accès associatif.)

Par contre j'ai une interrogation, y a t il une autre facon de faire?
J'ai utilisé malloc et free, car la fonction globale new appelle une
2eme fois le constructeur ce qui est genant.


La fonction new n'appelle de constructeur. C'est une expression
new qui l'appelle. Donc :

p = new Toto ; // appel du constructeur...
p = operator new( sizeof Toto ) ;
// pas d'appel du constructeur...

La même dichotomie existe entre la fonction delete, et une
experssion delete.


Ok, je vois que j'utilisais mal la fonction new.


Une partie du problème, c'est que la vocabulaire prête à
confusion. Un opérateur de new appelle une fonction de new,
mais non seulement. Il aurait été préférable d'avoir deux noms
différents ici.

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



Avatar
James Kanze
On Mar 24, 12:42 pm, Fabien LE LEZ wrote:
On 24 Mar 2007 03:08:51 -0700, :

Merci pour ces explications, mais comment fait-on pour mettre la pile
en haut de l'espace adresse ?


Comme l'a dit James, ça dépend de l'OS. Il faut donc t'intéresser à
l'API de ton OS.


Un bon éditeur de liens, sous un bon OS, pourrait permettre à ce
que tu le mets où tu veux. À condition que le hardware le
supporte. Mais les bons éditeurs de liens et des bons OS sont
plutôt rares.

Dans le cas d'Unix, le hardware du PDP-11 exigeait que la pile
soit en haut, et depuis, elle est en haut. Le hardware du PDP-11
mettait une forte penalité si char n'était pas signé. Du coup,
char est signé sous tous les Unix que je connais, bien que ça
pose plein de problèmes, et qu'il n'y a aucun avantage avec des
hardware moderne.

Mais du coup, tu perds la portabilité.


Curieusement, la solution que j'ai expliqué marche réelement sur
pas mal de plateformes : Linux sur PC, Solaris sur Sparc, CP/M
sur 8080, etc. Elle ne marche évidemment pas avec les
architectures à adresse segmentée (c-à-d les Intels où l'OS ne
le bride pas, et en général les Intels 16 bits), mais là, tu as
une solution ultra simple : régarde le ségment. Encore plus
genant, évidemment, c'est qu'elle ne marche pas du tout dans une
contexte multi-thread, pour des raisons évidentes. Et
aujourd'hui, quelle application n'est pas multi-thread (même
quand il n'y a aucune raison de l'être---on n'est vraiment pas
« in » si on n'est pas multi-thread).

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


Avatar
Jean-Marc Bourguet
"James Kanze" writes:

Le hardware du PDP-11 mettait une forte penalité si char n'était pas
signé. Du coup, char est signé sous tous les Unix que je connais,


AIX a un char non signé.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
dug8C
[...]
Ca marche plutot bien en apparence, et je me sert de cette classe
comme base pour le polymorphisme.
C'est que tu n'en as pas essayé les cas que j'ai énumérés

ci-dessus.
Effectivement je n'ai pas essayé les creations avec new[] ,

ni en multi-thread Dans ces cas là, existe -il une solution ?


Oui, mais elle n'est pas tout légère. À chaque allocation, tu en
mets l'adresse renvoyée dans un std::set, à chaque libération,
tu l'enlève, et pour savoir, tu la cherches dans le std::set.
(Tu peux remplacer std::set par n'importe quelle collection,
évidemment. Mais de préférence une qui supporte directement
l'accès associatif.)



J'ai modifé mon code pour allouer des tableaux et garder
la trace de l'allocation sur la tas et je n'ai pas rencontré de pb.

dans ma classe j'ai ajouté 2 variables statiques, un qui indique un
utilisation de la fonction new[] et un qui compte le nombre
d'elements
alloués.

void* operator new[]( unsigned bytes )
{
nb_elem = bytes / sizeof(CBase1) ;
creation_dyn_tb = true;

void* result = ::operator new [] ( bytes );
return result;
}

void operator delete[]( void* p )
{
::operator delete[]( p);
}
ensuite dans le constructeur chaque élèment note s'il est créé
sur le tas ou pas:
CBase1::CBase1(const std::string& name)
: m_name(name)
{
if ( creation_dyn_tb == false )
{
cree_dyn = creation_dyn;
creation_dyn = false;
}
else
{
cree_dyn = true;
// dernier element a cree sur le tas
if( --nb_elem == 0 )
{
creation_dyn_tb = false;
}
}
}

et ça à l'air de marcher.
En multi-thread c'est aléatoire, ça peut ne pas marcher si un thread
cree un tableau sur le tas
tant dis qu'un autre en crée un sur la pile puisqu'ils utilise la même
variable
statique.
Si j'ai bien compris, le std::set sert à la construction pour verifier
le valeur de this avec celle du set?
Si on la trouve on peut déduire si l'objet est créé sur le tas ou pas.




Avatar
Fabien LE LEZ
On 24 Mar 2007 09:15:06 -0700, "James Kanze" :

. Et
aujourd'hui, quelle application n'est pas multi-thread (même
quand il n'y a aucune raison de l'être---on n'est vraiment pas
« in » si on n'est pas multi-thread).


Une application n'a effectivement pas forcément besoin d'être
multithread mais...
- Le besoin peut s'en faire sentir plus tard (Ce fut mon cas
quand j'ai dû, au bout de 3 ans, rajouter du son dans une application
qui n'en avait pas du tout au départ.)
- Certaines API Win32 appellent des fonctions (callback) de
l'application dans un autre thread. Du coup, au moins sous Windows, on
a vite fait de faire du multithread sans forcément s'en apercevoir.

Avatar
James Kanze
On Mar 25, 5:50 am, Fabien LE LEZ wrote:
On 24 Mar 2007 09:15:06 -0700, "James Kanze" :

. Et
aujourd'hui, quelle application n'est pas multi-thread (même
quand il n'y a aucune raison de l'être---on n'est vraiment pas
« in » si on n'est pas multi-thread).


Une application n'a effectivement pas forcément besoin d'être
multithread mais...
- Le besoin peut s'en faire sentir plus tard (Ce fut mon cas
quand j'ai dû, au bout de 3 ans, rajouter du son dans une application
qui n'en avait pas du tout au départ.)


Le thread safety, c'est comme l'exception safety, en plus. Il se
règle au niveau de la conception. Ajouter des threads à une
application qui n'en a pas revient à recommencer de zéro (ce qui
ne veut pas dire qu'une foit la nouvelle conception en place, on
ne trouve pas de code de l'ancien version qui pourrait servir).

- Certaines API Win32 appellent des fonctions (callback) de
l'application dans un autre thread. Du coup, au moins sous Windows, on
a vite fait de faire du multithread sans forcément s'en apercevoir.


Être multithread sans s'en apercevoir est une façon sûr d'avoir
une application qui ne marche pas. Quand tu actives un callback,
il faut bien savoir dans quel thread il serait appelé, pour en
prendre des précautions adéquates.

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


Avatar
James Kanze
On Mar 25, 2:54 am, wrote:
[...]
Ca marche plutot bien en apparence, et je me sert de cette classe
comme base pour le polymorphisme.
C'est que tu n'en as pas essayé les cas que j'ai énumérés

ci-dessus.
Effectivement je n'ai pas essayé les creations avec new[] ,

ni en multi-thread Dans ces cas là, existe -il une solution ?


Oui, mais elle n'est pas tout légère. À chaque allocation, tu en
mets l'adresse renvoyée dans un std::set, à chaque libération,
tu l'enlève, et pour savoir, tu la cherches dans le std::set.
(Tu peux remplacer std::set par n'importe quelle collection,
évidemment. Mais de préférence une qui supporte directement
l'accès associatif.)


J'ai modifé mon code pour allouer des tableaux et garder
la trace de l'allocation sur la tas et je n'ai pas rencontré de pb.

dans ma classe j'ai ajouté 2 variables statiques, un qui indique un
utilisation de la fonction new[] et un qui compte le nombre
d'elements
alloués.


Et tu marques tous les éléments dans le tableau ? Mais ça
devient compliqué, ton histoire ; il faut que tu notes s'il
faut delete ou non, puis s'il faut delete ou delete[]. Et
qu'est-ce qui se passe si tu as un pointeur initialisé avec
quelque chose comme &array[3] ? Il faut alors noter si l'objet
est le premier dans le tableau ou non. Et que se passe-t-il si
l'utilisateur du premier élément décide qu'il a fini (et donc,
qu'il faut faire delete), et qu'un utilisateur d'un des autres
éléments n'en a pas fini ?

Je ne sais pas quel problème tu cherches à résoudre, mais j'ai
l'impression que c'est la fuite en avant.

void* operator new[]( unsigned bytes )
{
nb_elem = bytes / sizeof(CBase1) ;


Ce qui n'est pas forcement juste. Le nombre réel en serait bien
inférieur à nb_elem, jamais strictement supérieur, mais nb_elem
peut bien être strictement supérieur au nombre d'éléments.

creation_dyn_tb = true;

void* result = ::operator new [] ( bytes );


Et que se passe-t-il si ::operator new[] lève une exception ?

return result;
}

void operator delete[]( void* p )
{
::operator delete[]( p);
}
ensuite dans le constructeur chaque élèment note s'il est créé
sur le tas ou pas:
CBase1::CBase1(const std::string& name)
: m_name(name)
{
if ( creation_dyn_tb == false )
{
cree_dyn = creation_dyn;
creation_dyn = false;
}
else
{
cree_dyn = true;
// dernier element a cree sur le tas
if( --nb_elem == 0 )
{
creation_dyn_tb = false;
}
}
}

et ça à l'air de marcher.


Parfois, ça marchera. Dans un essai rapide chez moi, en
revanche, non :

class Toto
{
public:
Toto() ;
~Toto() ;

void* operator new( size_t n ) ;
void* operator new[]( size_t n ) ;
void operator delete( void* ) ;
void operator delete[]( void* ) ;

bool isDym ;

private:
static int allocCount ;
} ;

int Toto::allocCount = 0 ;

Toto::Toto()
: isDym( allocCount > 0 )
{
if ( allocCount > 0 ) {
-- allocCount ;
}
}

Toto::~Toto()
{
if ( isDym ) {
std::cout << "Salut" << std::endl ;
}
}

void*
Toto::operator new(
size_t n )
{
void* result = ::operator new( n ) ;
allocCount = 1 ;
return result ;
}

void*
Toto::operator new[](
size_t n )
{
void* result = ::operator new[]( n ) ;
allocCount = n / sizeof( Toto ) ;
return result ;
}

void
Toto::operator delete(
void* p )
{
::operator delete( p ) ;
}

void
Toto::operator delete[](
void* p )
{
::operator delete[]( p ) ;
}

int
main()
{
Toto* array = new Toto[ 5 ] ;
std::cout << "allocated 5 Toto dynamically" << std::endl ;
for ( int i = 0 ; i < 5 ; ++ i ) {
if ( ! array[ i ].isDym ) {
std::cout << "Oops: not dynamic" << std::endl ;
}
}
Toto local ;
if ( local.isDym ) {
std::cout << "Oops: dynamic" << std::endl ;
}
delete array ;
return 0 ;
}

renvoie :

allocated 5 Toto dynamically
Oops: dynamic
Salut
Segmentation fault

Ce n'est pas ce que j'appellerai « marcher ». (À vrai dire, ça
me semblait évident que cet exemple ne pourrait pas marcher.)

En multi-thread c'est aléatoire, ça peut ne pas marcher si un thread
cree un tableau sur le tas
tant dis qu'un autre en crée un sur la pile puisqu'ils utilise la même
variable
statique.


En somme, ça ne marche pas.

Si j'ai bien compris, le std::set sert à la construction pour verifier
le valeur de this avec celle du set?
Si on la trouve on peut déduire si l'objet est créé sur le tas ou p as.



Non. Il sert lors du delete. Si on trouve le pointeur dans
l'ensemble, on appelle delete ; sinon, on ne l'appelle pas.

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





Avatar
Fabien LE LEZ
On 25 Mar 2007 03:42:53 -0700, "James Kanze" :

Être multithread sans s'en apercevoir est une façon sûr d'avoir
une application qui ne marche pas.


Je me suis sans doute mal exprimé. Je voulais dire qu'une application
a vite fait d'être multithread sans que le programmeur ne l'ait
explicitement demandé.

Ma remarque venait en réponse à ta phrase

| Et aujourd'hui, quelle application n'est pas multi-thread (même
| quand il n'y a aucune raison de l'être---on n'est vraiment pas
| « in » si on n'est pas multi-thread).

Le fait qu'une application soit multithread ne veut pas dire que le
programmeur l'a choisi.

Avatar
James Kanze
On Mar 24, 8:18 pm, Jean-Marc Bourguet wrote:
"James Kanze" writes:
Le hardware du PDP-11 mettait une forte penalité si char n'était pas
signé. Du coup, char est signé sous tous les Unix que je connais,


AIX a un char non signé.


Intéressant. Est-ce qu'il y a une différence de performance
entre les deux ? (Sur un gros IBM, je sais que charger un char
signé dans un régistre coute, ou au moins a coûté, nettement
plus cher que d'en charger un non-signé.)

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


1 2 3