Considérez l'extrait de code suivant:
#include <array>
int main() {
using huge_type = std::array<char, 20*1024*1024>;
huge_type t;
}
Évidemment, cela planterait sur la plupart des plates-formes, car la taille de pile par défaut est généralement inférieure à 20 Mo.
Considérez maintenant le code suivant:
#include <array>
#include <vector>
int main() {
using huge_type = std::array<char, 20*1024*1024>;
std::vector<huge_type> v(1);
}
Étonnamment, il se bloque également! Le traceback (avec l'une des versions récentes de libstdc ++) mène au include/bits/stl_uninitialized.h
fichier, où nous pouvons voir les lignes suivantes:
typedef typename iterator_traits<_ForwardIterator>::value_type _ValueType;
std::fill(__first, __last, _ValueType());
Le vector
constructeur de redimensionnement doit initialiser par défaut les éléments, et c'est ainsi qu'il est implémenté. De toute évidence, _ValueType()
temporaire bloque la pile.
La question est de savoir s'il s'agit d'une implémentation conforme. Si oui, cela signifie en fait que l'utilisation d'un vecteur de types énormes est assez limitée, n'est-ce pas?
std::allocator
est utilisée.Réponses:
Il n'y a pas de limite sur la quantité de stockage automatique utilisée par l'API std.
Ils pourraient tous nécessiter 12 téraoctets d'espace de pile.
Cependant, cette API nécessite uniquement
Cpp17DefaultInsertable
et votre implémentation crée une instance supplémentaire par rapport à ce qui est requis par le constructeur. Sauf si elle est bloquée derrière la détection de l'objet est trivialement modifiable et copiable, cette implémentation semble illégale.la source
std::allocator
est utilisée. Je ne sais pas pourquoi ce cas spécial est fabriqué en premier lieu.std::fill
des types triviaux, qui utilise ensuitememcpy
pour dynamiser les octets dans des endroits, ce qui est potentiellement beaucoup plus rapide que de construire de nombreux objets individuels dans une boucle. Je crois que l'implémentation de libstdc ++ est conforme, mais provoquer un débordement de pile pour les objets énormes est un bogue de qualité d'implémentation (QoI). Je l'ai signalé sous gcc.gnu.org/PR94540 et le corrigerai.Je conteste l'hypothèse de "la plupart". Étant donné que la mémoire de l'objet énorme n'est jamais utilisée, le compilateur peut l'ignorer complètement et ne jamais allouer la mémoire, auquel cas il n'y aurait pas de plantage.
La norme C ++ ne limite pas l'utilisation de la pile, ni ne reconnaît même l'existence d'une pile. Donc, oui, il est conforme à la norme. Mais on pourrait considérer qu'il s'agit d'un problème de qualité de mise en œuvre.
Cela semble être le cas avec libstdc ++. Le crash n'a pas été reproduit avec libc ++ (en utilisant clang), il semble donc que ce ne soit pas une limitation dans le langage, mais plutôt uniquement dans cette implémentation particulière.
la source
Je ne suis ni juriste ni expert en standard C ++, mais cppreference.com dit:
Peut-être que je comprends mal "inséré par défaut", mais je m'attendrais à:
être équivalent à
Cette dernière version ne doit pas créer de copie de pile mais construire un énorme_type directement dans la mémoire dynamique du vecteur.
Je ne peux pas dire avec autorité que ce que vous voyez n'est pas conforme, mais ce n'est certainement pas ce que j'attendrais d'une implémentation de qualité.
la source
std::allocator
, il ne devrait donc pas y avoir de différence observable entre l'insertion directe dans la mémoire des vecteurs et la création d'une copie intermédiaire.emplace_back
mais pas seulement pour créer un vecteur. Ce qui signifie que vous pouvez avoirvector<mutex> v(1)
mais pasvector<mutex> v; v.emplace_back();
Pour quelque chose commehuge_type
vous pourriez avoir encore une opération d'allocation et de déplacement avec la deuxième version. Aucun des deux ne doit créer d'objets temporaires.vector::vector(size_type, Allocator const&)
requiert (Cpp17) DefaultInsertable