оптимизация gcc? ошибка? и его практическое значение для проекта - PullRequest
11 голосов
/ 23 мая 2010

Мои вопросы делятся на три части

Вопрос 1
Рассмотрим приведенный ниже код,

#include <iostream>
using namespace std;

int main( int argc, char *argv[])
{

    const int v = 50;
    int i = 0X7FFFFFFF;

    cout<<(i + v)<<endl;

    if ( i + v < i )
    {
        cout<<"Number is negative"<<endl;
    }
    else
    {
        cout<<"Number is positive"<<endl;
    }

    return 0;
}

Никаких конкретных опций оптимизации компилятора или флага O не используется. Это базовая команда компиляции g ++ -o test main.cpp используется для формирования исполняемого файла.

Казалось бы, очень простой код имеет странное поведение в 64-битной ОС SUSE, версия gcc 4.1.2. Ожидаемый результат - «Число отрицательно», вместо этого только в 64-битной ОС SUSE вывод будет «Число положительно».

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

  • Поскольку i одинаково с обеих сторон сравнения, его нельзя изменить в одном выражении, уберите «i» из уравнения.
  • Теперь сравнение приводит к if ( v < 0 ), где v - постоянная положительная величина, поэтому во время самой компиляции адрес функции функции остальной части cout добавляется в регистр. Инструкции cmp / jmp не найдены.

Я вижу, что это поведение только в gcc 4.1.2 SUSE 10. При попытке в AIX 5.1 / 5.3 и HP IA64 результат будет таким, как ожидалось.

Является ли приведенная выше оптимизация действительной?
Или использование механизма переполнения для int не является допустимым вариантом использования?

Вопрос 2
Теперь, когда я изменяю условный оператор с if (i + v < i) на if ( (i + v) < i ), даже тогда поведение такое же, по крайней мере, лично я с этим не согласен, так как предоставляются дополнительные скобки, я ожидаю, что компилятор создаст временную переменную встроенного типа и их сравнивают, что сводит на нет оптимизацию.

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

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

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


PS:

  • Когда const для v удаляется из кода, оптимизация не выполняется компилятором.
  • Полагаю, совершенно нормально использовать механизм переполнения, чтобы определить, находится ли переменная от значения MAX - 50 (в моем случае).

Обновление (1)
Чего бы я хотел достичь? Переменная я был бы счетчиком (вид syncID). Если я выполняю автономную работу (50 операций), то во время запуска я хотел бы сбросить счетчик. Для этого я проверяю граничное значение (для его сброса), а не добавляю его вслепую.

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

В любом случае, все спасибо за ваш вклад.


Обновление (2)
В большинстве статей указано, что я полагаюсь на поведение нижнего уровня при проверке переполнения. У меня есть один вопрос относительно того же,

  • Если это так, для неподписанного типа int, как проверить и сбросить значение во время переполнения или переполнения? например, если v = 10, i = 0X7FFFFFFE, я хочу сбросить i = 9. Точно так же для недостаточного заполнения?

Я бы не смог этого сделать, если не проверю отрицательность числа. Поэтому я утверждаю, что int должен возвращать отрицательное число, когда значение добавляется в + MAX_INT.

Пожалуйста, дайте мне знать ваши входные данные.

Ответы [ 5 ]

11 голосов
/ 23 мая 2010

Это известная проблема, и я не думаю, что это считается ошибкой в ​​компиляторе. Когда я компилирую с gcc 4.5 с -Wall -O2, он предупреждает

предупреждение: предполагается, что переполнение со знаком не происходит, если предположить, что (X + c)

Хотя ваш код переполняет .

Вы можете передать флаг -fno-strict-overflow, чтобы отключить эту конкретную оптимизацию.

3 голосов
/ 23 мая 2010

Ваш код вызывает неопределенное поведение. Языки C и C ++ не имеют «механизма переполнения» для целочисленной арифметики со знаком. Ваши вычисления переполняют целые числа со знаком - поведение сразу не определено. Учитывая, что это «позиция в компиляторе или нет», ничем не отличается от попытки проанализировать i = i++ + ++i примеров.

Компилятор GCC имеет оптимизацию, основанную на этой части спецификации языков C / C ++. Это называется "строгая семантика переполнения" или что-то вроде того. Он основан на том факте, что добавление положительного значения к целому числу со знаком в C ++ всегда приводит к большему значению или приводит к неопределенному поведению. Это сразу означает, что компилятор может свободно предполагать, что сумма всегда больше. Общая природа этой оптимизации очень похожа на оптимизацию со "строгим псевдонимом", также присутствующую в GCC. Они оба привели к некоторым жалобам от более «хакерских» частей сообщества пользователей GCC, многие из которых даже не подозревали, что уловки, на которые они опирались в своих программах на C / C ++, были просто незаконными хакерами.

2 голосов
/ 23 мая 2010

Q1: Возможно, число действительно положительно в 64-битной реализации?Кто знает?Перед отладкой кода я просто напечатал бы printf ("% d", i + v);

Q2: круглые скобки предназначены только для указания компилятору, как анализировать выражение.Обычно это делается в виде дерева, поэтому оптимизатор вообще не видит скобок.И это можно свободно преобразовать выражение.

Q3: Вот почему, как программист C / C ++, вы не должны писать код, который принимает определенные свойства базового оборудования, такие как, например, int32-битное количество в форме дополнения до двух.

0 голосов
/ 04 марта 2016

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

  1. ОП, заданный с использованием gcc 4.1.2, без каких-либо специальных флагов.Я предполагаю, что отсутствие флага -O эквивалентно -O0.Без запроса на оптимизацию, почему gcc оптимизировал удаленный код указанным способом?Это кажется мне ошибкой компилятора.Я также предполагаю, что это было исправлено в более поздних версиях (например, один ответ упоминает gcc 4.5 и флаг оптимизации -fno-strict-overflow).На текущей странице руководства gcc указано, что -fstrict-overflow включено с -O2 или более.

  2. В текущих версиях gcc есть опция -fwrapv, которая позволяет использоватькод, который вызвал проблемы для ОП.Конечно, при условии, что вы знаете размеры битов целочисленных типов.Со страницы руководства gcc:

-fstrict-overflow 
.....
See also the -fwrapv option. Using -fwrapv means that integer signed overflow 
is fully defined: it wraps. ... With -fwrapv certain types of overflow are 
permitted. For example, if the compiler gets an overflow when doing arithmetic 
on constants, the overflowed value can still be used with -fwrapv, but not otherwise. 
0 голосов
/ 23 мая 2010

Что означает строка:

cout<<(i + v)<<endl;

Вывод в примере SUSE? Вы уверены, что у вас нет 64-битных целых?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...