Преобразование Signed в unsigned на C - всегда ли это безопасно?

StackOverflow https://stackoverflow.com/questions/50605

  •  09-06-2019
  •  | 
  •  

Вопрос

Предположим, у меня есть следующий код на языке Си.

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Какие неявные преобразования здесь происходят, и безопасен ли этот код для всех значений u и i?(Безопасно, в том смысле, что даже несмотря на Результат в этом примере переполнится до некоторого огромного положительного числа, я мог бы привести его обратно к инт и получите реальный результат.)

Это было полезно?

Решение

Краткий Ответ

Ваш i будет преобразованный к целому числу без знака путем добавления UINT_MAX + 1, тогда сложение будет выполнено с беззнаковыми значениями, что приведет к большому result (в зависимости от значений u и i).

Длинный Ответ

В соответствии со стандартом C99:

6.3.1.8 Обычные арифметические преобразования

  1. Если оба операнда имеют один и тот же тип, то дальнейшее преобразование не требуется.
  2. В противном случае, если оба операнда имеют целочисленные типы со знаком или оба имеют целочисленные типы без знака, операнд с типом преобразования меньшего целого ранга преобразуется в тип операнда с большим рангом.
  3. В противном случае, если операнд, имеющий целочисленный тип без знака, имеет ранг, больший или равный рангу типа другого операнда, то операнд с целочисленным типом со знаком преобразуется в тип операнда с целочисленным типом без знака.
  4. В противном случае, если тип операнда с целочисленным типом со знаком может представлять все значения типа операнда с целочисленным типом без знака, то операнд с целочисленным типом без знака преобразуется в тип операнда с целочисленным типом со знаком.
  5. В противном случае оба операнда преобразуются в целочисленный тип без знака, соответствующий типу операнда с целочисленным типом со знаком.

В вашем случае у нас есть один неподписанный int (u) и подписанный int (i).Ссылаясь на (3) выше, поскольку оба операнда имеют одинаковый ранг, ваш i нужно будет быть преобразованный к целому числу без знака.

6.3.1.3 Целые числа со знаком и без знака

  1. Когда значение с целочисленным типом преобразуется в другой целочисленный тип, отличный от _Bool, если значение может быть представлено новым типом, оно остается неизменным.
  2. В противном случае, если новый тип без знака, значение преобразуется путем многократного добавления или вычитания на единицу больше максимального значения, которое может быть представлено в новом типе, пока значение не окажется в диапазоне нового типа.
  3. В противном случае новый тип будет подписан, и значение в нем не сможет быть представлено;либо результат определяется реализацией, либо выдается сигнал, определенный реализацией.

Теперь нам нужно обратиться к пункту (2) выше.Ваш i будет преобразовано в значение без знака путем добавления UINT_MAX + 1.Таким образом, результат будет зависеть от того, как UINT_MAX определяется в вашей реализации.Он будет большим, но не переполнится, потому что:

6.2.5 (9)

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

Бонус:Полуарифметическое преобразование-WTF

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

Вы можете воспользоваться этой ссылкой, чтобы попробовать это онлайн: https://repl.it/repls/QuickWhimsicalBytes

Бонус:Побочный эффект Арифметического преобразования

Правила арифметического преобразования могут быть использованы для получения значения UINT_MAX путем инициализации значения без знака в -1, то есть:

unsigned int umax = -1; // umax set to UINT_MAX

Это гарантированно переносимо независимо от представления системы чисел со знаком из-за правил преобразования, описанных выше.Смотрите этот вопрос SO для получения дополнительной информации: Безопасно ли использовать -1 для установки всех битов в значение true?

Другие советы

Преобразование из подписанного в неподписанный выполняет нет обязательно просто скопируйте или переосмыслите представление подписанного значения.Цитирую стандарт C (C99 6.3.1.3):

Когда значение с целочисленным типом преобразуется в другой целочисленный тип, отличный от _Bool, если значение может быть представлено новым типом, оно остается неизменным.

В противном случае, если новый тип без знака, значение преобразуется путем повторного добавления или вычитания на единицу больше максимального значения, которое может быть представлено в новом типе до тех пор, пока значение не окажется в диапазоне нового типа.

В противном случае новый тип будет подписан, и значение в нем не сможет быть представлено;либо результат определяется реализацией, либо выдается сигнал, определенный реализацией.

Для представления двух дополняющих друг друга элементов, которое в наши дни почти универсально, правила действительно соответствуют переосмыслению битов.Но для других представлений (знаково-величинных или дополняющих их) реализация C все равно должна обеспечивать тот же результат, что означает, что преобразование не может просто скопировать биты.Например, (без знака)-1 == UINT_MAX, независимо от представления.

В общем случае преобразования в C определены так, чтобы оперировать значениями, а не представлениями.

Чтобы ответить на первоначальный вопрос:

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Значение i преобразуется в unsigned int, получая UINT_MAX + 1 - 5678.Затем это значение добавляется к беззнаковому значению 1234, получая UINT_MAX + 1 - 4444.

(В отличие от переполнения без знака, переполнение со знаком вызывает неопределенное поведение.Обтекание распространено, но не гарантируется стандартом C, а оптимизация компилятора может привести к хаосу в коде, который делает необоснованные предположения.)

Ссылаясь на библия:

  • Ваша операция сложения приводит к преобразованию int в unsigned int.
  • Предполагая, что два представления дополняют друг друга и имеют одинаковый размер типов, битовый шаблон не меняется.
  • Преобразование из unsigned int в signed int зависит от реализации.(Но, вероятно, в наши дни это работает так, как вы ожидаете, на большинстве платформ.)
  • Правила немного сложнее в случае объединения подписанных и неподписанных файлов разного размера.

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

Так что это безопасно в том смысле, что результат может быть огромным и неправильным, но он никогда не рухнет.

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

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

Однако, если вы собираетесь выполнять приведение взад и вперед, я бы настоятельно посоветовал называть ваши переменные так, чтобы было ясно, к какому типу они относятся, например:

int iValue, iResult;
unsigned int uValue, uResult;

Слишком легко отвлечься на более важные проблемы и забыть, какая переменная какого типа, если они названы без подсказки.Вы же не хотите приводить к беззнаковому значению, а затем использовать его в качестве индекса массива.

Какие неявные преобразования здесь происходят,

я буду преобразован в целое число без знака.

и безопасен ли этот код для всех значений u и i?

Безопасный в смысле того, что он четко определен, да (см. https://stackoverflow.com/a/50632/5083516 ).

Правила написаны обычно трудночитаемым стандартным языком, но, по сути, какое бы представление ни использовалось в целом числе со знаком, целое число без знака будет содержать дополнительное представление числа в виде 2.

Сложение, вычитание и умножение будут корректно работать с этими числами, в результате чего получится еще одно целое число без знака, содержащее число дополнения twos, представляющее "реальный результат".

деление и приведение к более крупным целочисленным типам без знака будут иметь четко определенные результаты, но эти результаты не будут дополнять представления 2 "реального результата".

(Безопасно, в том смысле, что даже если результат в этом примере переполнится до некоторого огромного положительного числа, я мог бы преобразовать его обратно в int и получить реальный результат.)

В то время как преобразования из signed в unsigned определяются стандартом, обратное определяется реализацией, и gcc, и msvc определяют преобразование таким образом, что вы получите "реальный результат" при преобразовании числа дополнения 2, хранящегося в целом числе без знака, обратно в целое число со знаком.Я ожидаю, что вы найдете любое другое поведение только в малоизвестных системах, которые не используют дополнение 2 для целых чисел со знаком.

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx

Ужасные Ответы В Изобилии

Ozgur Ozcitak

При преобразовании из signed в unsigned (и наоборот) внутреннее представление числа не изменяется.Что меняется, так это то, как компилятор интерпретирует знаковый бит.

Это совершенно неправильно.

Матс Фредрикссон

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

Это тоже неправильно.Целые числа без знака могут быть повышены до целых чисел, если они имеют одинаковую точность из-за битов заполнения в беззнаковом типе.

smh

Ваша операция сложения приводит к преобразованию int в unsigned int.

Неправильно.Может быть, так оно и есть, а может быть, и нет.

Преобразование из unsigned int в signed int зависит от реализации.(Но в наши дни это, вероятно, работает так, как вы ожидаете на большинстве платформ.)

Неправильно.Это либо неопределенное поведение, если оно вызывает переполнение, либо значение сохраняется.

Аноним

Значение i преобразуется в unsigned int ...

Неправильно.Зависит от точности int относительно unsigned int.

Тейлор Прайс

Как было указано ранее, вы можете выполнять переключение между signed и unsigned без проблем.

Неправильно.Попытка сохранить значение за пределами диапазона целого числа со знаком приводит к неопределенному поведению.

Теперь я, наконец, могу ответить на этот вопрос.

Если точность int равна unsigned int , u будет повышен до signed int, и вы получите значение -4444 из выражения (u + i).Теперь, если у нас с u будут другие значения, вы можете получить переполнение и неопределенное поведение, но с этими точными числами вы получите -4444 [1].Это значение будет иметь тип int.Но вы пытаетесь сохранить это значение в unsigned int, чтобы оно затем было приведено к unsigned int, и значение, которое в итоге будет иметь результат, было бы (UINT_MAX +1) - 4444.

Если точность unsigned int больше, чем у int, то подписанный int будет повышен до unsigned int, что даст значение (UINT_MAX+1) - 5678, которое будет добавлено к другому unsigned int 1234.Если у нас с u есть другие значения, из-за которых выражение выходит за пределы диапазона {0..UINT_MAX}, значение (UINT_MAX + 1) будет либо добавляться, либо вычитаться до тех пор, пока результат НЕ попадет в диапазон {0..UINT_MAX) и никакого неопределенного поведения не произойдет.

Что такое точность?

Целые числа имеют биты заполнения, знаковые биты и биты значения.Очевидно, что целые числа без знака не имеют знакового бита.Кроме того, гарантируется, что Unsigned char не будет содержать битов заполнения.Количество битов значений, которыми обладает целое число, зависит от того, насколько высокой точностью оно обладает.

[Ошибки]

Размер макроса сам по себе не может быть использован для определения точности целого числа, если присутствуют биты заполнения.И размер байта не обязательно должен быть октетом (восемью битами), как определено в C99.

[1] Переполнение может произойти в одной из двух точек.Либо перед добавлением (во время продвижения) - когда у вас есть unsigned int, который слишком велик, чтобы поместиться внутри int.Переполнение также может произойти после добавления, даже если unsigned int был в пределах диапазона int, после добавления результат все равно может переполниться.


Кстати, я недавний аспирант, пытающийся найти работу ;)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top