Привет!
Знатокам C++ это конечно всё очевидно и естественно, а вот я как-то привык, что знаковое переполнение в С++ не приводит к настоящему неопределенному поведению на конкретной платформе. То есть понятно, что в зависимости от big-endianness/little-endian результат может получиться разным.
Случайно нашел пример забавного поведения:
#include <iostream>
int main()
{
for (int i = 0; i < 300; i++)
std::cout << i << " " << i * 12345678 << std::endl;
}
Этот код при компиляции с -O2
в современном g++ приводит к бесконечному циклу. Неожиданно, правда?
Полагаю, ход рассуждений компилятора примерно такой:
i * 12345678
, то это выражение никогда не переполняется. Значит, внутри тела цикла |i| ≤ 173.i++
всегда верно i ≤ 174.true
.Я бы назвал это багом — компилятор слишком много на себя берёт.
Это не баг, это — неопределенное поведение, при котором компилятор вправе делать все_что_угодно.
Должно быть хоть какое-то логичное ограничение, где это самое неопределённое поведение может вылезти. Получается, что из-за undefined behaviour где-то, где его результат ни на что не влияет (кроме вывода на экран в данном случае), undefined behaviour распространяется на другой участок кода, где всё как раз прекрасно defined. Так можно докатится до ситуации, где компилятор, заметив где-то потенциальное переполнение просто рубит всё программу и заменяет одним NOP'ом. Очень эффективная оптимизация — если что-то может не работать, зачем вообще это делать.
Хорошо, я согласен, что формально требования стандарта выполнены. Всё равно остаётся вопрос:
Если оптимизация настолько умна, то почему она режет код вместо того, чтобы выдать warning о потенциальном переполнении?
Потому что она не всегда понимает, откуда берется тот код, который она оптимизирует. Часто это поинлайненый код или код после каких-то другиз оптимизаций и это позволяет компилятору убрать лишние проверки/выкинуть недостижимые ветки кода
AlexDmitriev,
Ещё один вопрос — я с трудом представляю ситуацию, когда эта оптимизация (насколько я понял — aggressive-loop-optimization) может быть полезной. Точнее не представляю. Это либо кривой код, который лучше бы исправить, либо просто ошибка. Нормальный код, которому может принести пользу такая оптимизация... Не знаю, разве что параметризированный шаблон приходит в голову, где конкретный параметр шаблона позволяет "схлопнуть" цикл, которй может понадобится в общем случае. Мне кажется эта оптимизация evil :)
http://kristerw.blogspot.com/2016/02/how-undefined-signed-overflow-enables.html
И комментарии внизу очень похожи на мои, весело. Так что я просто прочитал там — остался при своём мнении (it's evil), но лучше понял обе точки зрения — спасибо!
Хорошо, когда редактор умный
Или когда оптимизатор тупой — на MSVC 2013 никаких проблем :)
Да на MSVC вообще не бывает проблем, я помню только одну, о которой постили на CF, а о багах g++ упоминали точно не менее пяти раз. А еще здесь минусуют за упоминание этого факта :)
А так?
А так молчит
Это не редактор умный, а g++.
Если будете из консоли компилировать код с -Wall -Wextra, то в точности те же самые варнинги получите.
Я компилирую без -Wall -Wextra, и тем не менее получил уведомление о возможной ошибке, так что таки редактор.
Прошу прощения, речь не в -Wall и -Wextra, просто я их использую всегда. Дейсвительно, и без них те же варнинги g++ даёт.
Поскольку вывод g++ совпадает с предупреждениями Вашего редактора, очевидно, что кто-то у кого-то эти варнинги заимствует.
Очевидно, что это не g++.
Что я делаю не так? Мой компилятор молчит без дополнительных флагов, а редактор всё равно подсказывает
Возможно у вас просто несколько разных g++-ов стоит.
У меня один g++. Скорее у вашего g++ выставлен флаг -Wall, или ему подобные, поумолчанию — сделайте g++ -dumpspecs или посмотрите по переменным окружения
Нет. И там и там ничего нет.
Last sentence is written in russian, on eng interface :(
This is so strange! I tested it on my machine, and it does indeed run infinitely.
It's interesting I believe. Why does that happen?
There is russian explanation from yeputons, so I am just translating:
-Waggresive-loop-optimization option is turned on by -O2,
Optimizer infers that because i*12345678 should never overflow, i will never be higher then 173, which means that i < 300 is always true, so it replaces condition "i < 300" with true.
strange, if you replace endl with '\n' , the code no longer runs infinitely
My take on this is that std::endl flushes cache, so the output operation happens in the cycle, so output is produced within the cycle and is expected to conform to language rules, which means no overflow is expected and optimization works.
If do you "\n", cache is flushed after the cycle ends, so there is no external output within the cycle, which somehow turns off optimization.
Strange indeed. It probably takes a guy from g++ development team to tell exactly what happens.
By the way, it looks like it's possible to disable that behavior in GCC with a special key:
-fno-strict-overflow
Это баян, конечно.
Вот ещё интересный пример:
Функция всегда возвращает true
http://ideone.com/z9npmc
Объясните почему?)
Компилятор имеет право считать что UB в коде нет.
Если i доходит до 4, то получим UB (выход за пределы массива). Значит i не дойдет до 4. Значит раньше мы вышли с return true.
Следовательно заменяет весь код на return true
clang 3.9.0 не воспроизводится.
А ещё есть почти легендарный код, который своим выполнением "опровергает" великую теорему ферма.
https://habrahabr.ru/post/229963/
И он неверен, по крайней мере в C11:
C11 clarifies the answer to this question, in the draft C11 standard section 6.8.5 Iteration statements added the following paragraph:
An iteration statement whose controlling expression is not a constant expression,156) that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate.157)
and footnote 157 says:
This is intended to allow compiler transformations such as removal of empty loops even when termination cannot be proven.
Верный код есть в статье из которой и сделали статью на Хабре, понятия не идею зачем автор изменил код.
const int MAX = 1000; int a=1,b=1,c=1; while ((c*c*c) != ((a*a*a)+(b*b*b))) { a++; if (a>MAX) { a=1; b++; } if (b>MAX) { b=1; c++; } if (c>MAX) { c=1; } }
Здесь не константа в условии.
На моем компьютере код с Хабра выполняется честно, потому я решил разобраться. Напишите если я что-то не так понял и у кого-то он работает
Да, вы правы.
И это касается не только с++11, но и других стандартов (например ansi, c11, c99, c++03, ...)
Вы говорили про c++11, а процитировали про c11
Что такое 1000?
Это число
Мы перебираем a,b и c до 1000 пытаясь найти среди них a^3+b^3=c^3
And sometimes such advanced optimizations even lead to vulnerabilities: http://lwn.net/Articles/342330/
I used "\n" instead of endl and got this:
Then this must me a warning bug. Initially whan I was reading this post I thought that gcc does not produce any warnings about that, but it is not good that warning is produced in one case and is not produced in another case.
I confirmed the difference in warning reporting between
std::endl
and"\n"
with GCC 4.8.1 and 4.8.2 . The bug seems to be fixed in GCC >= 4.9.0, I couldn't test if the fix is included in GCC 4.8.3 — 4.8.5 .Mine:
gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3)
This is another example of breaking the code with higher optimization when the code is not completely correct, Nicely explained as well. http://stackoverflow.com/questions/36626810/g-optimization-breaks-for-loops