Gcc-оптимизация? ошибка? и его практическое значение для проекта

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

Вопрос 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, выход будет "Число положительное".

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

  • Так как я одинаково по обе стороны сравнения, его нельзя изменить в том же выражении, удалите 'i' из уравнения.
  • Теперь сравнение приводит к if ( v < 0 ), где v является константой положительной. Поэтому во время самой компиляции в регистр добавляется адрес функции cout part else. Инструкции 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 (в моем случае).

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

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

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


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

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

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

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

Ответы

Ответ 1

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

предупреждение: при условии, что подписанное переполнение не происходит, если предположить, что (X + c) < X всегда false

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

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

Ответ 2

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

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

Ответ 3

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

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

Q3: Поэтому, как программист c/С++, вы не должны писать код, который предполагает определенные свойства базового оборудования, например, например, что int представляет собой 32-разрядное количество в двух дополнительных формах.

Ответ 4

Что делает строка:

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

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

Ответ 5

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

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

  • В текущих версиях gcc существует опция -fwrapv, которая позволяет использовать код, вызывающий проблемы для OP. Разумеется, вы убедитесь, что знаете размеры битов ваших целых типов. На странице gcc man:

-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.