Не понимайте предупреждение "предполагать подписанное переполнение"
Я получаю:
предупреждение: при условии, что подписанное переполнение не происходит, если предположить, что (X + c) < X всегда false [-Wstrict-overflow]
в этой строке:
if ( this->m_PositionIndex[in] < this->m_EndIndex[in] )
m_PositionIndex
и m_EndIndex
типа itk::Index
(http://www.itk.org/Doxygen/html/classitk_1_1Index.html), а их operator[]
возвращает signed long
.
(здесь строка 37: https://github.com/Kitware/ITK/blob/master/Modules/Core/Common/include/itkImageRegionConstIteratorWithIndex.hxx для контекста)
Может кто-нибудь объяснить, что может вызвать это предупреждение здесь? Я не вижу шаблон (x+c) < x
в любом месте, так как это просто сравнение signed long
.
Я попытался воспроизвести его в отдельном примере:
#include <iostream>
namespace itk
{
struct Index
{
signed long data[2];
Index()
{
data[0] = 0;
data[1] = 0;
}
signed long& operator[](unsigned int i)
{
return data[i];
}
};
}
int main (int argc, char *argv[])
{
itk::Index positionIndex;
itk::Index endIndex;
for(unsigned int i = 0; i < 2; i++)
{
positionIndex[i]++;
if ( positionIndex[i] < endIndex[i] )
{
std::cout << "something" << std::endl;
}
}
return 0;
}
но я не получаю предупреждение. Любые мысли о том, что отличает мое демо и реальный код, или что может вызвать предупреждение в реальном коде? Я получаю предупреждение с gcc 4.7.0 и 4.7.2 с флагом -Wall.
Ответы
Ответ 1
Чтобы просто отключить это предупреждение, используйте -Wno-strict-overflow
. Чтобы вместо этого отключить определенную оптимизацию, которая вызывает это предупреждение, используйте -fno-strict-overflow
или -fwrapv
.
gcc manpage описывает, что это предупреждение можно контролировать с помощью уровней: -Wstrict-overflow=n
.
Если это остановит вашу сборку из-за -Werror
, вы можете обойтись без скрытия предупреждений, используя -Wno-error=strict-overflow
(или просто -Wno-error
, чтобы переопределить -Werror
).
Анализ и комментарий...
Я получил то же предупреждение и потратил пару часов, пытаясь воспроизвести его в меньшем примере, но так и не удалось. Мой реальный код включал вызов встроенной функции в шаблонном классе, но алгоритм упрощается до следующего...
int X = some_unpredictable_value_well_within_the_range_of_int();
for ( int c=0; c<4; c++ ) assert( X+c >= X ); ## true unless (X+c) overflows
В моем случае предупреждение каким-то образом коррелировало с оптимизатором, разворачивающим цикл for
, поэтому я смог обходиться, объявив volatile int c=0
. Другое дело, что это было объявить unsigned int c=0
, но я не совсем уверен, почему это имеет значение. Еще одна вещь, которая фиксировала это, заключалась в том, что количество циклов достаточно велико, чтобы цикл не разворачивался, но это не полезное решение.
Так что же такое предупреждение действительно говорит? Либо он говорит, что оптимизатор изменил семантику вашего алгоритма (не допуская переполнения), либо просто информирует вас о том, что оптимизатор предполагает, что ваш код не имеет поведения undefined переполнения целое число со знаком. Если переполненные целые числа не являются частью предполагаемого поведения вашей программы, это сообщение, вероятно, не указывает на проблему в вашем коде, поэтому вы, вероятно, захотите отключить его для обычных сборок. Если вы получите это предупреждение, но не уверены в этом коде, может быть безопаснее просто отключить оптимизацию с помощью -fwrapv
.
Кстати, я столкнулся с этой проблемой в GCC 4.7, но тот же код скомпилирован без предупреждения с использованием 4.8 - возможно, указывая на то, что разработчики GCC признали необходимость быть немного менее "строгим" в обычном случае (или возможно, это было просто из-за различий в оптимизаторе).
Философия...
В [С++ 11: N3242 $5.0.4] он заявляет...
Если во время оценки выражения результат не математически определены или нет в диапазоне представимых значений для его тип, поведение undefined.
Это означает, что соответствующий компилятор может просто предположить, что переполнение никогда не происходит (даже для неподписанных типов).
В С++ из-за таких функций, как шаблоны и конструкторы копирования, elision бессмысленных операций является важной возможностью оптимизатора. Иногда, хотя, если вы используете С++ как низкоуровневый "системный язык", вы, вероятно, просто хотите, чтобы компилятор выполнял то, что вы ему рассказывали, и полагался на поведение базового оборудования. Учитывая язык стандарта, я не уверен, как добиться этого независимым от компилятора способом.
Ответ 2
Компилятор сообщает вам, что он обладает достаточным статическим знанием этого фрагмента, чтобы знать, что тест всегда будет успешным, если он сможет оптимизировать тест, предполагая, что никакая подписанная операция не переполнится.
Другими словами, единственный способ x + 1 < x
когда-либо вернуть true, когда x
подписан, если x
уже является максимальным знаковым значением. [-Wstrict-overflow]
пусть компилятор предупреждает, когда он может предположить, что никакое подписанное дополнение не переполнится; это в основном говорит вам, что он собирается оптимизировать тест, потому что подписанное переполнение - это поведение undefined.
Если вы хотите подавить предупреждение, избавитесь от теста.
Ответ 3
Я думаю, что компилятор изменил
positionIndex[i]++;
if ( positionIndex[i] < endIndex[i] )
в нечто более оптимизированное, например
if ( positionIndex[i]++ < endIndex[i] )
так
if ( positionIndex[i] + 1 < endIndex[i] )
который вызывает поведение undefined в случае переполнения (спасибо Seth).
Ответ 4
В моем случае это было хорошим предупреждением от компилятора. У меня была глупа ошибка копирования/вставки, где у меня был цикл внутри цикла, и каждый из них был одним и тем же набором условий. Что-то вроде этого:
for (index = 0; index < someLoopLimit; index++)
{
// blah, blah, blah.....
for (index = 0; index < someLoopLimit; index++)
{
// More blah, blah, blah....
}
}
Это предупреждение избавило меня от времени отладки. Спасибо, gcc!!!
Ответ 5
Это почти наверняка ошибка, хотя я не могу сказать, какая из трех возможных ошибок она есть.
Я считаю, что gcc каким-то образом выясняет, что вычисляются значения в этом 'this- > m_PositionIndex [in]' и т.д., а именно, что оба значения производятся от одного и того же значения, одно со смещением, которое также может доказать быть позитивным. Следовательно, это означает, что одна ветка конструкции if/else является недостижимым кодом.
Это плохо. Определенно. Зачем? Ну, есть ровно три возможности:
-
gcc прав в том, что код недостижим, и вы упускаете из виду, что это так. В этом случае у вас просто раздутый код. Лучшая из трех возможностей, но все еще плохая с точки зрения качества кода.
-
gcc прав, поскольку код недоступен, но вы хотели, чтобы он был доступен. В этом случае вам нужно сделать второй взгляд на то, почему он недоступен, и исправить ошибку в вашей логике.
-
gcc ошибочен, вы наткнулись на ошибку. Довольно маловероятно, но возможно, и, к сожалению, очень трудно доказать.
Действительно плохо, что вам абсолютно необходимо либо точно выяснить, почему код недостижим, либо вам нужно опровергнуть, что gcc прав (что проще сказать, чем сделать). Согласно закону Мерфи, предполагая случай 1 или 3., не доказывая, что он удостоверится, что это был случай 2., и что вам придется снова искать ошибку...