OVH Cloud OVH Cloud

delete []

39 réponses
Avatar
Jean-Marie Epitalon
Bonjour,

quelqu'un pourrait-il me dire à quoi sert les [] dans l'instruction delete
[] comme dans l'exemple suivant:

int * ma_table = new int[10];
...
delete [] ma_table;

Je me demande pourquoi on en a besoin car en langage C, quand on libère de
la mémoire, on passe un pointeur à la fonction free() et c'est tout....
Merci
Jean-Marie

10 réponses

1 2 3 4
Avatar
Jean-Marc Bourguet
Sylvain writes:

Jean-Marc Bourguet wrote on 20/11/2006 20:40:
* Dans le cas de new[] et delete[], le compilateur doit stocker le nombre
d'éléments alloués pour pouvoir faire ces appels. Le premier endroit qui
me vient à l'esprit, c'est d'allouer la taille d'un int (ou plus pour des
raisons d'alignement) en plus et de mettre ce nombre en tête de bloc.


depuis des lustres, l'allocation prends 8 octets (sur archi. 32 bits) de
plus que demandée, un long pour stocker la taille d'un élément, un long
pour le nombre d'éléments; le ptr retourné est sur le bloc alloué + 8.


C'est une technique courante, popularisee par K&R, mais ce n'est pas la
seule.

Ce n'est d'ailleurs pas ce dont je parlais. Je parlais de la necessite de
stocker le nombre d'element dans le tableau alloue d'une facon connue par
le compilateur.

(Comme ce nombre est inutile dans le cas des POD, ça ne m'étonnerait pas


non pas inutile, le bloc à libérer est toujours n * taille élement -- la
méthode concrête reçoit un void*, elle ne peut plus faire de sizeof ou
autre astuce, elle dépend nécessairement des infos du "header masqué".


D'une part quand tu peux definir ta propre allocation et que le compilateur
ne connait pas au moment ou il compile si tu le fais ou non, il n'a guere
le choix et ne peux pas compter sur ces infos cachees. Il doit dupliquer
celle dont il a besoin.

D'autre part, certains allocateurs allouent parfois plus que ce qui est
demande et alors ils stockent naturellement ce qui a ete alloue, pas ce qui
a ete demande.

non plus qu'un compilateur optimise en le supprimant dans ce cas là,
auquel cas en effet l'appel à delete plutôt que delete[] pourrait passer
inapperçu jusqu'à l'ajout d'un destructeur ou l'implémentation d'une
autre politique d'allocation pour les tableaux...)


je ne pense pas qu'il est intérêt à virer ces infos, par contre il ne
fait rien de spécial sinon de libérer le bloc.

dans le cas d'instances, il doit appeler le destructeur sur chaque item et
peut faire des contrôles de heap plus poussés (pour par exemple justement
détecter des erreurs dans ces destructeurs) Studio fait cela en mode Debug
et un delete à la place d'un delete [] envoie aussitôt au purgatoire.


On va peut-etre etre plus clair avec un exemple:

$ cat sylvain.cpp
#include <iostream>
#include <stddef.h>
#include <stdlib.h>

struct Tag {} tag;

void* operator new[](size_t sz, Tag)
{
void* result = malloc(sz);
std::cout << "Allocation de " << sz << " bytes, resultat: " << result << 'n';
return result;
}

int main()
{
char* ptr = new(tag) char[9];
std::cout << "Allocation de 9 chars: " << (void*) ptr << 'n';
std::string* ptrs = new(tag) std::string[9];
std::cout << "Allocation de 9 strings(" << 9*sizeof(std::string) << "): " << (void*) ptrs << 'n';
}
$ g++-4.1.1 -Wall -Wextra -std=c++98 -pedantic-errors -o sylvain sylvain.cpp
$ ./sylvain
Allocation de 9 bytes, resultat: 0x215e0
Allocation de 9 chars: 0x215e0
Allocation de 40 bytes, resultat: 0x21be0
Allocation de 9 strings(36): 0x21be4
$ CC -o sylvain sylvain.cpp
$ ./sylvain
Allocation de 9 bytes, resultat: 41420
Allocation de 9 chars: 41420
Allocation de 44 bytes, resultat: 41bc8
Allocation de 9 strings(36): 41bd0

Tiens, il se passe exactement ce que j'ai ecrit avec g++ et sun CC. Un
appel a delete plutot que delete[] peut passer inappercu pour un tableau de
char, il a peut de chance de passer inappercu pour un tableau de string.

A+

--
Jean-Marc


Avatar
James Kanze
Sylvain wrote:
Jean-Marc Bourguet wrote on 20/11/2006 20:40:

* Dans le cas de new[] et delete[], le compilateur doit stocker le nomb re
d'éléments alloués pour pouvoir faire ces appels. Le premier e ndroit qui
me vient à l'esprit, c'est d'allouer la taille d'un int (ou plus po ur des
raisons d'alignement) en plus et de mettre ce nombre en tête de blo c.


depuis des lustres, l'allocation prends 8 octets (sur archi. 32 bits) de
plus que demandée, un long pour stocker la taille d'un élément, un long
pour le nombre d'éléments; le ptr retourné est sur le bloc alloué + 8.


Ça dépend beaucoup des implémentations. Chez moi, un tableau des
int ne ajoute rien, quelque soit le compilateur, mais un tableau
d'un type avec un destructeur ajoute 8 octets, que ce soit avec
Sun CC ou avec g++, mais au moins avec Sun CC, je ne crois pas
que la taille de l'objet y est : le compilateur appelle une
fonction __Crun::vector_del avec l'adresse du tableau, la taille
de chaque élément, et l'adresse du destructeur. Je suppose donc
qu'il cherche le nombre d'éléments à partir de l'adresse du
tableau, mais non la taille de chaque élément, parce qu'on lui
la passe en paramètre.

Ce comportement, c'est à peu près ce que je me rappelle de
CFront. À partir de 2.1, en tout cas -- historiquement,
évidemment, il fallait fournir aussi le nombre d'éléments, c-à-d
delete [10] p.

Est-ce que tu ne confonds pas avec l'implémentation de malloc,
qui sert en-dessous des operator new ? Sauf que là non plus, je
ne connais pas d'algorithme répandu qui fonctionne comme ce que
tu as l'air de décrire ; surtout, à ce niveau-là, la taille de
l'élément est sans importance. (J'avoue ne jamais avoir vu
l'organisation que tu décris.

(Comme ce nombre est inutile dans le cas des POD, ça ne
m'étonnerait pas


non pas inutile, le bloc à libérer est toujours n * taille élement -- la
méthode concrête reçoit un void*, elle ne peut plus faire de sizeof ou
autre astuce, elle dépend nécessairement des infos du "header masqu é".


Oui, mais c'est gérer à un autre niveau, dans la fonction
operator new même (voire encore plus bas, dans malloc). Et là,
il existe beaucoup d'algorithmes différents, avec des
utilisations variées de la mémoire : des deux sur lesquelles j'ai
réelement travaillé, un se servait d'un simple pointeur à la
bloc prochain, et calculait la taille en faisait la différence
des pointeurs (et aux incontinuités dans l'arène, s'assurer
qu'il y avait un bloc factice toujours alloué à la fin de chaque
zone contiguë), l'autre arrondissait la taille toujours à la
puissance de deux suppérieur, et gérait autant de pools
séparés : lors de l'allocation, il mettait seulement
l'identificateur du pool avant l'adresse renvoyée.

non plus qu'un compilateur optimise en le supprimant dans ce cas là,
auquel cas en effet l'appel à delete plutôt que delete[] pourrait passer
inapperçu jusqu'à l'ajout d'un destructeur ou l'implémentation d'une
autre politique d'allocation pour les tableaux...)


je ne pense pas qu'il est intérêt à virer ces infos, par contre il ne
fait rien de spécial sinon de libérer le bloc.

dans le cas d'instances, il doit appeler le destructeur sur chaque item
et peut faire des contrôles de heap plus poussés (pour par exemple
justement détecter des erreurs dans ces destructeurs) Studio fait cela
en mode Debug et un delete à la place d'un delete [] envoie aussitôt au
purgatoire.


En mode débogue, évidemment, on ajoute habituellement un préfixe
et un suffixe suffisamment pour détecter des cas de débordement.
Mais typiquement, ça se situe dans une couche intermédiaire
entre la couche de l'allocation proprement dite, et les operator
new[]/delete[]. Au moins qu'on modifie le code appelant, comme
fait Purify.

--
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
Sylvain
Jean-Marc Bourguet wrote on 21/11/2006 09:21:

C'est une technique courante, popularisee par K&R, mais ce n'est pas la
seule.


oui surement pas la seule, mais assez répandu.

Ce n'est d'ailleurs pas ce dont je parlais. Je parlais de la necessite de
stocker le nombre d'element dans le tableau alloue d'une facon connue par
le compilateur.


ah pardon, moi je parlais de la façon dont le compilo stocke le nombre
d'éléments alloués ?!...

[...]
Tiens, il se passe exactement ce que j'ai ecrit avec g++ et sun CC. Un
appel a delete plutot que delete[] peut passer inappercu pour un tableau de
char, il a peut de chance de passer inappercu pour un tableau de string.


c'est ce que j'ai dit, un type primitive (un POD) ne créé aucune
différence; pour des instances, un compilo un peu plus répandu comme vc
détecte l'erreur au runtime et ne peut pas laisser (sauf à ne rien
débugger) l'erreur inaperçue.

Sylvain.

Avatar
Jean-Marc Bourguet
Sylvain writes:

Jean-Marc Bourguet wrote on 21/11/2006 09:21:

Ce n'est d'ailleurs pas ce dont je parlais. Je parlais de la necessite de
stocker le nombre d'element dans le tableau alloue d'une facon connue par
le compilateur.


ah pardon, moi je parlais de la façon dont le compilo stocke le nombre
d'éléments alloués ?!...


Effectivement, je t'ai lu trop vite. Nous ne sommes même plus d'accord sur
le premier point. Reprenons ce que tu as écrit:

depuis des lustres, l'allocation prends 8 octets (sur archi. 32 bits)
de plus que demandée, un long pour stocker la taille d'un élément, un
long pour le nombre d'éléments; le ptr retourné est sur le bloc alloué
+ 8.




Les allocateurs pour le C et le C++ d'occupent rarement du nombre des
éléments. En C, il n'y en a pas (calloc en a un mais il n'introduit pas de
différence). En C++, il y faut bien le conserver pour la paire new[]
delete[] mais les allocateurs globaux peuvent être remplacés par
l'utilisateur et l'interface ne prévoit pas de moyen de le communiquer
entre le code généré et l'allocateur.

L'allocateur génère éventuellement un en-tête qui l'aide pour son algo
d'allocation et qui contient généralement la taille allouée (celle-ci peut
être implicite dans une allocation par région) et éventuellement d'autres
informations (la taille du bloc précédant par exemple). Le code généré
doit stocker ailleurs le nombre d'éléments d'un tableau alloué avec new[];
il ne peut même pas collaborer avec la bibliothèque standard pour utiliser
une place qui serait perdue à cause des contraintes d'alignement parce
qu'il ne peut pas dépendre de la bibliothèque standard: l'utilisateur peut
remplacer l'allocateur sans que le compilateur soit au courant de la
substitution au moment de la génération de code (c'est par contre
envisageable pour un compilateur qui fait de l'optimisation et de la
génération de code au moment de l'édition de liens).

Une technique possible (utilisée visiblement par g++ et Sun CC; j'ai en
fait du mal à en imaginer une autre qui tienne la route) est d'allouer en
plus un espace pour le socker avant le tableau et d'ajuster le résultat de
l'opérateur new[]. Cet espace supplémentaire n'est pas nécessaire dans le
cas des POD: ceux n'ayant pas de destructeur, le code généré n'a pas besoin
de connaître le nombre d'élements.

[...]
Tiens, il se passe exactement ce que j'ai ecrit avec g++ et sun CC. Un
appel a delete plutot que delete[] peut passer inappercu pour un tableau de
char, il a peut de chance de passer inappercu pour un tableau de string.


c'est ce que j'ai dit, un type primitive (un POD) ne créé aucune
différence;


Tu as fait attention à la trace que j'ai donnée? Un POD se comporte de
manière différente avec les deux compilateurs que j'ai essayé.

pour des instances, un compilo un peu plus répandu comme vc détecte
l'erreur au runtime et ne peut pas laisser (sauf à ne rien débugger)
l'erreur inaperçue.


D'après ce que j'ai cru comprendre des discussions lancées par des gens
utilisant VC++ et ayant des problèmes, ce qu'il fait c'est utiliser des
macros pour substituer aux appels à new et new[] des appels à un new/new[]
avec placement pour fournir des d'informations pour un allocateur de debug.

Si j'ai bien compris, il aurait été plus robuste de fournir une
bibliothèque contenant un allocateur de debug qui socke l'adresse de
l'appelant et se sert des maps de l'édition de liens et autres informations
de debug pour récupérer l'information cherchée. Sun fournit une telle
bibliothèque. On peut s'en faire une sans problème pour gcc car il fournit
les primitives nécessaires.

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
Jean-Marc Bourguet
Sylvain writes:

Jean-Marc Bourguet wrote on 21/11/2006 22:06:
Effectivement, je t'ai lu trop vite. Nous ne sommes même plus d'accord
sur
le premier point. Reprenons ce que tu as écrit:


te me rassures ;)

[...]


intéressant mais très implémentation (CRT) dépendant et:
1- loin de la question initiale (et je me place à ce niveau, savoir le bit
et le byte de ce qui est stocké par qui et où n'était pas indispensable),
ou alors ...


On a repondu a la question initiale. La question en cours est celle de
Jean-Marc Desperrier: << Justement la plupart des implémentations
"pardonnent" cette erreur et font ce qu'il faut même avec le mauvais appel,
non ? >>

Auquel je reponds "uniquement pour les POD" en expliquant pourquoi.

2- mais ne dit pas pourquoi, concrêtement, un type primitif marche et pas
un array d'instance, explique-nous quelles infos sont requises (l'adresse
d'une tabmle de meth. virt., l'adresse d'un destructeur particulier, un
typeof, ???) et comment on-sait-pas-qui (pas ton opérateur qui ne fait que
::free()) itère les instances pour appeler chaque destructeur.


Une expression

T* p = new T[10];

s'execute en deux phases: l'allocation avec ::operator new[] et la
construction. Comme il faut stocker le 10 quelque part, une technique
(utilisee par tous les compilateurs que j'ai au boulot, je peux encore
regarder avec deux autres chez moi si tu veux) est d'allouer plus que
10*sizeof(T) et de mettre 10 dans la partie allouee supplementaire.

De meme

delete[] p;

s'execute en deux phases: la destruction du tableau en recuperant le 10 a
l'endroit ou il a ete stocke et l'appel a ::operator delete[] pour la
liberation.

L'utilisateur peut remplacer ::operator new[] et ::operator delete[], donc
la zone de stockage du 10 ne peut pas se trouver dans les entetes utilises
eventuellement par les allocateurs, meme si ceux-ci ont de la place libre a
cause des contraintes d'alignement.

Tu as fait attention à la trace que j'ai donnée? Un POD se comporte de
manière différente avec les deux compilateurs que j'ai essayé.


et ? rapelle-moi CC est utilisé pour quel % des applis in-the-field?


IBM xlC a le meme comportement aussi. Donc entre g++, Sun CC et IBM xlC,
on est a 100% pour la version en cours (il y a pour le moment un mouvement
de progression de Linux et de retrait de Solaris, de ma position c'est
difficile de savoir ou ca en est exactement -- il y a de bonne raisons de
preferer l'un ou l'autre et il n'est pas impossible qu'on delivre aussi
pour Solaris x86 dans le futur ce qui pourrait reinverser la tendance; la
version sur AIX m'a l'air d'etre utilisee uniquement par IBM). La version
precedante etait aussi fournie avec HP aCC sur HP-UX mais on a arrete le
support -- ici on n'a meme plus d'HP donc je ne peux pas tester facilement.

il est toujours possible (et parfois facile) de trouver un contre-exemple
mais si celui-ci est peu fréquent ou inexistant pour qui cherchait une
explication, cela n'explique rien.


As-tu fait tourner mon exemple avec VC++ puisque tu as l'air de considerer
tout le reste comme peu frequent ou inexistant? Personnellement, sauf a
changer de division ou d'employeur, je ne me vois pas l'utiliser dans les
cinq ans a venir; quand j'ai ete embauche il y a 8 ans, il y avait un plan
pour que tous nos produits soient disponibles dans les deux ans sous
Windows. Je crois que pour le moment les seules choses disponibles sous
Windows sont des produits nouvellement developpes a partir de zero -- ils
sont rares -- ou des produits de boites rachetees. Le plan est passe
discretement aux oubliettes assez rapidement. Par contre je n'ai jamais
entendu parler de grand plan Linux -- mais c'est en passe de devenir notre
premiere plateforme de deploiement, s'il ne l'est pas deja.

D'après ce que j'ai cru comprendre des discussions lancées par des gens
utilisant VC++ et ayant des problèmes, ce qu'il fait c'est utiliser des
macros pour substituer aux appels à new et new[] des appels à un new/new[]
avec placement pour fournir des d'informations pour un allocateur de debug.


non pas vraiment, je distinguerais:


Merci pour les infos.

A+

--
Jean-Marc


Avatar
James Kanze
Jean-Marc Bourguet wrote:

[...]
Une technique possible (utilisée visiblement par g++ et Sun CC; j'ai en
fait du mal à en imaginer une autre qui tienne la route) est d'allouer en
plus un espace pour le socker avant le tableau et d'ajuster le résultat de
l'opérateur new[]. Cet espace supplémentaire n'est pas nécessaire dans le
cas des POD: ceux n'ayant pas de destructeur, le code généré n'a pa s besoin
de connaître le nombre d'élements.


À part reserver de la place en tête, et stocker les informations
là, je ne vois effectivement pas d'autre solution réaliste. (On
parle parfois de mettre les informations dans un tableau indicé
par l'adresse, genre std::map ou std::unordered_map, mais je
n'ai jamais entendu parler d'une implémentation qui le fait
réelement.) En revanche, les informations stockées peuvent
varier. Dans le cas de Sun CC, par exemple, on n'y stocke que le
nombre d'éléments ; c'est lors du delete qu'on passe l'adresse
du destructeur et la taille de chaque élément à la fonction qui
appelle les destructeurs. Mais on aurait aussi bien mettre ces
informations dans la zone cachée d'en-tête -- c'est peut-être
ce qu'essaie de dire Sylvain, que VC++ met à la fois le nombre
d'éléments et la taille de chaque élément là. Il faut dire
qu'avec une machine 32 bits qui a besoin d'un alignement de 8
octets (c'est le cas des anciens Sparcs, par exemple), il y a
bien quatre octets qui ne servent à rien dans cette en-tête. Et
qu'on lui a mal compris à cause de son « Depuis des lustres,
l'allocation [...] ». Or que la solution « classique »,
celle de CFront, et bien celle utilisée aujourd'hui encore par
Sun CC et g++, avec le nombre d'éléments seulement, et par
« allocation », on entend habituellement ce qui se fait *dans*
operator new ou malloc, et non ce que le compilateur ajoute pour
gerer l'appel des destructeurs.

Seulement, un essai rapide avec VC++ (celui du Studio 2005)
montre qu'il ne réserve que quatre octets (un pointeur ou un
int), et ceci, que si le type a un destructeur non-trivial. Et
en regardant le code généré, c'est bien le nombre d'éléments
qu'il y stocke. (À l'encontre de Sun, qui utilise une fonction
commune à laquelle il passe l'adresse du destructeur et la
taille de chaque élément, VC++ génère une fonction distincte
pour chaque type. Plus ou moins comme si la fonction appelée
était un template.)

Du coup, je me démande de quoi parlait Sylvain réelement.

[...]
pour des instances, un compilo un peu plus répandu comme vc détecte
l'erreur au runtime et ne peut pas laisser (sauf à ne rien débugger)
l'erreur inaperçue.


D'après ce que j'ai cru comprendre des discussions lancées par des ge ns
utilisant VC++ et ayant des problèmes, ce qu'il fait c'est utiliser des
macros pour substituer aux appels à new et new[] des appels à un new/ new[]
avec placement pour fournir des d'informations pour un allocateur de debu g.


Je ne crois pas. En tout cas, il accepte des new de placement,
ce qui pose des problèmes avec des macros.

Si j'ai bien compris, il aurait été plus robuste de fournir une
bibliothèque contenant un allocateur de debug qui socke l'adresse de
l'appelant et se sert des maps de l'édition de liens et autres informat ions
de debug pour récupérer l'information cherchée.


C'est ce qu'il font. Le problème, c'est que par défaut, on linke
cette bibliothèque statiquement avec chaque .dll, et que chaque
.dll utilise sa propre version (un peu comme si on utilisait des
link map sous Solaris, et que les adresses de malloc et de free
n'était pas exportées). Du coup, si un des .dll est linké avec
la version debug, et un autre non, on a des problèmes si la
mémoire est allouée dans un .dll, et libérée dans un autre.

Sun fournit une telle bibliothèque. On peut s'en faire une
sans problème pour gcc car il fournit les primitives
nécessaires.


Dans la pratique, la façon que dlopen marche fait que si on a
linké statiquement une version de malloc/free dans la racine,
c'est bien celle-là qui servira dans tous les .so. (On peut en
faire pareil avec VC++, je crois.)

--
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
Sylvain wrote:
Jean-Marc Bourguet wrote on 21/11/2006 22:06:
[...]
Tu as fait attention à la trace que j'ai donnée? Un POD se comport e de
manière différente avec les deux compilateurs que j'ai essayé.


et ? rapelle-moi CC est utilisé pour quel % des applis in-the-field?


Il a donné un trace et pour CC et pour g++. J'ai le même
résultat avec VC++, et c'est la façon que fonctionnait CFront.
Une fois qu'on a VC++ et g++, et CFront pour la passée, ça doit
représentait un percentage pas negligeable des applications
« in-the-field ».

il est toujours possible (et parfois facile) de trouver un
contre-exemple mais si celui-ci est peu fréquent ou inexistant pour qui
cherchait une explication, cela n'explique rien.


Je ne considère pas VC++ et g++ « peu fréquent ou
inexistant ».

--
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:

Jean-Marc Bourguet wrote:

[...]

Une technique possible (utilisée visiblement par g++ et Sun CC;
j'ai en fait du mal à en imaginer une autre qui tienne la route)
est d'allouer en plus un espace pour le socker avant le tableau et
d'ajuster le résultat de l'opérateur new[]. Cet espace
supplémentaire n'est pas nécessaire dans le cas des POD: ceux
n'ayant pas de destructeur, le code généré n'a pas besoin de
connaître le nombre d'élements.


À part reserver de la place en tête, et stocker les informations là,
je ne vois effectivement pas d'autre solution réaliste. (On parle
parfois de mettre les informations dans un tableau indicé par
l'adresse, genre std::map ou std::unordered_map, mais je n'ai jamais
entendu parler d'une implémentation qui le fait réelement.)


Cette separation a parfois du sens pour conserver ensemble des
informations qui doivent etre utilisee ensemble et donc avoir une
meilleure utilisation des caches. Ca ne me semble pas le cas ici: il
faut executer les destructeurs...

Je peux aussi imaginer un pointeur "gras" qui contiendrait ce nombre.
A nouveau, il y a des raisons de ne pas faire cela et j'ai du mal a
imaginer des raisons pour le faire a part eviter un brevet.

A+

--
Jean-Marc


Avatar
Sylvain
Jean-Marc Bourguet wrote on 22/11/2006 09:38:

On a repondu a la question initiale. La question en cours est celle de
Jean-Marc Desperrier: << Justement la plupart des implémentations
"pardonnent" cette erreur et font ce qu'il faut même avec le mauvais appel,
non ? >>


pardon! mon propos était très mal formulé voire dédaignieux.
nous sommes d'accord, je réagissais également à ce point, pensant que
cet emploi ("delete" à la place de "delete []") est non neutre.

Une expression
T* p = new T[10];
s'execute en deux phases [...]
De meme
delete[] p;
s'execute en deux phases [...]


non sommes d'accord aussi (je n'avais pas les idées claires hier!!)

nécessairement on distingue bien 2 opérations:
- l'allocation brute, qui peut être surchargée, mais dont
l'implementation ne peut avoir la responsabilité de la taille à allouer
- les traitements générés par le compilateur (ajout d'un overhead, si
besoin et selon sa stratégie, boucle sur les élements, si tableau, ...).

disposant d'un pointeur seul, le compilo ne peux pas savoir s'il utilise
le résultat de "new A;" ou "new A[n];" (oui, évidence).

dès lors, soit le compilateur choisit par toute allocation d'instances
(1 ou plusieurs) de stocker une information 'nombre d'items' qu'il
accédera (dans son code inséré) pour appeler en boucle le destructeur (1
ou n fois) - et cette option laisse entendre que "delete" serait
suffisant(équivalent) à "delete []" car "de toutes façons il le fera
pour moi ..."

soit, le compilo choisit d'insérer un nombre d'items (et donc de
requérir plus d'espace alloué) uniquement pour un tableau; il codera
alors ou non la boucle de destruction selon la présence, ou non, du
modifier [] appliqué à delete.

mon expérience (souvenir de CFront sur Mac (ça date!), et ce que fait
VC; je ne sais pas ce que faisais SunCC) est que l'option 2 me semble
assez fréquente; (ton code me donne bien 2 tailles différentes (de 4 pas
8, malgré un alignement de 8) octets pour "new(tag) std::string[9];"
mais 2 tailles identiques pour "new(tag) std::string;" (ayant ajouté un
"void* operator new(size_t sz, Tag)").

dès lors, le traitement (le code généré) étant différent selon la
présence ou non de [], le code (probable, non exclu, ...) de contrôle
provoquera des erreurs en cas de mauvaise syntaxe de delete.

mon raccourci "depuis des lustres etc" voulait traduire ce fait qui rend
nécessaire la bonne écriture.

L'utilisateur peut remplacer ::operator new[] et ::operator delete[], donc
la zone de stockage du 10 ne peut pas se trouver dans les entetes utilises
eventuellement par les allocateurs, meme si ceux-ci ont de la place libre a
cause des contraintes d'alignement.


oui, ma compréhension est bien qu'ils ne peuvent que allouer/libérer ce
qui leur est demandé sans (eux-mêmes) initialiser quoi que ce soit.

As-tu fait tourner mon exemple avec VC++ puisque tu as l'air de considerer
tout le reste comme peu frequent ou inexistant?


re-désolé si cela donnait cette impression.

Sylvain.

Avatar
Sylvain
James Kanze wrote on 22/11/2006 09:41:

Seulement, un essai rapide avec VC++ (celui du Studio 2005)
montre qu'il ne réserve que quatre octets [...]

Du coup, je me démande de quoi parlait Sylvain réelement.


bien de la même chose mais avec une idée erronée (inventée) ou obsolète
(VC5 et disparue depuis).

D'après ce que j'ai cru comprendre des discussions lancées par des gens
utilisant VC++ et ayant des problèmes, ce qu'il fait c'est utiliser des
macros pour substituer aux appels à new et new[] des appels à un new/new[]
avec placement pour fournir des d'informations pour un allocateur de debug.


Je ne crois pas. En tout cas, il accepte des new de placement,
ce qui pose des problèmes avec des macros.


j'avais également mal lu ce point.

il n'y a pas (je n'ai jamais vu) de 'placement new' avec Studio (et j'ai
arrêté d'en faire en abandonnant le Pascal et son absolute).

pour compléter ce que j'indiquais hier, le CRT (donc le new par défaut)
de Studio, utilise un bloc qui contient une structure avec notamment des
infos de suivi de debug mais également des infos tjrs présente liée à
son gestionnaire de tas (il faudrait comparer des CRT debug et release
pour faire la part des choses, ce n'est surement pas le propos).

Si j'ai bien compris, il aurait été plus robuste de fournir une
bibliothèque contenant un allocateur de debug qui socke l'adresse de
l'appelant et se sert des maps de l'édition de liens et autres informations
de debug pour récupérer l'information cherchée.


C'est ce qu'il font. Le problème, c'est que par défaut, on linke
cette bibliothèque statiquement avec chaque .dll, et que chaque
..dll utilise sa propre version (un peu comme si on utilisait des
link map sous Solaris, et que les adresses de malloc et de free
n'était pas exportées).


exact.

Du coup, si un des .dll est linké avec
la version debug, et un autre non, on a des problèmes si la
mémoire est allouée dans un .dll, et libérée dans un autre.


on peut aussi avoir des pbs avec 2 versions debug ou release (il vaut
mieux éviter les alloc/libérations croisées et préférer des static "X*
getIntance()" / "release(X*)").

Sylvain.


1 2 3 4