42 votes

Itérateur inverse renvoie la poubelle lorsque optimisé

J'ai un AsIterator classe de modèle qui prend un numérique-comme le type, dans cet exemple juste un int, et les convertit en un itérateur (++ et -- d'incrémentation et de décrémentation du nombre et operator* retourne une référence).

Cela fonctionne très bien , sauf si elle est enveloppée dans une std::reverse_iterator et compilé avec toute optimisation (-O est suffisant). Quand j'optimiser le binaire, le compilateur se déshabille sur le déréférencement d'appel à l' reverse_iterator et le remplace par un peu bizarre de la valeur. Il faut noter qu'il est toujours fait le bon nombre d'itérations. C'est juste la valeur obtenue par l'itérateur inverse, c'est-à ordures.

Considérons le code suivant:

#include <iterator>
#include <cstdio>

template<typename T>
class AsIterator : public std::iterator<std::bidirectional_iterator_tag, T> {
    T v;
public:
    AsIterator(const T & init) : v(init) {}

    T &operator*() { return v; }

    AsIterator &operator++() { ++v; return *this; }
    AsIterator operator++(int) { AsIterator copy(*this); ++(*this); return copy; }
    AsIterator &operator--() { --v; return *this; }
    AsIterator operator--(int) { AsIterator copy(*this); --(*this); return copy; }

    bool operator!=(const AsIterator &other) const {return v != other.v;}
    bool operator==(const AsIterator &other) const {return v == other.v;}
};

typedef std::reverse_iterator<AsIterator<int>> ReverseIt;

int main() {
    int a = 0, b = 0;
    printf("Insert two integers: ");
    scanf("%d %d", &a, &b);
    if (b < a) std::swap(a, b);

    AsIterator<int> real_begin(a);
    AsIterator<int> real_end(b);
    for (ReverseIt rev_it(real_end); rev_it != ReverseIt(real_begin); ++rev_it) {
        printf("%d\n", *rev_it);
    }
    return 0;
}

Cela devrait supposé boucle vers le bas à partir de la plus haute inséré nombre le plus bas et de les imprimer, comme dans cette course (compilé avec -O0):

Insert two integers: 1 4 
3
2
1

Ce que je reçois avec -O est plutôt:

Insert two integers: 1 4 
1
0
0

Vous pouvez essayer en ligne ici; les chiffres peuvent varier, mais ils sont toujours "mal" lors de l'optimisation de la binaire.


Ce que j'ai essayé:

  • coder en dur l'entrée des entiers est assez pour produire le même résultat;
  • le problème persiste avec gcc 5.4.0 et clang 3.8.0, également lors de l'utilisation de la libc++;
  • de faire de tous les objets const (c'est à dire de revenir const int &, et de déclarer toutes les variables en tant que tel) ne résout pas le problème;
  • à l'aide de l' reverse_iterator de la même manière, par exemple, certains std::vector<int> fonctionne très bien;
  • si je viens d'utiliser AsIterator<int> pour une normale de l'avant ou vers l'arrière de la boucle, il fonctionne très bien.
  • dans mes tests, la constante 0 qui est imprimé est en fait codé en dur par le compilateur, les appels à l' printf tous ressembler à ceci lorsqu'il est compilé avec -S -O:
    movl    $.L.str.2, %edi  # .L.str.2 is "%d\n"
    xorl    %eax, %eax
    callq   printf

Compte tenu de la consistance de clang et de la gccdans le comportement ici, je suis assez sûr qu'ils sont en train de faire et j'ai mal compris, mais je ne peux vraiment pas le voir.

45voto

Vittorio Romeo Points 2559

En regardant std::reverse_iterators' libstdc++ mise en œuvre révèle quelque chose d'intéressant:

  /**
   *  @return  A reference to the value at @c --current
   *
   *  This requires that @c --current is dereferenceable.
   *
   *  @warning This implementation requires that for an iterator of the
   *           underlying iterator type, @c x, a reference obtained by
   *           @c *x remains valid after @c x has been modified or
   *           destroyed. This is a bug: http://gcc.gnu.org/PR51823
  */
  _GLIBCXX17_CONSTEXPR reference
  operator*() const
  {
    _Iterator __tmp = current;
     return *--__tmp;
  }

L' @warning article nous dit que l'exigence du sous-jacent itérateur de type est qu' *x doit demeurer valide même après le sous-jacent itérateur est modifié ou détruit.

En regardant le bug mentionné lien révèle plusieurs informations intéressantes:

à un certain moment entre le C++03 et C++11 la définition de reverse_iterator::operator* a été modifié pour préciser cela, faire libstdc++de mise en œuvre de mal. La norme dit maintenant:

[ Remarque: Cette opération doit utiliser une membre auxiliaire de la variable plutôt qu'une variable temporaire pour éviter de retourner une référence qui persiste au-delà de la durée de vie de son associé de l'itérateur. (Voir 24.2.) -la note de fin ]

commentaire par Jonathan Wakely (2012)

De sorte qu'il ressemble à un bug... mais à la fin de la rubrique:

La définition de reverse_iterator a été modifié pour le C++03 version, qui n'utilise pas un membre supplémentaire, de sorte que "cachant des itérateurs" ne peut pas être utilisé avec reverse_iterator.

commentaire par Jonathan Wakely (2014)

Il semble donc que l'utilisation d' std::reverse_iterator avec "cachant des itérateurs", en effet, conduire à UB.


En regardant la RD 2204: "reverse_iterator ne devrait pas nécessiter une deuxième copie de la base de itérateur" précise la question:

Cette remarque dans 24.5.1.3.4 [inverse.iter.op.star]/2:

[ Remarque: Cette opération doit utiliser une membre auxiliaire de la variable plutôt qu'une variable temporaire pour éviter de retourner une référence qui persiste au-delà de la durée de vie de son associé de l'itérateur. (Voir 24.2.) -la note de fin ]

[ma note: je pense que la remarque ci-dessus de fixer votre UB question]

est incorrect, car ces itérateur implémentations sont exclues par 24.2.5 [de l'avant.les itérateurs]/6, où il est dit:

Si a et b sont deux dereferenceable, alors a == b si et seulement si *a *et b sont liés à l'objet même.

Prograide.com

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.

Powered by:

X