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

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

#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

5 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

6.5.3.3/3:

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

6.2.5 / 9:

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

6.3.1.1/2:

Следующее может быть использовано в выражении везде, где int или же unsigned int может быть использовано:

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

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

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

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

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

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

Целочисленные правила продвижения 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-битном целом числе со знаком.

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

Когда вы вычисляете -c, c обрабатывается как int, оно становится -15, затем сохраняется в x (который все еще считает, что это НЕ ПОДПИСАНО int) и сохраняется как таковое.

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

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