Непоследовательное поведение неявного преобразования между неподписанными и большими подписанными типами

Рассмотрим следующий пример:

#include <stdio.h>

int main(void)
{
    unsigned char a  = 15; /* one byte */
    unsigned short b = 15; /* two bytes */
    unsigned int c   = 15; /* four bytes */

    long x = -a; /* eight bytes */
    printf("%ld\n", x);

    x = -b;
    printf("%ld\n", x);

    x = -c;
    printf("%ld\n", x);

    return 0;
}

Для компиляции я использую GCC 4.4.7 (и это не давало мне никаких предупреждений):

gcc -g -std=c99 -pedantic-errors -Wall -W check.c

Мой результат:

-15
-15
4294967281

Вопрос в том, почему значения unsigned char и unsigned short "правильно распространяются" (подписаны) long, а unsigned int - нет? Есть ли какая-либо ссылка или правило на этом?

Вот результаты от gdb (слова в порядке младшего порядка):

(gdb) x/2w &x
0x7fffffffe168: 11111111111111111111111111110001    11111111111111111111111111111111 

(gdb) x/2w &x
0x7fffffffe168: 11111111111111111111111111110001    00000000000000000000000000000000

Ответ 1

Это связано с тем, как целые акции применяются к операнду и требование, чтобы результат унарного минуса имел один и тот же тип. Это описано в разделе 6.5.3.3 Унарные арифметические операторы и говорит (акцент мой вперед):

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

и целочисленное продвижение, которое включено в черновик проекта c99 6.3 Conversions и говорит:

если int может представлять все значения исходного типа, значение преобразуется в int; в противном случае он преобразуется в unsigned int. Они называются целыми рекламными акциями. 48) Все остальные типы не изменяются целыми рекламными акциями.

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

-15 преобразуется в unsigned int, используя правила, указанные в разделе 6.3.1.3 Целочисленные и беззнаковые целые числа, которые гласят:

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

Итак, мы закончили с -15 + (UMAX + 1), результатом которого является UMAX - 14, что приводит к большому значению без знака. Иногда вы видите, что код -1 преобразуется в значение без знака, чтобы получить максимальное значение без знака типа, так как оно всегда будет -1 + UMAX + 1, которое равно UMAX.

Ответ 2

int является особенным. В арифметических операциях все меньше int получает int.

Таким образом, -a и -b - это приложения унарного минуса до int значений 15, которые просто работают и производят -15. Затем это значение преобразуется в long.

-c отличается. c не продвигается до int, поскольку он не меньше int. Результат унарного минуса, примененного к значению unsigned int k, снова является unsigned int, вычисленным как 2 N -k (N - количество бит).

Теперь это значение unsigned int обычно преобразуется в long.

Ответ 3

Это правильно. Цитаты из C 9899: TC2.

6.5.3.3/3:

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

6.2.5/9:

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

6.3.1.1/2:

В выражении могут использоваться следующие выражения:21 > ​​или unsigned int:

  • Объект или выражение с целым типом, чей целочисленный ранг преобразования меньше или равен рангам int и unsigned int.

  • Битовое поле типа _Bool, int, signed int или unsigned int.

Если int может представлять все значения исходного типа, значение преобразуется в int; в противном случае он преобразуется в unsigned int. Они называются целыми акциями. Все остальные типы не изменяются целыми рекламными акциями.

Итак, для long x = -a;, так как операнд a, a unsigned char, ранг преобразования меньше ранга int и unsigned int, а все значения unsigned char могут быть представлены как int (на вашей платформе), мы сначала предлагаем тип int. Отрицательный результат прост: int со значением -15.

Та же логика для unsigned short (на вашей платформе).

unsigned int c не изменяется с помощью рекламы. Таким образом, значение -c вычисляется с использованием модульной арифметики, давая результат UINT_MAX-14.

Ответ 4

C целыми правилами продвижения являются то, что они из-за того, что разработчики стандартов хотели разрешить широкий спектр существующих реализаций, которые делали разные вещи, в некоторых случаях, потому что они были созданы до того, как были "стандарты", продолжать делать то, что они были делая при определении правил для новых реализаций, которые были более конкретными, чем "делать то, что вы чувствуете". К сожалению, правила, написанные в письменном виде, чрезвычайно затрудняют запись кода, который не зависит от размера целочисленного компилятора. Даже если будущие процессоры смогут выполнять 64-битные операции быстрее, чем 32-разрядные, правила, продиктованные стандартами, могут привести к разрыву кода, если int когда-либо выросло за 32 бита.

Вероятно, ретроспективно было бы лучше обрабатывать "странные" компиляторы, явно признавая существование множества диалектов C и рекомендуя, чтобы компиляторы реализовали диалект, который обрабатывает различные вещи последовательно, но при условии, что они также могут применять диалекты, которые делают их по-разному. Такой подход может в конечном итоге оказаться единственным способом, который int может вырасти за 32 бита, но я даже не слышал, чтобы кто-то даже рассматривал такую ​​вещь.

Я думаю, что корень проблемы с неподписанными целыми типами связан с тем, что они иногда используются для представления числовых величин и иногда используются для представления членов абстрактного алгебраического кольца обертывания. Неподписанные типы ведут себя в соответствии с абстрактным алгебраическим кольцом в обстоятельствах, которые не связаны с продвижением по типу. Применяя унарный минус к члену кольца, он должен (и делает) давать член того же кольца, который при добавлении к оригиналу будет давать нуль [т.е. аддитивная инверсия]. Существует один способ отображать целочисленные величины в кольцевые элементы, но существует много способов сопоставления элементов кольца с целыми числами. Таким образом, добавление кольцевого элемента к целочисленной величине должно давать элемент одного и того же кольца независимо от размера целого числа, а преобразование из колец в целые количества должно требовать, чтобы код указывал, как должно выполняться преобразование. К сожалению, C неявно преобразует кольца в целые числа в случаях, когда размер кольца меньше, чем целочисленный тип по умолчанию, или когда операция использует элемент кольца с целым числом более крупного типа.

Правильное решение этой проблемы должно состоять в том, чтобы разрешить коду указывать, что определенные переменные, возвращаемые значения и т.д. следует рассматривать как типы звонков, а не числа; выражение, подобное -(ring16_t)2, должно давать 65534 независимо от размера int, а не давать 65534 в системах, где int - 16 бит, а -2 - в тех системах, где оно больше. Аналогично, (ring32)0xC0000001 * (ring32)0xC0000001 должен давать (ring32)0x80000001, даже если int - 64 бит [обратите внимание, что если int - 64 бита, компилятор может законно делать все, что угодно, если код пытается умножить два беззнаковых 32-битных значения который равен 0xC0000001, так как результат будет слишком большим для представления в 64-разрядном значении целого числа.

Ответ 5

Негативы сложны. Особенно, когда речь идет о неподписанных значениях. Если вы посмотрите на c-документацию, вы заметите, что (в отличие от того, что вы ожидаете) unsigned chars и shorts продвигаются до подписанных int для вычисления, в то время как unsigned int будет вычисляться как unsigned int.

Когда вы вычисляете -c, c рассматривается как int, он становится -15, затем сохраняется в x, (который по-прежнему полагает, что это UNSIGNED int) и сохраняется как таковой.

Для пояснения - Никакая АКТУАЛЬНАЯ акция не выполняется, если "отрицательное" значение не указано. Когда вы назначаете отрицательный результат любому типу int (или принимаете отрицательный результат), вместо этого используется 2 комплимента числа. Поскольку единственное практическое различие между неподписанными и подписанными значениями состоит в том, что MSB действует как знак знака, он берется как очень большое положительное число вместо отрицательного.