Как я узнаю, что компилятор нарушил мой код и что мне делать, если это был компилятор?

14

Время от времени код C ++ не будет работать при компиляции с некоторым уровнем оптимизации. Это может быть компилятор, выполняющий оптимизацию, которая нарушает код, или это может быть код, содержащий неопределенное поведение, которое позволяет компилятору делать все, что он чувствует.

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

Sharptooth
источник
43
Скорее всего это ты.
littleadv
9
@littleadv, даже последние версии gcc и msvc полны ошибок, поэтому я не был бы в этом уверен.
SK-logic
3
У вас все предупреждения включены?
@ Thorbjørn Ravn Andersen: Да, они включены.
Sharp
3
FWIW: 1) я стараюсь не делать ничего хитрого, что могло бы соблазнить компилятор испортить 2) единственное место, где важны флаги оптимизации (для скорости), в коде, где программный счетчик тратит значительную часть своего времени. Если вы не пишете жесткие циклы ЦП, во многих приложениях ПК тратит практически все свое время в библиотеках или на ввод-вывод. В такого рода приложениях переключатели / O совсем не помогают.
Майк Данлавей

Ответы:

19

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

В любом случае, если вы можете доказать, что нашли ошибку компилятора (на основе спецификации языка), сообщите об этом разработчикам компилятора, чтобы они могли исправить ее через некоторое время.

Петер Тёрёк
источник
@ SK-logic, достаточно справедливо, у меня нет статистики, подтверждающей это. Он основан на моем собственном опыте, и я признаю, что редко расширял границы языка и / или компилятора - другие могут делать это чаще.
Петер Тёрёк
(1) @ SK-Logic: только что обнаружил ошибку компилятора C ++, тот же код, попробовал на одном компиляторе и работает, попробовал в другом, он сломался.
umlcat
8
@umlcat: скорее всего, это был ваш код в зависимости от неуказанного поведения; на одном компиляторе это соответствует вашим ожиданиям, на другом - нет. это не значит, что он сломан.
Хавьер
@ Ритч Мелтон, ты когда-нибудь использовал LTO?
SK-logic
1
Я согласен с Crashworks, когда говорю об игровых приставках. Нет ничего необычного в том, чтобы найти ошибки эзотерического компилятора в этой конкретной ситуации. Если вы нацелены на обычные ПК, используя широко используемый компилятор, то вряд ли вы столкнетесь с ошибкой компилятора, которую никто раньше не видел.
Тревор Пауэлл
14

Как обычно, как и с любыми другими ошибками: выполните контролируемый эксперимент. Сузьте область подозрений, отключите оптимизацию для всего остального и начните изменять оптимизации, примененные к этому фрагменту кода. Как только вы получите 100% воспроизводимость, начните изменять свой код, вводя вещи, которые могут нарушить определенную оптимизацию (например, вводить возможный псевдоним указателя, вставлять внешние вызовы с потенциальными побочными эффектами и т. Д.). Также может помочь просмотр кода сборки в отладчике.

SK-логика
источник
может помочь с чем? Если это ошибка компилятора - ну и что?
littleadv
2
@littleadv, если это ошибка компилятора, вы можете попытаться ее исправить (или просто сообщить об этом должным образом, во всех подробностях), или вы можете узнать, как избежать этого в будущем, если вы обречены продолжать использовать это версия вашего компилятора на время. Если это что-то с вашим собственным кодом, одна из многочисленных пограничных проблем C ++, этот вид проверки также помогает исправить ошибку и избежать ее появления в будущем.
SK-logic
Итак, как я сказал в своем ответе - кроме отчетности, нет большой разницы в лечении, независимо от того, чья это вина.
littleadv
3
@littleadv, не понимая природу проблемы, вы, вероятно, столкнетесь с ней снова и снова. И часто есть возможность исправить компилятор самостоятельно. И да, к сожалению, совсем не маловероятно найти ошибку в компиляторе C ++.
SK-logic
10

Изучите полученный код сборки и посмотрите, выполняет ли он то, к чему призывает ваш источник. Помните, что шансы очень высоки, потому что это действительно ваш код, в чем-то неочевидный.

Лорен Печтель
источник
1
Это действительно единственный ответ на этот вопрос. Работа компиляторов, в данном случае, заключается в том, чтобы перевести вас с C ++ на язык ассемблера. Вы думаете, что это компилятор ... проверьте работу компиляторов. Это так просто.
old_timer
7

За 30 лет программирования число найденных мной ошибок подлинного компилятора (генерации кода) все еще составляет ~ 10. Число моих собственных (и других) ошибок, которые я обнаружил и исправил за тот же период, вероятно, составляет > 10000 Моё «правило большого пальца» заключается в том, что вероятность того, что ошибка возникла из-за компилятора, составляет <0,001.

Пол Р
источник
1
Ты счастливчик. Мой средний показатель составляет около 1 действительно плохой ошибки в месяц, и незначительные пограничные проблемы встречаются гораздо чаще. И чем выше уровень оптимизации вы используете, тем выше вероятность ошибки компилятора. Если вы пытаетесь использовать -O3 и LTO, вам очень повезет, что вы не найдете пару из них в кратчайшие сроки. И здесь я считаю только ошибки в релизных версиях - как разработчик компиляторов, я сталкиваюсь с гораздо большим количеством проблем такого рода в моей работе, но это не считается. Я просто знаю, как легко испортить компилятор.
SK-logic
2
25 лет и я тоже видел очень много. Компиляторы ухудшаются с каждым годом.
old_timer
5

Я начал писать комментарий, а потом решил, что это слишком долго и слишком точно.

Я бы сказал, что это ваш код не работает. В маловероятном случае, если вы обнаружили ошибку в компиляторе - вы должны сообщить об этом разработчикам компилятора, но на этом разница заканчивается.

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

littleadv
источник
5
  1. Перечитай свой код внимательно. Убедитесь, что вы не делаете вещи с побочными эффектами в ASSERT или другими отладочными (или более общими, конфигурационными) инструкциями. Также помните, что в отладочной сборке память инициализируется иначе - значения указателя указателя вы можете проверить здесь: Отладка - Представления распределения памяти . При запуске из Visual Studio вы почти всегда используете кучу отладки (даже в режиме выпуска), если вы явно не укажете в переменной среды, что это не то, что вам нужно.
  2. Проверьте свою сборку. Проблемы со сложными сборками часто возникают в других местах, кроме реального компилятора - часто виновниками являются зависимости. Я знаю, что «вы пытались полностью восстановить» почти так же бесит ответ, как «пытались ли вы переустановить Windows», но это часто помогает. Попробуйте: а) перезагрузка. б) Удаление всех ваших промежуточных и выходных файлов ВРУЧНУЮ и восстановление.
  3. Просмотрите свой код, чтобы найти возможные места, где вы можете вызывать неопределенное поведение. Если вы некоторое время работали в C ++, вы знаете, что есть места, где вы думаете: «Я НЕ ПОЛНОСТЬЮ уверен, что мне позволено это предположить ...» - Google или спросите здесь об этом конкретном тип кода, чтобы увидеть, является ли оно неопределенным поведением или нет.
  4. Если это все еще не так, сгенерируйте предварительно обработанный вывод для файла, который вызывает проблемы. Неожиданное расширение макроса может вызвать все виды удовольствия (мне вспоминается время, когда коллега решил, что макрос по имени H будет хорошей идеей ...). Изучите предварительно обработанные выходные данные для неожиданных изменений между конфигурациями вашего проекта.
  5. В крайнем случае - теперь вы действительно находитесь на земле ошибок компилятора - посмотрите на результаты сборки. Это может занять некоторое копание и борьбу только для того, чтобы понять, что на самом деле делает сборка, но на самом деле это довольно информативно. Вы можете использовать приобретенные здесь навыки для оценки микрооптимизаций, так что еще не все потеряно.
Йорис Тиммерманс
источник
+1 за "неопределенное поведение". Я был укушен этим. Написал некоторый код, который зависел от int + intпереполнения точно так, как если бы он был скомпилирован в аппаратную инструкцию ADD. Он прекрасно работал при компиляции со старой версией GCC, но не при компиляции с новым компилятором. По-видимому, хорошие люди из GCC решили, что, поскольку результат целочисленного переполнения не определен, их оптимизатор может работать при условии, что этого никогда не произойдет. Это оптимизировало важную ветку прямо из кода.
Соломон Медленный
2

Если вы хотите знать, является ли это ваш код или компилятор, вы должны прекрасно знать спецификацию C ++.

Если сомнения сохраняются, вы должны отлично знать сборку x86.

Если у вас нет настроения учиться обоим до совершенства, то это почти наверняка неопределенное поведение, которое ваш компилятор решает по-разному в зависимости от уровня оптимизации.

mouviciel
источник
(+1) @mouviciel: Это также зависит от того, поддерживается ли эта функция компилятором, даже если она есть в спецификации. У меня странная ошибка с GCC. Я объявляю «простую структуру c» с «указателем на функцию», это разрешено в спецификации, но работает в некоторых ситуациях и не работает в других.
umlcat
1

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

У меня нет предложений о том, как узнать, если это вы или компилятор. Вы можете попробовать другой компилятор.

Однажды мне стало интересно, был ли это мой код или нет, и кто-то предложил мне valgrind. Я потратил 5 или 10 минут, чтобы запустить свою программу с ним (я думаю,valgrind --leak-check=yes myprog arg1 arg2 сделал это, но я играл с другими вариантами), и она сразу показала мне ОДНУ линию, которая проходила под одним конкретным случаем, который был проблемой. Тогда мое приложение работало без ошибок, без каких-либо странных сбоев, ошибок или странного поведения. valgrind или другой подобный инструмент - это хороший способ узнать, является ли он вашим кодом.

Примечание: однажды я удивился, почему производительность моего приложения отстой. Оказалось, что все мои проблемы с производительностью были в одной строке. Я написал for(int i=0; i<strlen(sz); ++i) {. СЗ было несколько мб. По какой-то причине компилятор запускается каждый раз, даже после оптимизации. Одна строка может иметь большое значение. От выступлений до сбоев


источник
1

Все более распространенная ситуация заключается в том, что компиляторы нарушают код, написанный для диалектов C, который поддерживает поведение, не предписанное Стандартом, и позволяет кодам, нацеленным на эти диалекты, быть более эффективными, чем может быть строго согласованный код. В таком случае было бы несправедливо описывать как «сломанный» код, который был бы на 100% надежным для компиляторов, которые реализовали целевой диалект, или описывать как «сломанный» компилятор, который обрабатывает диалект, который не поддерживает требуемую семантику. , Вместо этого проблемы проистекают просто из того факта, что язык, обрабатываемый современными компиляторами с включенной оптимизацией, отличается от диалектов, которые раньше были популярны (и до сих пор обрабатываются многими компиляторами с отключенными оптимизациями или некоторыми даже с включенными оптимизациями).

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

Supercat
источник
Ну, конечно, нет ничего плохого в кодировании стандартных X + расширений Y и Z, если это дает вам значительные преимущества, вы знаете, что сделали это, и тщательно документировали это. К сожалению, все три условия обычно не выполняются , и поэтому справедливо сказать, что код нарушен.
Дедупликатор
@Deduplicator: Компиляторы C89 были продвинуты как совместимые с предшествующими версиями, а также для C99 и т. Д. Хотя C89 не предъявляет никаких требований к поведению, которое ранее было определено на некоторых платформах, но не на других, восходящая совместимость предполагает, что компиляторы C89 для платформы, которые рассматривали поведение как определенное, должны продолжать делать это; Логическое обоснование для продвижения коротких беззнаковых типов в знаки должно указывать на то, что авторы Стандарта ожидали, что компиляторы будут вести себя таким образом независимо от того, предписал ли это Стандарт. Далее ...
суперкат
... строгое толкование правил псевдонимов выкинуло бы совместимость вверх и сделало бы многие виды кода неработоспособными, но несколько небольших изменений (например, выявление некоторых шаблонов, в которых следует ожидать перекрестного псевдонима и, следовательно, допустимого), решило бы обе проблемы , Вся заявленная цель правила состояла в том, чтобы не требовать от компиляторов делать «пессимистичные» предположения о псевдонимах, но с учетом «float x», если предположить, что «foo ((int *) & x)» может изменить x, даже если «foo» не Не пишите ли какие-либо указатели типа «float *» или «char *», считающиеся «пессимистичными» или «очевидными»?
суперкат
0

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

Я, вероятно, не был бы таким дотошным. Скорее, я бы попросил форум поддержки / список рассылки производителя компилятора. Если это действительно ошибка в компиляторе, то они могут это исправить. Вероятно, это был бы мой код в любом случае. Например, языковые спецификации, касающиеся видимости памяти при многопоточности, могут быть довольно противоречивыми, и они могут стать очевидными только при использовании определенных флагов оптимизации на некотором конкретном оборудовании (!). Некоторое поведение может быть не определено спецификацией, поэтому оно может работать с некоторым компилятором / некоторыми флагами, а не работать с некоторыми другими и т. Д.

Joonas Pulakka
источник
0

Скорее всего, ваш код имеет неопределенное поведение (как объяснили другие, у вас гораздо больше шансов иметь ошибки в коде, чем в компиляторе, даже если компиляторы C ++ настолько сложны, что у них есть ошибки; даже в спецификации C ++ есть ошибки проектирования) , И UB может быть здесь, даже если скомпилированный исполняемый файл работает (к счастью).

Так что вам следует прочитать блог Латтнера « Что должен знать каждый программист на C» о неопределенном поведении (в основном это относится и к C ++ 11).

Инструмент valgrind и последние -fsanitize= опции инструментовки для GCC (или Clang / LLVM ) также должны быть полезны. И, конечно же, включите все предупреждения:g++ -Wall -Wextra

Василий Старынкевич
источник