Re[3]: каждый раз, когда вы пишете i++ + ++i...
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 19.06.14 19:07
Оценка:
Здравствуйте, watchmaker, Вы писали:

X>>Обещают в 4.8.3 пофиксить: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58143

W>Не, это совершенно другой баг.

Как минимум Comment 9 как раз про идентичный баг.
The God is real, unless declared integer.
Re: Не используйте int там где он не нужен
От: Serg27  
Дата: 20.06.14 09:07
Оценка:
У меня есть старое правило — не использовать int там где он не нужен. В частности для счетчиков циклов я всегда использую size_t. Это позволяет избежать кучу проблем, на которые можно наступить. Это код — как раз пример такой проблемы. Изменение типа счетчика цикла с int на unsigned int полностью убирает проблему — здесь
Я тут не спорю об правильности/неправильности поведения компилятора и всяких других интересных вещах, о которых интересно поговорить/почитать. Я тут говорю о некоторых простых правилах, которые позволяют избежать проблем при написании производственного кода. Правило "Не используйте int там где он не нужен" одно из них. Все же арифметика unsigned величин значительно проще чем для signed и гораздо предсказуемее. В качестве иллюстрации приведу статью на codeproject — здесь В ней автор "оптимизирует" руками вычислительный алгоритм. Мне его методы показались подозрительными (это умеют делать компиляторы уже лет 40) и подозрительным показалось разница в производительности x86 и 64-битного кода. И действительно замена типа переменной с long long на unsigned long long сразу дала нужный прирост производительности. Можно посмотреть ветку "best optimization" в обсуждении этой статьи. (здесь)
Re: каждый раз, когда вы пишете i++ + ++i...
От: /aka/ СССР  
Дата: 20.06.14 09:12
Оценка: 14 (2)
Здравствуйте, Кодт, Вы писали:

К>Я немножко дополировал и заретушировал его.


Убрал ретушь. gcc 4.8.2, вызываю так: g++ -O2 1.cpp -Wall

#include <iostream>
#include <stdio.h>

int main()
{
    for(int i=0; i < 10; ++i)
    {
        std::cout << i << " : " << i*1000000000 << " : " << std::endl;
    }
}


Нет варнинга, тихо компилируется и виснет в бесконечном цикле. И следующее:

#include <iostream>
#include <stdio.h>

int main()
{
    for(int i=0; i < 10; ++i)
    {
        printf ("%d %d\n", i, i*1000000000);
        std::cout << i << " : " << i*1000000000 << " : " << std::endl;
    }
}


Тоже компилируется без варнинга и тоже виснет.

Однако же если не пользоваться богомерзкими стримами, оставить один тёплый ламповый printf:

#include <iostream>
#include <stdio.h>

int main()
{
    for(int i=0; i < 10; ++i)
    {
        printf ("%d %d\n", i, i*1000000000);
    }
}


Получаю то, что должен получить:

aka@c8:/tmp$ g++ -O2 1.cpp -Wall
1.cpp: In function ‘int main()’:
1.cpp:8:43: warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
         printf ("%d %d\n", i, i*1000000000);
                                           ^
1.cpp:6:5: note: containing loop
     for(int i=0; i < 10; ++i)
     ^


Плюсы зло
Re[2]: Google C++ Style Guide
От: Qbit86 Кипр
Дата: 20.06.14 09:20
Оценка: +1
Здравствуйте, Serg27, Вы писали:

S>У меня есть старое правило — не использовать int там где он не нужен. В частности для счетчиков циклов я всегда использую size_t. Это позволяет избежать кучу проблем, на которые можно наступить.


«Document that a variable is non-negative using assertions. Don't use an unsigned type.» Google C++ Style Guide
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Google C++ Style Guide
От: Serg27  
Дата: 20.06.14 09:51
Оценка: 5 (1) +1
> «Document that a variable is non-negative using assertions. Don't use an unsigned type.» Google C++ Style Guide

ну я прошел по ссылке. В приведенном ими примере использовать int нужно (с unsigned будет просто обычная ошибка программиста). Документировать, что величина должна быть положительной действительно нужно с помощью assert. С чем тут спорить? Как это соотносится с моим утверждением — "Не используйте int там где он не нужен" я не понял. У вас какая мысль то была?

P.S.
Вообще-то Вы дали ссылку на корпоративные правила. Их очень много в мире. Сочиняются они вполне по конкретным (в каждом случае) причинам. Не понимая этих причин использовать их как истину в последней инстанции довольно не умно. Ну, например, в этих же правилах написано "We do not use C++ exceptions. ". И что мы сейчас срач поднимем по этому поводу?
P.P.S
у них там есть довольно полное объяснение почему они написали "We do not use C++ exceptions.". Почитать это интересно, но это не означает, что в своем коде надо отказаться от exceptions, только потому что так сделал сам велиrий Google.
Re[4]: каждый раз, когда вы пишете i++ + ++i...
От: Xeor Россия  
Дата: 20.06.14 10:07
Оценка:
Здравствуйте, netch80, Вы писали:

N>Как минимум Comment 9 как раз про идентичный баг.


Да и описание фикса подходит под проблему:

    * tree-ssa-loop-im.c (arith_code_with_undefined_signed_overflow):
    New function.
    (rewrite_to_defined_overflow): Likewise.
    (move_computations_dom_walker::before_dom): Rewrite stmts
    with undefined signed overflow that are not always executed
    into unsigned arithmetic.
Re[2]: каждый раз, когда вы пишете i++ + ++i...
От: Xeor Россия  
Дата: 20.06.14 10:10
Оценка:
Здравствуйте, /aka/, Вы писали:

A>Однако же если не пользоваться богомерзкими стримами, оставить один тёплый ламповый printf

A>Получаю то, что должен получить

Богомерзкие стримы вносят в тело цикла неявный выход через throw std::bad_cast. Из-за этого он становится слишком сложный для того анализа, что задействуется без лишних выходов, как в случае с printf.
Re[2]: каждый раз, когда вы пишете i++ + ++i...
От: Xeor Россия  
Дата: 20.06.14 10:15
Оценка:
Здравствуйте, netch80, Вы писали:

N>2. А вот ещё более суровый вариант

N>это всё, оно остановилось.

В обоих случаях gcc из-за знакового переполнения считает, что цикл будет выполнен не более 3 раз. Просто иногда (в первом случае) это приводит к выкидыванию проверки на превышение 10 итераций, а иногда (как в этом случае) gcc считает цикл пригодным для loop unroll-а, и делает анролл на 3 итерации, т.к. уверен что больше точно не будет.
Re: каждый раз, когда вы пишете i++ + ++i...
От: ononim  
Дата: 20.06.14 12:41
Оценка:
...Unbelivable Behaviour
Как много веселых ребят, и все делают велосипед...
Re[5]: каждый раз, когда вы пишете i++ + ++i...
От: watchmaker  
Дата: 20.06.14 14:39
Оценка:
X>Здравствуйте, netch80, Вы писали:

N>>Как минимум Comment 9 как раз про идентичный баг.


Безусловно, это так. Но это не меняет того, что ни описанная проблема, ни патч никоим образом не относятся к комментарию №9 — там просто нерелевантные рассуждения.

Патч именно исправляет проблему с UB в мёртвом коде, который считается живым из-за ошибки в оптимизаторе циклов. Так, до патча компилятор выносил инвариант из цикла, который мог приводить к UB, но не приводил из-за того, что цикл никогда не выполнялся (а в исходном примере темы ситуация, грубо говоря, обратная — цикл-то выполняется). После патча компилятор по прежнему может выносить инвариант из цикла, но только предварительно преобразовав его в вариант без UB.

А саму проблему, о которой рассказано в комментарии №9, там не чинят, ибо она уже давным-давно разрешена. И решение — использовать -fno-strict-overflow.

Здравствуйте, Xeor, Вы писали:

X>Да и описание фикса подходит под проблему:


Ну как будто ты не видел ни одного репозитория с commit message вида "fix", "bugfix" или "small imrovement" Невозможно без контекста понять о чём они. А главное, по описанию они тоже подходят под проблему
У gcc, конечно, с комментариями всё намного лучше, но всё равно контекст важен. Так что в случае сомнений нужно смотреть код. А из кода патча видно, что он даже не срабатывает на исходном примере этой темы.
Re[6]: каждый раз, когда вы пишете i++ + ++i...
От: Xeor Россия  
Дата: 20.06.14 15:03
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Патч именно исправляет проблему с UB в мёртвом коде, который считается живым из-за ошибки в оптимизаторе циклов. Так, до патча компилятор выносил инвариант из цикла, который мог приводить к UB, но не приводил из-за того, что цикл никогда не выполнялся (а в исходном примере темы ситуация, грубо говоря, обратная — цикл-то выполняется). После патча компилятор по прежнему может выносить инвариант из цикла, но только предварительно преобразовав его в вариант без UB.


W>А саму проблему, о которой рассказано в комментарии №9, там не чинят, ибо она уже давным-давно разрешена. И решение — использовать -fno-strict-overflow.


Да, действительно, там сначала запостили фикс, который починил бы эту проблему, но потом сделали по-другому, а я решил что довели до ума.
Re[3]: Произвол компилятора
От: ononim  
Дата: 20.06.14 18:36
Оценка:
Q>Компилятор вместо «i < 10» статически подставляет «true», потому что полагает i не выходящем из диапазона [-2, 2].
Q>А полагает он это, потому что может. Это его способ отрицания реальности. Если закрыть глаза на ++i, то переполнение исчезнет. Закрыть глаза — одна из возможных реализаций неопределённого поведения.
Я в асм листинг не смотрел, но первое что в голову пришло, это примерно такой вот псевдокод:
for(int i=0; i < 10; ++i)
{
//блаблабла
i*= 1000000000;
//блаблабла
i/= 1000000000;
//блаблабла
}

— типа сэкономили на памяти
Как много веселых ребят, и все делают велосипед...
Re[4]: каждый раз, когда вы пишете i++ + ++i...
От: enji  
Дата: 24.07.14 11:57
Оценка:
Здравствуйте, watchmaker, Вы писали:


N>>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.

W>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?

Охренеть

А из каких позывов компилер дергает EraseAll? Do не может быть 0, следовательно есть внешний код, который дергает NeverCalled?
Re[4]: Произвол компилятора
От: alzt  
Дата: 26.07.14 18:37
Оценка:
Здравствуйте, k55, Вы писали:

k55>Яснее не стало.

k55>Какого лешего он полагает что "i не выходящем из диапазона [-2, 2]", если i изменяется только тут ++i??

k55>Т.е. UB в любом месте кода, даже если оно случилось во временной переменной, может повлиять вооообще на все что угодно?


Если ты пишешь
x = y/z;
if(!z)
  func(0);

то компилятор видит, что z либо равно нулю и тогда произойдёт ужасное, либо z не равно нулю и тогда функция func никогда не будет вызвана и код можно выкинуть. Смысла делить на ноль всё равно нет, поэтому можно этот факт распространить дальше и оптимизировать другие числа, которые как-то зависят от z.
Re[3]: каждый раз, когда вы пишете i++ + ++i...
От: Vain Россия google.ru
Дата: 27.07.14 09:38
Оценка:
Здравствуйте, Xeor, Вы писали:

A>>Однако же если не пользоваться богомерзкими стримами, оставить один тёплый ламповый printf

A>>Получаю то, что должен получить
X>Богомерзкие стримы вносят в тело цикла неявный выход через throw std::bad_cast. Из-за этого он становится слишком сложный для того анализа, что задействуется без лишних выходов, как в случае с printf.
ещё один фейл в копилку уродливых стримов
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.