Оберните вокруг объяснения для подписанных и неподписанных переменных в 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) в целочисленном смысле без обратной связи.

Это не отвечает на более глубокий вопрос о том, каково правильное поведение, когда переполняется подписанное целое число, поскольку в соответствии со стандартом не существует "правильного" поведения.