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

Недавно я обнаружил ошибку в своем коде, на отладку которой у меня ушло несколько часов.

проблема была в функции, определенной как:

unsigned int foo(unsigned int i){
   long int v[]={i-1,i,i+1} ;
       .
       .
       .
 return x ; // evaluated by the function but not essential how for this problem.
}

Определение v не вызвало никаких проблем на моей машине разработки (32-разрядная версия Ubuntu 12.04, компилятор g++), где беззнаковые целые числа были неявно преобразованы в длинные целые числа, и поэтому отрицательные значения обрабатывались правильно.

Однако на другой машине (64-разрядная версия Ubuntu 12.04, компилятор g++) эта операция была небезопасной. Когда i=0, v[0] было установлено не в -1, а в какое-то странное большое значение (как это часто бывает при попытке сделать unsigned int отрицательным).

Я мог бы решить проблему приведения значения i к long int

long int v[]={(long int) i - 1, (long int) i, (long int) i + 1};

и все работало нормально (на обеих машинах).

Я не могу понять, почему первый отлично работает на машине и не работает на другом.

Можете ли вы помочь мне понять это, чтобы я мог избежать этой или других проблем в будущем?


person lucacerone    schedule 18.09.2012    source источник


Ответы (2)


Для значений unsigned сложение/вычитание четко определено как арифметика по модулю, поэтому 0U-1 будет работать как std::numeric_limits<unsigned>::max().

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

Поэтому, когда long является 64-битным (предположительно, на вашей 64-битной машине), беззнаковый подходит и копируется прямо.

Когда long является 32-битным на 32-битной машине, опять же, скорее всего, он просто интерпретирует битовый шаблон как значение со знаком, которое в данном случае равно -1.

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

Тем не менее, g++ (по крайней мере, версия 4.5) предоставляет удобный -Wsign-conversion, который обнаруживает эту проблему в вашем конкретном коде.

person Mark B    schedule 18.09.2012
comment
спасибо за объяснение, хотя я не мог понять ваш ответ очень хорошо. Я не совсем уверен, но я помню, что long int достаточно велики, чтобы содержать unsigned int (по крайней мере, согласно некоторым ссылкам на С++, которые вы можете найти в Интернете). В любом случае, я согласен, что лучше не смешивать их, но в данном случае мне необходимо это сделать. Мне просто было любопытно узнать, когда могут возникнуть эти проблемы, зависящие от машины. - person lucacerone; 19.09.2012

Вы также можете иметь специализированный бросок, ловящий все броски с переполнением:

template<typename O, typename I>
O architecture_cast(I x) {
/* make sure I is an unsigned type. It  */
static_assert(std::is_unsigned<I>::value, "Input value to architecture_cast has to be unsigned");

assert(x <= static_cast<typename std::make_unsigned<O>::type>( std::numeric_limits<O>::max() ));

return static_cast<O>(x);
}

Используя это, в отладке будут пойманы все приведения из большего числа, чем может вместить результирующий тип. Это включает в себя ваш случай, когда unsigned int равен 0 и вычитается на -1, что приводит к наибольшему unsigned int.

person Pavel Celba    schedule 08.04.2016