Когда тривиальный (код, который не имеет никакого эффекта) код будет удален в процессе компиляции?
volatile int num = 0;
num = num + 10;
Вышеупомянутый код C++, кажется, производит следующий код в сборке Intel:
mov DWORD PTR [rbp-4], 0
mov eax, DWORD PTR [rbp-4]
add eax, 10
mov DWORD PTR [rbp-4], eax
Если я изменю код C++ на
volatile int num = 0;
num = num + 0;
почему компилятор не выдаст ассемблерный код как:
mov DWORD PTR [rbp-4], 0
mov eax, DWORD PTR [rbp-4]
add eax, 0
mov DWORD PTR [rbp-4], eax
gcc7.2 -O0
оставляет add eax, 0
, но все остальные инструкции одинаковы (Godbolt).
В какой части процесса компиляции этот вид тривиального кода удаляется. Есть ли какой-либо флаг компилятора, который заставит компилятор GCC не выполнять такого рода оптимизации.
2 ответа
Лязг будет излучать add eax, 0
в -O0
, но ни gcc, ни ICC, ни MSVC не будут. Увидеть ниже.
gcc -O0
не означает "без оптимизации". У gcc нет режима "braindead literal translation", где он пытается транслитерировать каждый компонент каждого выражения C непосредственно в инструкцию asm.
ССЗ -O0
не предназначен быть полностью неоптимизированным. Он предназначен для "быстрой компиляции" и заставляет отладку давать ожидаемые результаты (даже если вы изменяете переменные C с помощью отладчика или переходите на другую строку в функции). Таким образом, он разливает / перезагружает все вокруг каждого оператора C, предполагая, что память может быть асинхронно изменена отладчиком, остановленным перед таким блоком. (Интересный пример последствий и более подробное объяснение: почему целочисленное деление на -1 (отрицательное) приводит к FPE?)
Там не так много спроса на gcc -O0
сделать еще более медленный код (например, забыв, что 0
аддитивная идентичность), поэтому никто не реализовал опцию для этого. И это может даже сделать gcc медленнее, если это поведение будет необязательным. (Или, может быть, есть такая опция, но она включена по умолчанию даже в -O0
потому что это быстро, не мешает отладке и полезно. Обычно людям нравится, когда их отладочные сборки выполняются достаточно быстро, чтобы их можно было использовать, особенно для больших проектов или проектов в реальном времени.)
Как объясняет @Basile Starynkevitch в " Отключить все параметры оптимизации в GCC", gcc всегда трансформируется через свои внутренние представления на пути к созданию исполняемого файла. Просто выполнение всего этого приводит к некоторым видам оптимизации.
Например, даже в-O0
Алгоритм деления на константу в gcc использует мультипликативный обратный с фиксированной запятой или сдвиг (для степеней 2) вместо idiv
инструкция. Но clang -O0
буду использовать idiv
за x /= 2
,
Clang-х -O0
в этом случае тоже оптимизирует меньше, чем gcc:
void foo(void) {
volatile int num = 0;
num = num + 0;
}
вывод asm на Godbolt для x86-64
push rbp
mov rbp, rsp
# your asm block from the question, but with 0 instead of 10
mov dword ptr [rbp - 4], 0
mov eax, dword ptr [rbp - 4]
add eax, 0
mov dword ptr [rbp - 4], eax
pop rbp
ret
Как вы говорите, GCC оставляет бесполезным add eax,0
, ICC17 хранит / перезагружает несколько раз. MSVC обычно чрезвычайно буквален в режиме отладки, но даже он избегает излучения add eax,0
,
Clang также является единственным из 4 x86-компиляторов на Godbolt, который будет использовать idiv
за return x/2;
, Остальные все SAR + CMOV или что-то еще, чтобы реализовать подписанную семантику деления Си.
Согласно правилу "как будто" в C++, реализации разрешается свободно делать все, что она хочет, при условии, что наблюдаемое поведение соответствует стандарту. В частности, в C++17, 4.6/1
(в качестве одного примера):
... соответствующие реализации требуются для эмуляции (только) наблюдаемого поведения абстрактной машины, как объяснено ниже.
Это положение иногда называют правилом "как если бы", потому что реализация может свободно игнорировать любое требование настоящего международного стандарта, если в результате получается, что требование было выполнено, насколько это можно определить из наблюдаемого поведения. программы.
Например, фактическая реализация не должна оценивать часть выражения, если она может сделать вывод, что ее значение не используется и что не возникает никаких побочных эффектов, влияющих на наблюдаемое поведение программы.
Что касается того, как контролировать gcc
Моим первым предложением было бы отключить всю оптимизацию с помощью -O0
флаг. Вы можете получить более точную настройку, используя различные -f<blah>
варианты но -O0
должно быть хорошее начало