Як дізнатись, чи компілятор порушив мій код і що робити, якщо це компілятор?


14

Час від часу код C ++ не працюватиме, коли компілюється з деяким рівнем оптимізації. Можливо, компілятор робить оптимізацію, що порушує код, або може бути код, що містить неозначене поведінку, що дозволяє компілятору робити все, що відчуває.

Припустимо, у мене є якийсь фрагмент коду, який ламається, коли компілюється лише з більш високим рівнем оптимізації. Як я можу знати, чи це код чи компілятор і що робити, якщо це компілятор?


43
Швидше за все це ти.
littleadv

9
@littleadv, навіть останні версії gcc та msvc переповнені помилками, тому я не був би таким впевненим.
SK-логіка

3
У вас увімкнено всі попередження?

@ Thorbjørn Ravn Andersen: Так, у мене їх увімкнено.
гострий зуб

3
FWIW: 1) Я намагаюся не робити нічого складного, що може спокусити компілятора зіпсувати, 2) єдине місце, де знаки оптимізації (для швидкості) знаходяться в коді, де лічильник програм витрачає значну частину свого часу. Якщо ви не пишете чіткі циклічні процесори, у багатьох програмах ПК витрачає по суті весь свій час у бібліотеках чи вводу / виводу. У такому додатку перемикачі / виходи зовсім не допомагають.
Майк Данлаве

Відповіді:


19

Я б сказав, що це безпечна ставка, що в переважній більшості випадків порушено саме ваш код, а не компілятор. І навіть у надзвичайному випадку, коли це компілятор, ви, мабуть, використовуєте якусь незрозумілу мовну особливість незвичним чином, до якої конкретний компілятор не підготовлений; іншими словами, ви, швидше за все, можете змінити свій код на більш ідіоматичний і уникнути слабкого місця компілятора.

У будь-якому випадку, якщо ви зможете довести, що ви знайшли помилку компілятора (на основі мовної специфікації), повідомте про це розробникам компілятора, щоб вони могли виправити це деякий час.


@ SK-логіка, досить чесно, у мене немає статистики, яка б це підтримувала. Він заснований на моєму власному досвіді, і я визнаю, що я рідко розтягував межі мови та / або компілятора - інші можуть робити це частіше.
Péter Török

(1) @ SK-Logic: Щойно знайшов помилку компілятора C ++, той самий код, приміряв один компілятор і працює, спробував в іншому він запускається.
umlcat

8
@umlcat: швидше за все, це був ваш код залежно від невказаної поведінки; на одному компіляторі він відповідає вашим очікуванням, на іншому - ні. це не означає, що він зламаний.
Хав'єр

@Ritch Melton, ти коли-небудь використовував LTO?
SK-логіка

1
Я погоджуюся з Crashworks, коли говорити про ігрові консолі. Зовсім не незвично знайти езотеричні помилки компілятора в цій конкретній ситуації. Якщо ви орієнтуєтесь на звичайні ПК, використовуючи компілятор, що використовується широко, то навряд чи ви натрапите на помилку компілятора, яку ніхто раніше не бачив.
Тревор Пауелл

14

Так само, як зазвичай, як і у всіх інших помилок: виконайте контрольований експеримент. Зменшіть підозрілу область, вимкніть оптимізацію для всього іншого та почніть змінювати оптимізації, застосовані до цього фрагменту коду. Як тільки ви отримаєте 100% відтворюваність, починайте змінювати код, вводячи речі, які можуть порушити певну оптимізацію (наприклад, ввести можливе псевдонім вказівника, вставити зовнішні дзвінки з потенційними побічними ефектами тощо). Перегляд коду складання у відладчику також може допомогти.


може допомогти з чим? Якщо це помилка компілятора - то що?
littleadv

2
@littleadv, якщо це помилка компілятора, ви можете спробувати виправити її (або просто повідомити про це належним чином, детально), або ви зможете дізнатися, як цього уникнути в майбутньому, якщо ви приречені продовжувати використовувати це версія свого компілятора на деякий час. Якщо це щось із власним кодом, одне з численних проблем, пов'язаних із C ++, то цей вид перевірки також допомагає виправити помилку та уникнути її виду в майбутньому.
SK-логіка

Тому, як я вже говорив у своїй відповіді, - окрім як звітування, різниці в лікуванні немає, незалежно від того, чия вона вина.
littleadv

3
@littleadv, не розуміючи природи проблеми, ви, швидше за все, стикаєтеся з нею знову і знову. І часто є можливість виправити компілятор самостійно. І так, на жаль, зовсім не "навряд" знайти помилку в компіляторі C ++.
SK-логіка

10

Вивчіть код складання, який привів, і подивіться, чи він виконує те, що вимагає ваш джерело. Пам'ятайте, що шанси дуже великі, що це справді ваш код у вині якимось неочевидним способом.


1
Це справді єдина відповідь на це питання. Завдання компіляторів у цьому випадку полягає в тому, щоб перейти від C ++ до мови асемблера. Ви думаєте, що це компілятор ... перевірте роботу компіляторів. Це так просто.
old_timer

7

За більш ніж 30 років програмування кількість справжніх помилок компілятора (генерування коду), які я знайшов, досі становить лише 10. 10. Напевно, кількість моїх власних (та інших) помилок, які я знайшов та виправив за той самий період, ймовірно, > 10 000. Моє "велике правило" тоді полягає в тому, що ймовірність виникнення будь-якої помилки через компілятора <0,001.


1
Ти щасливчик. Мій середній показник - приблизно 1 дійсно погана помилка на місяць, а незначні прикордонні проблеми - це набагато частіше. І чим вищий рівень оптимізації ви використовуєте, тим вище шанси помилок компілятора. Якщо ви намагаєтесь використовувати -O3 та LTO, вам дуже пощастить не знайти пару за один раз. І я зараховую тут лише помилки у версіях релізів - як розробник компіляторів я стикаюся з набагато більшими проблемами такого роду в своїй роботі, але це не враховується. Я просто знаю, як легко накрутити компілятор.
SK-логіка

2
25 років, і я теж бачив дуже багато. Компілятори з кожним роком погіршуються.
old_timer

5

Я почав писати коментар, а потім вирішив його занадто довго і занадто багато до суті.

Я б заперечував, що ваш код порушений. У тому випадку, коли ви виявили помилку в компіляторі - ви повинні повідомити про це розробникам компілятора, але на цьому різниця закінчується.

Рішення полягає в тому, щоб визначити структуру, що ображає, і рефакторировать її, щоб вона виконувала таку ж логіку інакше. Це, швидше за все, вирішить проблему, чи є помилка на вашому боці чи у компіляторі.


5
  1. Перечитайте свій код уважно. Переконайтеся, що у ASSERT або інших специфічних виправленнях (або більш загальних конфігураційних) операцій не робиться побічних ефектів. Також пам’ятайте, що пам'ять збірки налагодження ініціалізується по-різному - значення покажчика Telltale ви можете перевірити тут: Налагодження - Представлення розподілу пам’яті . Під час запуску зсередини Visual Studio ви майже завжди використовуєте Hebu Debug Heap (навіть у режимі випуску), якщо ви чітко не вказуєте зі змінною середовища, що це не те, що ви хочете.
  2. Перевірте свій збір. Проблеми зі складними складаннями зазвичай зустрічаються в інших місцях, ніж власне компілятор - винуватцем часто є залежності. Я знаю, що "ви спробували повністю відновити" - це настільки ж неприємний відповідь, як "ви спробували перевстановити Windows", але це часто допомагає. Спробуйте: а) Перезавантажити. b) ВИКОРИСТОВУЄТЬСЯ всі проміжні та вихідні файли Вручну та відновлення.
  3. Перегляньте свій код, щоб перевірити, чи немає потенційних місць, де ви можете викликати невизначене поведінку. Якщо ви деякий час працювали в C ++, ви знатимете, що є певні місця, де ви думаєте, що "Я НЕ ВІДПОВІСТЬ впевнений, що мені дозволяють припустити, що ..." - гугл або запитайте тут про те, що саме тип коду, щоб побачити, чи не визначена поведінка чи ні.
  4. Якщо це все ще не так, генеруйте попередньо оброблений вихід для файлу, який викликає проблеми. Несподіване розширення макросу може спричинити всілякі розваги (мені нагадується час, коли колега вирішив макрос на ім'я H було б хорошою ідеєю ...). Вивчіть попередньо оброблений вихід на предмет несподіваних змін між конфігураціями вашого проекту.
  5. В крайньому випадку - тепер ви справді перебуваєте в помилках компілятора - подивіться на висновок збірки. Це може зайняти деяке копання та боротьбу лише для того, щоб зрозуміти, що насправді робить збірка, але насправді це досить інформативно. Ви можете скористатися навичками, які ви отримаєте тут, і для оцінки мікрооптимізації, щоб все не було втрачено.

+1 для "невизначеної поведінки". Мене вкусив той. Написав якийсь код, від якого залежав int + intпереповнення, точно так, як якщо б він був складений на інструкцію щодо апаратного додавання. Він спрацював чудово, коли компілювався зі старшою версією GCC, але не тоді, коли компілювався з новішим компілятором. Мабуть, приємні люди в GCC вирішили, що оскільки результат цілого переповнення не визначений, їх оптимізатор може працювати за умови, що цього ніколи не станеться. Це оптимізувало важливу гілку прямо з коду.
Соломон повільно

2

Якщо ви хочете знати, чи це ваш код чи компілятор, ви повинні досконало знати специфікацію C ++.

Якщо сумніви зберігаються, ви повинні досконало знати збірку x86.

Якщо ви не в настрої вчитися обом до досконалості, то майже точно не визначена поведінка, що ваш компілятор вирішує по-різному залежно від рівня оптимізації.


(+1) @mouviciel: Це також залежить, якщо функція підтримується компілятором, навіть якщо вона є в специфікації. У мене дивна помилка з gcc. Я оголошую "просту структуру c" з "покажчиком функції", її дозволено в специфікації, але працює в деяких ситуаціях, а не в іншій.
umlcat

1

Отримати помилку компіляції у стандартному коді чи внутрішню помилку компіляції швидше, ніж оптимізатори помиляються. Але я чув про компілятори, що оптимізують цикли, неправильно забуваючи про деякі побічні ефекти.

Я не маю пропозицій, як знати, чи це ти, чи компілятор. Ви можете спробувати інший компілятор.

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

Побічна примітка: Я колись замислювався, чому ефективність мого додатка смоктала Виявилося, всі мої проблеми з працездатністю були і в одному рядку. Я писав for(int i=0; i<strlen(sz); ++i) {. Sz було кілька mb. Чомусь компілятор виконував стринги щоразу навіть після оптимізації. Один рядок може бути великою справою. Від виступів до збоїв


1

Все більш поширеною ситуацією є те, що компілятори порушують код, написаний для діалектів С, які підтримують поведінку, не встановлену Стандартом, і дозволяють коду, орієнтованому на ці діалекти, більш ефективним, ніж це може бути строго відповідний код. У такому випадку було б несправедливо описати як "зламаний" код, який би був на 100% надійним для компіляторів, які реалізували цільовий діалект, або описати як "зламаний" компілятор, який обробляє діалект, який не підтримує необхідну семантику. . Натомість проблеми випливають просто з того, що мова, оброблена сучасними компіляторами з увімкненими оптимізаціями, відрізняється від діалектів, які раніше були популярними (і досі обробляються багатьма компіляторами з вимкнутими оптимізаціями, або деякими навіть із включеною оптимізацією).

Наприклад, багато коду написано для діалектів, які визнають легітимним ряд в'яжучих шаблонів покажчиків, не мандатированних інтерпретацією gcc стандарту, і використовує такі шаблони, що дозволяє прямому перекладу коду бути більш зрозумілим і ефективним ніж це було б можливо в інтерпретації стандарту С gcc. Такий код може бути не сумісний з gcc, але це не означає, що він порушений. Він просто покладається на розширення, які gcc підтримує лише з відключеною оптимізацією.


Ну, впевнений, що в кодуванні стандартних C + X + розширень Y і Z немає нічого поганого, якщо це дає значні переваги, ви знаєте, що ви це зробили, і ви ретельно це задокументували. На жаль, всі три умови зазвичай не виконуються , і тому справедливо можна сказати, що код порушений.
Дедуплікатор

@Deduplicator: Компілятори C89 рекламувались як сумісні з вище своїми попередниками, так само як і для C99 тощо. Хоча C89 не пред'являє жодних вимог до поведінки, які раніше були визначені на деяких платформах, але не в інших, сумісність вгору дозволила б компіляторам C89 для платформи, які розглядали поведінку як визначену, повинні продовжувати це робити; обґрунтування просування коротких неподписаних типів до знаків може підказати, що автори Стандарту очікували, що компілятори поводяться саме так, незалежно від того, чи вимагає це Стандарт. Далі ...
supercat

... сувора інтерпретація правил псевдоніму викинула б сумісність у бік вікна і зробила багато видів коду непрацездатним, але кілька незначних змін (наприклад, ідентифікація деяких моделей, де слід очікувати крос-тип псевдоніму і, отже, допустимих), вирішили б обидві проблеми. . Загальновизначена мета цього правила полягала в тому, щоб не вимагати від компіляторів робити "песимістичних" псевдоніматичних припущень, але з урахуванням "float x", якщо буде припущення, що "foo ((int *) & x)" може змінювати x, навіть якщо "foo" не не писати в будь-які покажчики типу "float *" або "char *" вважати "песимістичним" чи "очевидним"?
supercat

0

Виділіть проблемне місце та порівняйте спостережувану поведінку з тим, що має відбутися відповідно до мовної специфікації. Однозначно не просто, але це те, що ти повинен знати (а не просто припускати ).

Я, мабуть, не був би таким прискіпливим. Швидше, я б попросив форуму підтримки розробника / списку розсилки від виробника компілятора. Якщо це справді помилка в компіляторі, то вони можуть її виправити. Напевно, це все одно мій код. Наприклад, мовні характеристики щодо видимості пам’яті в нанизуванні можуть бути досить протизаконними, і вони можуть стати очевидними лише при використанні певних прапорців оптимізації на певному апаратному забезпеченні (!). Певна поведінка може бути невизначена специфікацією, тому вона може працювати з деяким компілятором / деякими прапорами, а не працювати з деякими іншими тощо.


0

Швидше за все, ваш код має певну не визначену поведінку (як пояснили інші, у вас є набагато більша ймовірність помилок у вашому коді, ніж у компіляторі, навіть якщо компілятори C ++ настільки складні, що в них є помилки; навіть специфікація C ++ має помилки в дизайні) . І UB може бути тут, навіть якщо компільований виконуваний файл станеться непоганим.

Тож вам слід прочитати блог Леттнера Що повинен знати кожен програміст C про невизначене поведінку (більшість із них застосовується і для C ++ 11).

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.