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

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

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

unsigned int result = u + i;

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

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
144
0
180 935
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Ответ принят как подходящий

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

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

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

Согласно стандарту C99:

6.3.1.8 Usual arithmetic conversions

  1. If both operands have the same type, then no further conversion is needed.
  2. Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
  3. Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
  4. Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.
  5. Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

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

6.3.1.3 Signed and unsigned integers

  1. When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.
  2. Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.
  3. Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

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

6.2.5 (9)

A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.

Бонус: арифметическое преобразование Semi-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, чтобы установить все биты в истинное значение?

Я не понимаю, почему он не может просто определить абсолютное значение, а затем рассматривать его как беззнаковый, как и с положительными числами?

Jose Salvatierra 23.04.2013 03:22

@ D.Singh, не могли бы вы указать не на те части в ответе?

Shmil The Cat 03.09.2014 14:27

Для преобразования подписанного в беззнаковый мы добавляем максимальное значение беззнакового значения (UINT_MAX +1). Аналогично, какой простой способ преобразовать из неподписанного в подписанный? Нужно ли нам вычесть данное число из максимального значения (256 в случае беззнакового символа)? Например: 140 при преобразовании в число со знаком становится -116. Но 20 само становится 20. Так есть какой простой трюк?

Jon Wheelock 18.10.2015 17:01

@JonWheelock см .: stackoverflow.com/questions/8317295/…

Ozgur Ozcitak 19.10.2015 19:55

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

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

Не правда. 6.3.1.8 Обычные арифметические преобразования Если вы суммируете int и беззнаковый char, последний преобразуется в int. Если вы суммируете два беззнаковых символа, они преобразуются в int.

2501 05.05.2015 13:44

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

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

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

int iValue, iResult;
unsigned int uValue, uResult;

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

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

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

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

When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.

Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

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

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

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

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

unsigned int result = u + i;

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

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

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

Озгур Озчитак

When you cast from signed to unsigned (and vice versa) the internal representation of the number does not change. What changes is how the compiler interprets the sign bit.

Это совершенно неверно.

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

When one unsigned and one signed variable are added (or any binary operation) both are implicitly converted to unsigned, which would in this case result in a huge result.

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

smh

Your addition operation causes the int to be converted to an unsigned int.

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

Conversion from unsigned int to signed int is implementation dependent. (But it probably works the way you expect on most platforms these days.)

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

Анонимный

The value of i is converted to unsigned int ...

Неправильный. Зависит от точности int относительно беззнакового int.

Тейлор Прайс

As was previously answered, you can cast back and forth between signed and unsigned without a problem.

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

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

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

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

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

[Попался]

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

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

«Неподписанные целые числа могут быть повышены до целых». Не правда. Целое число повышение не встречается, поскольку типы уже имеют ранг> = int. 6.3.1.1: «Ранг любого целочисленного типа без знака должен равняться рангу соответствующего целочисленного типа со знаком, если таковой имеется». и 6.3.1.8: «В противном случае, если операнд, имеющий целочисленный тип без знака, имеет ранг больше или равно, чем ранг типа другого операнда, то операнд со знаком целочисленного типа преобразуется в тип операнда с целочисленным типом без знака. . " оба гарантируют, что int преобразуется в unsigned int, когда применяются обычные арифметические преобразования.

CB Bailey 08.07.2010 15:19

6.3.1.8 Происходит только после целочисленного продвижения. В первом абзаце говорится: «В противном случае целочисленные повышения выполняются для обоих операндов. ЗАТЕМ следующие правила применяются к повышенным операндам». Итак, прочтите правила продвижения 6.3.1.1 ... «Объект или выражение с целочисленным типом, ранг целочисленного преобразования которого меньше или РАВНО рангу int и unsigned int» и «Если int может представлять все значения исходный тип, значение преобразуется в int ".

Elite Mx 08.07.2010 19:31

6.3.1.1 Целочисленное продвижение, используемое для преобразования некоторых целочисленных типов, которые не являются int или unsigned int, в один из тех типов, где ожидается что-то типа unsigned int или int. «Или равно» было добавлено в TC2, чтобы позволить перечислимым типам ранга преобразования, равным int или unsigned int, преобразовываться в один из этих типов. Никогда не предполагалось, что описанное продвижение приведет к преобразованию между unsigned int и int. Определение общего типа между unsigned int и int по-прежнему регулируется 6.3.1.8, даже после TC2.

CB Bailey 09.07.2010 02:05

6.3.1.8 после продвижения. Итак, если вы читаете 6.3.1.8, продвижение либо произошло, либо нет. 6.3.1.8 не помогает определить, происходит ли продвижение. Это постфактум. 6.3.1.1 говорит о продвижении, и я уже цитировал правильный текст. Более того, продвижение может быть одного из двух типов (беззнаковое или с сохранением значения). C99 - это сохранение ценностей. Итак, если у вас есть два беззнаковых целых числа со значениями x = 5 и y = 6 соответственно, имеет смысл, что если бы подписанный int мог представлять все значения беззнакового int x - y, результатом было бы -1. Пожалуйста, процитируйте C99 в качестве аргумента.

Elite Mx 09.07.2010 03:30

Публикация неправильных ответов при одновременной критике неправильных ответов других не звучит как хорошая стратегия для получения работы ... ;-)

R.. GitHub STOP HELPING ICE 07.08.2010 22:21

Ужасный ответ сам по себе. Слишком много ошибок. Когда преобразование из unsigned int в int переполняется, неопределенное поведение отсутствует. Результат либо определяется реализацией, либо возникает сигнал, определяемый реализацией.

AnT 31.08.2010 15:16

@Charles Bailey: Я согласен с тем, что это, безусловно, намерение, но это больше не эффект простого языка в стандарте, пост TC2. Я предлагаю написать отчет о дефектах, чтобы добавить формулировку к первому пункту в 6.3.1.1p2, отметив, что это не относится к int или unsigned int.

caf 15.07.2011 03:55

Я не голосую за удаление, поскольку такой уровень неправоты в сочетании с высокомерием слишком интересен.

M.M 12.12.2017 11:36

What implicit conversions are going on here,

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

and is this code safe for all values of u and i?

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

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

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

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

(Safe, in the sense that even though result in this example will overflow to some huge positive number, I could cast it back to an int and get the real result.)

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

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

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