Могут ли разные уровни оптимизации привести к функционально различному коду? - PullRequest
44 голосов
/ 16 июня 2011

Мне любопытно, какие свободы имеет компилятор при оптимизации.Давайте ограничим этот вопрос GCC и C / C ++ (любая версия, любой вариант стандарта):

Можно ли написать код, который ведет себя по-разному, в зависимости от того, с каким уровнем оптимизации он был скомпилирован?

Пример, который я имею в виду, - это печать разных фрагментов текста в различных конструкторах в C ++ и получение разницы в зависимости от того, выбраны ли копии (хотя я не смог заставить такую ​​вещь работать).

Подсчет тактов не допускается.Если у вас есть пример для компилятора без GCC, мне тоже было бы любопытно, но я не могу его проверить.Бонусные баллы за пример в C.: -)

Редактировать: Код примера должен соответствовать стандарту и не содержать неопределенного поведения с самого начала.

Редактировать 2: Получил несколько отличных ответов!Позвольте мне немного поднять ставку: код должен составлять правильно сформированную программу и соответствовать стандартам, а также должен компилироваться для корректных, детерминированных программ на каждом уровне оптимизации.(Это исключает такие вещи, как условия гонки в плохо сформированном многопоточном коде.) Также я ценю, что это может повлиять на округление с плавающей запятой, но давайте не будем об этом говорить.Репутация щедрости на первом законченном примере, соответствующем (духу) этих условий;25, если это связано со злоупотреблением строгим псевдонимом.(При условии, что кто-то показывает мне, как отправить вознаграждение кому-то другому.)

Ответы [ 13 ]

19 голосов
/ 16 июня 2011

Часть применяемого стандарта C ++ - §1.9 «Выполнение программы».Он читает, частично:

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

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

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

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

РЕДАКТИРОВАТЬ: Позвольте мне исправить мой вывод: Да, код может вести себя по-разному на разных уровнях оптимизации, если каждое поведение заметно идентично одному из поведений абстрактной машины стандарта.

15 голосов
/ 16 июня 2011

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

Только если вы вызываете ошибку компилятора.

EDIT

Этот пример ведет себя по-другому на gcc 4.5.2:

void foo(int i) {
  foo(i+1);
}

main() {
  foo(0);
}

Скомпилировано с -O0 создает сбой программы с ошибкой сегментации.
Скомпилированный с -O2 создает программу, входящую в бесконечный цикл.

13 голосов
/ 16 июня 2011

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

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

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

10 голосов
/ 17 августа 2011

ОК, моя вопиющая игра за награду, приведя конкретный пример. Я соберу биты из ответов других людей и моих комментариев.

В целях различного поведения на разных уровнях оптимизации, «уровень оптимизации A» будет обозначать gcc -O0 (я использую версию 4.3.4, но это не имеет большого значения, я думаю, что любая даже смутно недавняя версия будет покажите разницу, за которой я следую), а «уровень оптимизации B» будет обозначать gcc -O0 -fno-elide-constructors.

Код прост:

#include <iostream>

struct Foo {
    ~Foo() { std::cout << "~Foo\n"; }
};

int main() {
    Foo f = Foo();
}

Вывод на уровне оптимизации A:

~Foo

Вывод на уровне оптимизации B:

~Foo
~Foo

Код полностью допустим, но выходные данные зависят от реализации из-за elision конструктора копирования, и в частности он чувствителен к флагу оптимизации gcc, который отключает копирование ctor elision.

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

8 голосов
/ 16 июня 2011

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

  • неопределенное поведение не должно быть согласуется между разными компиляторами выполнение или выполнение ошибочного кода
  • операции с плавающей запятой могут вызвать разное округление
  • аргументы для вызовов функций могут быть оценивается в любом порядке
  • выражений с volatile квалифицированными тип может или не может быть оценен только за их побочные эффекты
  • идентичные const квалифицированные составные литералы могут или не могут быть свернуты в одну статическую ячейку памяти
4 голосов
/ 16 июня 2011

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

2 голосов
/ 16 июня 2011

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

2 голосов
/ 16 июня 2011

Опция -fstrict-aliasing может легко вызвать изменения в поведении, если у вас есть два указателя на один и тот же блок памяти. Предполагается, что это недействительно, но на самом деле это довольно часто.

1 голос
/ 17 июня 2011

Эта C-программа вызывает неопределенное поведение, но отображает разные результаты на разных уровнях оптимизации:

#include <stdio.h>
/*
$ for i in 0 1 2 3 4 
    do echo -n "$i: " && gcc -O$i x.c && ./a.out 
  done
0: 5
1: 5
2: 5
3: -1
4: -1
*/

void f(int a) {
  int b;
  printf("%d\n", (int)(&a-&b));
}
int main() {
 f(0);
 return 0;
}
0 голосов
/ 21 марта 2019

ac:

char *f1(void) { return "hello"; }

bc:

#include <stdio.h>

char *f1(void);

int main()
{
    if (f1() == "hello") printf("yes\n");
        else printf("no\n");
}

Вывод зависит от того, включена или отключена оптимизация констант строки слияния:

$ gcc acbc -oa -fno-merge-constants;./a
нет
$ gcc ac bc -oa -fmerge-constants;./a
да

...