`String.assign (string.data (), 5)` est-il bien défini ou UB?

11

Un collègue voulait écrire ceci:

std::string_view strip_whitespace(std::string_view sv);

std::string line = "hello  ";
line = strip_whitespace(line);

J'ai dit que le retour string_viewme mettait mal à l'aise a priori , et en plus, le pseudonyme ici me semblait UB.

Je peux dire avec certitude que line = strip_whitespace(line)dans ce cas est équivalent à line = std::string_view(line.data(), 5). Je crois que va appeler string::operator=(const T&) [with T=string_view], qui est défini pour être équivalent à line.assign(const T&) [with T=string_view], qui est défini pour être équivalent à line.assign(line.data(), 5), qui est défini pour faire ceci:

Preconditions: [s, s + n) is a valid range.
Effects: Replaces the string controlled by *this with a copy of the range [s, s + n).
Returns: *this.

Mais cela ne dit pas ce qui se passe quand il y a un alias.

J'ai posé cette question sur le cpplang Slack hier et j'ai obtenu des réponses mitigées. Vous recherchez ici des réponses faisant autorité et / ou une analyse empirique des implémentations de fournisseurs de bibliothèques réelles.


J'ai écrit des cas de test pour string::assign, vector::assign, deque::assign, list::assignet forward_list::assign.

  • Libc ++ fait fonctionner tous ces cas de test.
  • Libstdc ++ les fait tous fonctionner sauf pour forward_list, qui segfaults.
  • Je ne connais pas la bibliothèque de MSVC.

Le segfault dans libstdc ++ me donne l'espoir que c'est UB; mais je vois aussi à la fois libc ++ et libstdc ++ faire de gros efforts pour que cela fonctionne au moins dans les cas courants.

Quuxplusone
la source
Avez-vous compilé les cas de test avec ASan et / ou les avez-vous exécutés sous Valgrind? Cela éliminerait la conjecture de savoir si le code provoque des violations d'accès, bien qu'il puisse encore fonctionner dans la pratique plutôt que par définition.
Konrad Rudolph
1
"Si une fonction membre ou un opérateur de basic_string lève une exception, cette fonction ou cet opérateur n'a aucun autre effet sur l'objet basic_string." - cela force l'allocation de stockage à se produire avant que le stockage existant ne soit libéré, de sorte qu'une exception est levée si l'allocation échoue, sans altération *this. Mais je ne vois rien pour empêcher la réutilisation du stockage existant, auquel cas cela devient non spécifié, car la sémantique de la copie du stockage n'est pas spécifiée.
Sam Varshavchik
2
Pour les conteneurs de séquence mentionnés, il s'agit certainement d'UB, en raison de la violation de précondition des assignexigences de [tab: container.seq.req] .
noyer

Réponses:

8

Sauf quelques exceptions dont la vôtre n'est pas une, appeler une fonction membre non const (c'est-à-dire assign) sur une chaîne invalide [...] les pointeurs vers ses éléments. Cela constitue une violation de la condition sine qua non à c'est une plage valide, donc ce comportement est indéfini.assign[s, s + n)

Notez que le string::operator=(string const&)langage a spécifiquement pour faire de l'auto-affectation un no-op.

ecatmur
la source
1
Alors, quel est exactement le point d'invalidation et le point auquel la condition préalable doit être remplie? La réponse semble supposer que la condition préalable doit être remplie après l'appel de la fonction membre.
noyer
1
@walnut Je ne suis pas un juriste de la langue (ni une personne avec une connaissance particulièrement étendue du C ++), mais lorsque nous inversons votre scénario, nous pouvons poser une question - la plage pourrait-elle être invalidée pendant l'exécution de assign? Si oui, alors nous devrons définir un point spécifique dans l'implémentation de l'attribut pour marquer quand exactement l'invalidation peut se produire, et je crois que ce n'est pas quelque chose que C ++ ferait. Je pourrais toutefois avoir tord.
Fureeish
2
@Fureeish Je ne sais pas non plus, mais voir par exemple le numéro 526 du LWG , fermé comme " pas un défaut ", qui mentionne dans sa recommandation de fermeture qui std::vector::insert(iterator pos, const T& value)doit fonctionner s'il se valuetrouve dans le vecteur lui-même, car la norme ne spécifie pas qu'il est autorisé à ne pas fonctionner, même si cette référence peut être invalidée par l'appel.
noyer
1
@walnut " est nécessaire pour fonctionner parce que la norme ne donne pas la permission de ne pas fonctionner. " - j'adore . Sooo ... vaut-il la peine de demander ce qui se passe dans la pratique ? La mise en œuvre est-elle requise pour faire une copie de l'argument dans une telle situation? Comment pourriez-vous le mettre en œuvre de manière réaliste ..? J'ai entendu parler d'une norme exigeant que les compilateurs fassent l'impossible - est-ce l'un de ces cas? Quoi qu'il en soit, merci pour le commentaire!
Fureeish
1
@Fureeish En fait, mon exemple précédent (maintenant supprimé) ne testait pas réellement ce que je voulais tester. Voici un exemple fixe montrant que libc ++ et libstdc ++ copient réellement avant de passer à la réallocation comme requis.
noyer