Оберните вокруг объяснения для подписанных и неподписанных переменных в C?
Я прочитал немного в C-спецификации, что беззнаковые переменные (в частности, unsigned short int
) выполняют так называемую перестановку при переполнении целых чисел, хотя я не мог найти ничего в знаковых переменных, кроме того, что я оставил с неопределенным поведением.
Мой профессор сказал мне, что их значения также обернуты (возможно, он просто имел в виду gcc). Я думал, что биты просто обрезаются, а биты, которые я оставляю, дают мне какое-то странное значение!
Что такое обтекание и чем оно отличается от просто обрезания битов.
Ответы
Ответ 1
Подписанные целочисленные переменные не имеют поведения обертки на языке C. Поднятое целочисленное переполнение во время арифметических вычислений вызывает поведение undefined. Обратите внимание, что BTW, о котором вы говорили, компилятор GCC известен тем, что он использует строгую семантику переполнения при оптимизации, что означает, что он использует свободу, обеспечиваемую такими ситуациями поведения undefined: компилятор GCC предполагает, что подписанные целочисленные значения никогда не обертываются. Это означает, что GCC на самом деле является одним из компиляторов, в котором вы не можете полагаться на поведение обертки со знаком целочисленных типов.
Например, компилятор GCC может предположить, что для переменной int i
выполняется следующее условие
if (i > 0 && i + 1 > 0)
эквивалентно простому
if (i > 0)
Это именно то, что означает строгая семантика переполнения.
Неподписанные целые типы реализуют модульную арифметику. По модулю равен 2^N
, где N
- это количество бит в представлении значений типа. По этой причине неподписанные целые типы действительно оказываются обернутыми при переполнении.
Однако, язык C никогда не выполняет арифметические вычисления в областях, меньших, чем у int
/unsigned int
. Тип unsigned short int
, который вы укажете в своем вопросе, обычно будет повышаться, чтобы напечатать int
в выражениях до начала любых вычислений (если предположить, что диапазон unsigned short
соответствует диапазону int
). Это означает, что 1) вычисления с unsigned short int
будут предварительно сформированы в области int
, когда переполнение произойдет, когда переполнение int
, 2) переполнение во время таких вычислений приведет к поведению undefined, а не к обертыванию поведение.
Например, этот код создает обертку вокруг
unsigned i = USHRT_MAX;
i *= INT_MAX; /* <- unsigned arithmetic, overflows, wraps around */
в то время как этот код
unsigned short i = USHRT_MAX;
i *= INT_MAX; /* <- signed arithmetic, overflows, produces undefined behavior */
приводит к поведению undefined.
Если переполнение int
не происходит, и результат преобразуется обратно в тип unsigned short int
, он снова уменьшается по модулю 2^N
, который будет выглядеть так, как если бы значение обернулось вокруг.
Ответ 2
Представьте, что у вас есть тип данных шириной всего 3 бита. Это позволяет вам отображать 8 различных значений: от 0 до 7. Если вы добавите от 1 до 7, вы "обернете" обратно на 0, потому что у вас недостаточно битов для представления значения 8 (1000).
Это поведение хорошо определено для неподписанных типов. Он не является корректным для подписанных типов, поскольку существует множество методов представления знаковых значений, и результат переполнения будет интерпретироваться по-разному на основе этого метода.
Знак-величина: самый верхний бит представляет знак; 0 для положительных, 1 для отрицательных. Если мой тип снова имеет ширину три бита, я могу представить подписанные значения следующим образом:
000 = 0
001 = 1
010 = 2
011 = 3
100 = -0
101 = -1
110 = -2
111 = -3
Поскольку один бит используется для знака, у меня есть только два бита для кодирования значения от 0 до 3. Если я добавлю 1 к 3, я перейду с результатом -0 в качестве результата. Да, есть два представления для 0, один положительный и один отрицательный. Вы часто не сталкиваетесь с представлением знаковой величины.
Одно-дополнение: отрицательное значение является поразрядным положительным значением. Опять же, используя трехбитовый тип:
000 = 0
001 = 1
010 = 2
011 = 3
100 = -3
101 = -2
110 = -1
111 = -0
У меня есть три бита для кодирования моих значений, но диапазон - [-3, 3]. Если я добавлю 1 к 3, в результате я переполним -3. Это отличается от приведенного выше результата знака. Опять же, с помощью этого метода есть два кодирования для 0.
Два дополнения: отрицательное значение побитно инвертирует положительное значение, плюс 1. В трехбитовой системе:
000 = 0
001 = 1
010 = 2
011 = 3
100 = -4
101 = -3
110 = -2
111 = -1
Если я добавлю 1 к 3, я перейду с результатом -4, что отличается от предыдущих двух методов. Заметим, что мы имеем немного больший диапазон значений [-4, 3] и только одно представление для 0.
Два дополнения, вероятно, являются наиболее распространенным методом представления подписанных значений, но это не единственный, поэтому стандарт C не может гарантировать никаких гарантий того, что произойдет, когда вы переполните целочисленный тип со знаком. Поэтому он оставляет поведение undefined, поэтому компилятору не приходится иметь дело с интерпретацией нескольких представлений.
Ответ 3
Поведение undefined происходит из-за проблем с переносимостью, когда объявленные целочисленные типы могут быть представлены как знак и величина, одно дополнение или два дополнения.
В настоящее время все архитектуры представляют целые числа в виде двух дополнений, которые обертываются. Но будьте осторожны: поскольку ваш компилятор прав предположить, что вы не будете выполнять поведение undefined, вы можете столкнуться с странными ошибками при включении оптимизации.
Ответ 4
В подписанном 8-битовом целом, интуитивное определение обертки вокруг может выглядеть как от +127 до -128 - в двоичном двоичном формате: 0111111 (127) и 1000000 (-128). Как вы можете видеть, это естественный прогресс приращения двоичных данных - без учета его представления целого, подписанного или неподписанного. Счетчик интуитивно, фактическое переполнение происходит при переходе от -1 (11111111) в 0 (00000000) в целочисленном смысле без обратной связи.
Это не отвечает на более глубокий вопрос о том, каково правильное поведение, когда переполняется подписанное целое число, поскольку в соответствии со стандартом не существует "правильного" поведения.