Une simple application de test :
cout << new int[0] << endl;
sorties :
0x876c0b8
On dirait que ça marche. Que dit la norme à ce sujet ? Est-il toujours légal d'"allouer" un bloc de mémoire vide ?
Une simple application de test :
cout << new int[0] << endl;
sorties :
0x876c0b8
On dirait que ça marche. Que dit la norme à ce sujet ? Est-il toujours légal d'"allouer" un bloc de mémoire vide ?
De 5.3.4/7
Lorsque la valeur de l'expression dans un déclarateur direct-new est zéro, la fonction d'allocation est appelée pour allouer un tableau sans éléments.
De 3.7.3.1/2
L'effet du déréférencement d'un pointeur renvoyé comme une demande de taille zéro est indéfini.
Aussi
Même si la taille de l'espace demandé [par new] est nulle, la demande peut échouer.
Cela signifie que vous pouvez le faire, mais que vous ne pouvez pas légalement (d'une manière bien définie sur toutes les plateformes) déréférencer la mémoire que vous obtenez - vous pouvez seulement la passer à array delete - et vous devez la supprimer.
Voici une note de bas de page intéressante (c'est-à-dire qui n'est pas une partie normative de la norme, mais qui est incluse à des fins d'exposition) attachée à la phrase de 3.7.3.1/2
[32. L'intention est d'avoir l'opérateur new() implémentable en appelant malloc() ou calloc(), donc les règles sont substantiellement les mêmes. Le C++ diffère du C en exigeant qu'une requête zéro renvoie un pointeur non nul].
J'obtiens une fuite de mémoire si je ne supprime pas. Est-ce que c'est prévu ? En tout cas, je ne m'y attendais pas.
@EralpB : au contraire, même si votre demande est nulle, cette allocation se fait sur le Heap, où une demande implique plusieurs opérations de comptabilité, comme l'allocation et l'initialisation des gardes du Heap avant et après la zone donnée par l'allocateur, l'insertion dans des freelists ou d'autres structures horribles complexes. Le libérer signifie faire la comptabilité à l'envers.
@EralpB oui je suppose que vous pouvez vous attendre à une fuite de mémoire chaque fois que vous n'équilibrez pas un new[]
avec un delete[]
- quelle que soit la taille. En particulier, lorsque vous appelez new[i]
vous avez besoin d'un peu plus de mémoire que celle que vous essayez d'allouer pour stocker la taille du tableau (qui est ensuite utilisée par delete[]
lors de la désallocation)
Avez-vous une référence à ce sujet ? Nous savons tous que int ar[0];
est illégale, pourquoi la nouvelle est-elle acceptable ?
Il est intéressant de constater que le C++ n'est pas aussi strict en ce qui concerne l'interdiction des objets de taille zéro. Pensez à l'optimisation de la classe de base vide : Ici aussi, un sous-objet de classe de base vide peut avoir une taille nulle. En revanche, la norme C s'efforce de garantir qu'aucun objet de taille zéro ne soit jamais créé : La définition de malloc(0) précise que l'effet est d'exécuter malloc avec un argument non nul, par exemple. Et dans struct { ... ; T n[] ; } ; lorsqu'il n'y a pas d'espace alloué pour le tableau (FAM), il est dit qu'il se comporte comme si "n" n'avait qu'un seul élément. (Dans les deux cas, l'utilisation de l'objet de quelque manière que ce soit est UB, comme x.n[0]).
Je pense que sizeof (type)
est censé ne jamais renvoyer zéro. Voir par exemple : stackoverflow.com/questions/2632021/can-sizeof-return-0-zero
Que dit la norme à ce sujet ? Est-il toujours légal d'"allouer" un bloc de mémoire vide ?
Chaque objet a une identité unique, c'est-à-dire une adresse unique, ce qui implique une longueur non nulle (la quantité réelle de mémoire sera augmentée silencieusement, si vous demandez zéro octet).
Si vous attribuez plusieurs de ces objets, vous constaterez qu'ils ont des adresses différentes.
"Chaque objet a une identité unique, c'est-à-dire une adresse unique, ce qui implique une longueur non nulle" - c'est vrai, mais c'est faux. L'objet a une adresse, mais le pointeur sur l'objet peut pointer sur une mémoire aléatoire. "La quantité réelle de mémoire sera augmentée silencieusement si vous demandez zéro octet" - pas sûr. operator []
stocke également la taille du tableau quelque part (voir isocpp.org/wiki/faq/freestore-mgmt#num-elems-in-new-array ). Ainsi, si l'implémentation alloue le nombre d'octets avec les données, elle pourrait simplement allouer le nombre d'octets et 0 octet pour les données, ce qui renverrait le pointeur 1-past-last.
Une longueur non nulle de la mémoire allouée, et non une longueur non nulle de la mémoire utilisable. Ce que je voulais dire, c'est que deux pointeurs vers deux objets distincts ne devraient pas pointer vers la même adresse.
La norme ( open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf ) indique en effet (3.7.4.1/2) que les différents appels à la fonction operator new[]
doivent renvoyer des pointeurs différents. Par ailleurs, la fonction new expression
comporte des règles supplémentaires (5.3.4). Je n'ai pas trouvé d'indice permettant d'affirmer que new
avec une taille de 0 est en fait requis pour allouer quoi que ce soit. Désolé, j'ai rétrogradé parce que je trouve que votre réponse ne répond pas aux questions, mais fournit des déclarations controversées.
Oui, il est tout à fait légal d'allouer un bloc de taille 0 avec new. Vous ne pouvez simplement rien faire d'utile avec ce bloc puisqu'il n'y a aucune donnée valide à laquelle vous pouvez accéder. int[0] = 5;
est illégale.
Cependant, je crois que la norme permet des choses comme malloc(0)
pour retourner NULL.
Vous devrez toujours delete []
quel que soit le pointeur que vous récupérez de l'allocation aussi.
En ce qui concerne malloc, vous avez raison - il est défini par l'implémentation. Cela est souvent considéré comme une erreur.
Je suppose qu'une question intéressante se pose : la version nothrow de new peut-elle renvoyer NULL si on lui donne une taille de 0 ?
Curieusement, le C++ exige que l'opérateur new renvoie un même lorsque zéro octet est demandé. (L'exigence de ce comportement simplifie les choses ailleurs dans le langage).
J'ai trouvé C++ efficace Troisième édition comme suit : "Point 51 : Respecter les conventions lors de la rédaction de nouveaux documents et de la suppression de documents".
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.
4 votes
+1 Question très intéressante - même si je ne suis pas sûr qu'elle ait beaucoup d'importance dans le code réel.
43 votes
@Zifre : Je pose la question par curiosité, mais cela pourrait avoir de l'importance dans le monde réel, par exemple lorsque la taille des blocs de mémoire alloués est calculée d'une certaine manière, et que le résultat du calcul peut être zéro, alors il n'y a pas de besoin direct d'ajouter des exceptions pour ne pas allouer des blocs de taille zéro . En effet, ils devraient être alloués et supprimés sans erreur (si seulement le bloc de taille zéro n'est pas déréférencé). D'une manière générale, cela permet d'élargir l'abstraction de ce qu'est un bloc de mémoire.
2 votes
@emg-2 : Dans votre exemple, cela n'aurait pas d'importance, car delete[] est parfaitement légal sur un pointeur NULL :-).
0 votes
@Evan : tu as raison :) un meilleur exemple : ce serait important s'il y avait une condition if() qui dépendrait de la valeur NULL, et par exemple arrêterait d'allouer plus de mémoire à cause d'une supposition erronée, qu'il n'y a plus de mémoire.
2 votes
Ce n'est qu'un lien indirect - c'est pourquoi je le commente ici - mais le C++ garantit à bien des égards que des objets distincts ont des adresses uniques... même s'ils n'ont pas explicitement besoin d'être stockés. Une expérience connexe consisterait à vérifier la taille d'une structure vide. Ou d'un tableau de cette structure.
2 votes
Pour compléter le commentaire de Shmoopty : En C++, il est courant d'avoir des objets de taille nulle, surtout lorsqu'on programme avec des modèles (par exemple, des modèles de classe de politique comme std::allocator). Le code générique peut avoir besoin d'allouer dynamiquement de tels objets et d'utiliser des pointeurs vers eux pour comparer l'identité des objets. C'est pourquoi l'opérateur new() renvoie des pointeurs uniques pour les requêtes de taille nulle. Bien que moins important/commun, le même raisonnement s'applique à l'allocation de tableaux et à l'opérateur new[]().
0 votes
Voyons ce qui se passe dans le code réel de la bibliothèque standard.
std::vector<double> v;
construit un vecteur vide (zéro élément). L'allocateur par défaut ne renvoie pasnullptr
(je viens de vérifier), mais il s'avère quev.data() == nullptr
Cela indique que dans le cas particulier où il n'y a pas d'élément, l'allocateur par défaut n'est pas utilisé (ou, ce qui est moins probable, écrasé). Si vous vous fiez à la sagesse de la bibliothèque standard (peut-être à la perspicacité de Stepanov), cela signifie que ce n'est probablement pas une bonne idée d'essayer d'allouer des tableaux dynamiques de taille zéro.(new int[0]
utilise un peu de mémoire car le `0 est stocké quelque part)0 votes
Je parie que la bibliothèque standard contient du code de ce type (par exemple dans le constructeur),
struct vector : ..., ptr_(num_elements==0?nullptr:allocator_.allocate(num_elements)), ...{...}
.