Подтвердить что ты не робот

Почему GCC-O3 вызывает бесконечное std:: distance с итераторами фильтров над std:: deque?

После сильной боли и страданий я обнаружил очень странное поведение, когда std::distance никогда не возвращается, если задан диапазон boost::filter_iterator по сравнению с std::deque. По-видимому, проблема уникальна для GCC (6.1+) с оптимизацией -O3. Вот пример, демонстрирующий поведение с нарушением:

#include <string>
#include <deque>
#include <iterator>
#include <iostream>

#include <boost/iterator/filter_iterator.hpp>

struct Foo
{
    std::string bar, s = "";
    char a = '\0';
};

int main()
{
    const std::deque<Foo> foos(14, {""});
    const std::string test {};
    const auto p = [test] (const auto& foo) { return foo.bar == test; };
    using boost::make_filter_iterator;
    const auto begin = make_filter_iterator(p, std::cbegin(foos), std::cend(foos));
    const auto end   = make_filter_iterator(p, std::cend(foos), std::cend(foos));
    std::cout << std::distance(begin, end) << std::endl;
}

Некоторые наблюдения:

  • GCC с оптимизацией -O2 или меньше возвращается, как ожидалось.
  • Clang (3.8) возвращает правильный ответ с любым уровнем оптимизации.
  • Изменение std::deque до std::vector или std::list приводит к ожидаемому поведению.
  • 14 является критическим; что-то меньшее, и проблема исчезает.
  • Важна sizeof(Foo); удаление s или a заставляет проблему уйти.
  • Захват test по ссылке или просто сравнение с константным выражением (например, foo.bar == " ") приводит к нормальному поведению.
  • Предупреждений компилятора нет (с -Wall -Wextra -pedantic).
  • Valgrind не сообщает об ошибках.
  • Используйте fsanitize=undefined, и проблема исчезнет.

Что происходит?

4b9b3361

Ответ 2

Я думаю, что эти выводы ниже могут быть полезны как для улучшения отчета об ошибках, так и для использования в вашем коде для решения этой проблемы.

Отлаживая оптимизированный вывод и играя с флагами оптимизации и с незначительными изменениями кода, я пришел к выводу о конкретных флагах оптимизации, которые вызывают ошибку.

Набор параметров:

-O -fno-auto-inc-dec -fno-branch-count-reg -fno-combine-stack-adjustments -fno-compare-elim -fno-cprop-registers -fno-dce -fno-defer-pop -fno-delayed-branch -fno-dse -fno-forward-propagate -fno-guess-branch-probability -fno-if-conversion2 -fno-if-conversion -fno-inline-functions-called-once -fno-ipa-pure-const -fno-ipa-profile -fno-ipa-reference -fno-merge-constants -fno-move-loop-invariants -fno-reorder-blocks -fno-shrink-wrap -fno-split-wide-types -fno-ssa-backprop -fno-ssa-phiopt -fno-tree-bit-ccp -fno-tree-ccp -fno-tree-ch -fno-tree-coalesce-vars -fno-tree-phiprop -fno-tree-sink -fno-tree-slsr -fno-tree-dse -fno-tree-forwprop -fno-tree-fre -fno-unit-at-a-time -fno-tree-ter -fno-tree-sra -fno-tree-copy-prop -fstrict-aliasing -ftree-slp-vectorize -std=c++14

Извините за этот длинный набор, но то, что я действительно хотел, было что-то вроде: -O0 -ftree-copy-prop -ftree-pta -ftree-dce -fstrict-aliasing -ftree-slp-vectorize (я тоже пробовал с -Og), а также магические шаги O1...

Обратите внимание, что только -O3 -f-no-tree-slp-vectorize уже будет исправлять поведение, но, используя полные параметры, которые я отправил, отладка почти проста...

Кроме того, похоже, что существование оператора ==(string, string) порождает путаницу в компиляторе.

Если вы посмотрите на код, вставленный ниже, где все прокомментированные кодом #if 0, когда активировано работает вместо исходного кода, вы можете найти проблему, где я этого не сделал.

Обратите внимание, что оператор ==() даже не вызывается, потому что foo.a != '\0' всегда является истинным в тесте. Поэтому похоже, что существование делает компилятор для генерации плохого кода.

Обратите внимание также, что любой из прокомментированного кода внутри цикла также меняет поведение на ожидаемый, и почему я подозревал флаги векторизации для стартеров.

#include <string>
#include <deque>
#include <iterator>
#include <iostream>

#include <boost/iterator/filter_iterator.hpp>
#include <string.h>

struct Foo
{
    std::string bar, s = "";
    char a = 'n';
};

std::ostream& operator<<(std::ostream& os, const Foo& f)
{
    os << f.bar << '/' << f.a;
    return os;
}

int main()
{
    std::deque<Foo> foos(14, {"abc"});
    const std::string test {"abc"};
    Foo other;
    other.bar = "last"; other.a = 'l';
    foos.push_back(other);
    other.bar = "first";
    other.a = 'f';
    foos.push_front(other);
    //
#if 0
    const auto p = [test] (const auto& foo) { return foo.a != '\0'; };
#elif 0
    const auto p = [test] (const auto& foo) {
        bool  rc =  (foo.a != '\0');
        if (!rc)
            rc = (foo.bar == std::string(test));
        return rc;
    };
#elif 1
    const auto p = [test] (const auto& foo) {
        bool  rc =  (foo.a != '\0');
        if (!rc)
            rc = (foo.bar == test);
        return rc;
    };
#endif
    using boost::make_filter_iterator;
    const auto begin = make_filter_iterator(p, std::cbegin(foos), std::cend(foos));
    const auto end   = make_filter_iterator(p, std::cend(foos), std::cend(foos));
    std::cout << std::distance(end, end) << std::endl;
    std::cout << std::distance(begin, begin) << std::endl;
    std::cout << std::distance(std::cbegin(foos), std::cend(foos)) << std::endl;

    auto __first = begin;
    auto __last = end;

    int __n = 0;
    //std::cout << __last << std::endl;
    //std::deque<char> trace;
    //Foo trace[21];
    const int max = foos.size();
    char trace[max+5]; memset(trace, 'c', sizeof(trace));

    std::cout << max << std::endl;
    std::cout << *__last << std::endl;

    while (__first != __last)
    {
        trace[__n] = (*__first).a;
        //trace[__n] = (*__first);
        //trace.push_back((*__first).a);
        //std::cout << *__first << std::endl;
        ++__n;
        ++__first;
        if (__n > max + 5)
            break;
        //std::cout << __n << std::endl;
        //std::cout << (__first != __last) << std::endl;
    }

    for (auto f: trace)
        std::cout << f  << std::endl;
    std::cout << "Tadaaaaa: " <<  __n << std::endl;

    //std::cout << std::distance(begin, end) << std::endl;

}