Спасибо, это дало мне больше ясности

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

Пример 1)
Почему это дает странное, большое целое число, а не 255?

unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y); 

Пример 2)
Почему это дает «-1 больше 0»?

unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
  puts("-1 is larger than 0");

Пример 3)
Почему изменение типа в приведенном выше примере наshort решить проблему?

unsigned short a = 1;
signed short b = -2;
if(a + b > 0)
  puts("-1 is larger than 0"); // will not print

(Эти примеры были предназначены для 32- или 64-разрядного компьютера с 16-разрядным сокращением.)

 savram06 сент. 2017 г., 17:15
Подождите секунду, ОП - это тот же парень, который ответил на вопрос? Он говорит, что Лундин спросил, лучший ответ Лундина, а лол
 Andre Kampling07 сент. 2017 г., 14:06
@savram: Делить знания таким способом абсолютно нормально. Видетьздесь: сам ответ.
 Ian Abbott06 сент. 2017 г., 14:41
Я предлагаю документировать предположения для примеров, например, Пример 3 предполагает, чтоshort уже чемint (или другими словами, предполагается, чтоint может представлять все значенияunsigned short).
 Lundin07 сент. 2017 г., 08:31
@savram Да, мы планируем написать FAQ. Обмен знаниями таким способом подходит для SO - в следующий раз, когда вы публикуете вопрос, отметьте флажок «ответить на свой вопрос». Но, конечно, вопрос все еще рассматривается как любой другой вопрос, и другие могут также публиковать ответы. (И вы не заработаете ни одного представителя от принятия своего собственного ответа)

Ответы на вопрос(2)

я хочу дать больше информации о каждом примере.

Пример 1)

int main(){
    unsigned char x = 0;
    unsigned char y = 1;
    printf("%u\n", x - y); 
    printf("%d\n", x - y);
}

Поскольку unsigned char меньше, чем int, мы применяем к ним целочисленное продвижение, тогда мы имеем (int) x- (int) y = (int) (- 1) и unsigned int (-1) = 4294967295.

Вывод из приведенного выше кода: (так же, как мы ожидали)

4294967295
-1

Как это исправить?

Я попробовал то, что рекомендовал предыдущий пост, но это на самом деле не работает. Вот код, основанный на предыдущем посте:

измените один из них на неподписанный int

int main(){
    unsigned int x = 0;
    unsigned char y = 1;
    printf("%u\n", x - y); 
    printf("%d\n", x - y);
}

Поскольку x уже является целым числом без знака, мы применяем только целочисленное продвижение к y. Тогда мы получим (без знака int) x- (int) y. Поскольку они по-прежнему не имеют одинаковый тип, мы применяем обычные арифметические преобразования, мы получаем (unsigned int) x- (unsigned int) y = 4294967295.

Вывод из приведенного выше кода: (так же, как мы ожидали):

4294967295
-1

Точно так же следующий код получает тот же результат:

int main(){
    unsigned char x = 0;
    unsigned int y = 1;
    printf("%u\n", x - y); 
    printf("%d\n", x - y);
}

изменить оба из них без знака Int

int main(){
    unsigned int x = 0;
    unsigned int y = 1;
    printf("%u\n", x - y); 
    printf("%d\n", x - y);
}

Поскольку оба они являются беззнаковыми int, целочисленное продвижение не требуется. По обычной арифметической конверсии (имеют одинаковый тип), (без знака int) x- (без знака int) y = 4294967295.

Вывод из приведенного выше кода: (так же, как мы ожидали):

4294967295
-1

Один из возможных способов исправить код: (добавьте приведение типа в конце)

int main(){
    unsigned char x = 0;
    unsigned char y = 1;
    printf("%u\n", x - y); 
    printf("%d\n", x - y);
    unsigned char z = x-y;
    printf("%u\n", z);
}

Выход из вышеприведенного кода:

4294967295
-1
255

Пример 2)

int main(){
    unsigned int a = 1;
    signed int b = -2;
    if(a + b > 0)
        puts("-1 is larger than 0");
        printf("%u\n", a+b);
}

Поскольку оба они являются целыми числами, целочисленное продвижение не требуется. При обычном арифметическом преобразовании мы получаем (int без знака) a + (int без знака) b = 1 + 4294967294 = 4294967295.

Вывод из приведенного выше кода: (так же, как мы ожидали)

-1 is larger than 0
4294967295

Как это исправить?

int main(){
    unsigned int a = 1;
    signed int b = -2;
    signed int c = a+b;
    if(c < 0)
        puts("-1 is smaller than 0");
        printf("%d\n", c);
}

Выход из вышеприведенного кода:

-1 is smaller than 0
-1

Пример 3)

int main(){
    unsigned short a = 1;
    signed short b = -2;
    if(a + b < 0)
        puts("-1 is smaller than 0");
        printf("%d\n", a+b);
}

Последний пример исправил проблему, поскольку a и b оба преобразованы в int из-за целочисленного продвижения.

Выход из вышеприведенного кода:

-1 is smaller than 0
-1

Если я перепутал некоторые понятия, пожалуйста, дайте мне знать. Благодаря ~

 AmanSharma06 июл. 2018 г., 14:17
Спасибо, это дало мне больше ясности
Решение Вопроса

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

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

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

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

Целочисленные типы и рейтинг конверсии

Целочисленные типы в C:char, short, int, long, long long а такжеenum.
_Bool/bool также рассматривается как целочисленный тип, когда речь заходит о продвижении по типу.

Все целые числа имеют указанныйконверсионный ранг, C11 6.3.1.1, особое внимание уделено наиболее важным частям:

Каждый целочисленный тип имеет ранг целочисленного преобразования, определенный следующим образом:
- Ни один из двух целочисленных типов со знаком не должен иметь одинаковый ранг, даже если они имеют одинаковое представление.
- ранг целочисленного типа со знаком должен быть больше ранга целочисленного типа со знаком с меньшей точностью.
- званиеlong long int должно быть больше, чем рангlong int, который должен быть больше, чем рангint, который должен быть больше, чем рангshort int, который должен быть больше, чем рангsigned char.
- ранг любого целого типа без знака должен равняться рангу соответствующего целого типа со знаком, если таковой имеется.

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

Типы изstdint.h сортировать и здесь, с тем же рангом, что и любой тип, которому они соответствуют в данной системе. Например,int32_t имеет тот же ранг, что иint в 32-битной системе.

Кроме того, C11 6.3.1.1 определяет, какие типы считаютсямаленькие целочисленные типы (не формальный термин):

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

- объект или выражение с целочисленным типом (кромеint или жеunsigned int) чей целочисленный конверсионный ранг меньше или равен рангуint а такжеunsigned int.

Что этот загадочный текст на практике означает, что_Bool, char а такжеshort (а такжеint8_t, uint8_t и т. д.) являются "малыми целочисленными типами". Они рассматриваются особым образом и подлежат скрытому продвижению, как описано ниже.

Целочисленные акции

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

Формально правило гласит (C11 6.3.1.1):

Еслиint может представлять все значения исходного типа (как ограничено шириной для битового поля), значение преобразуется вint; в противном случае он преобразуется вunsigned int, Это называетсяцелочисленные акции.

Этот текст часто неправильно понимают как: «все малые целочисленные типы со знаком преобразуются в целое число со знаком, а все малые целочисленные типы без знака преобразуются в целое число без знака». Это неверно Часть без знака здесь означает только то, что если мы имеем, например,unsigned short операнд иint бывает того же размера, что иshort в данной системе, тоunsigned short операнд преобразуется вunsigned int, Как, впрочем, ничего особенного на самом деле не происходит. Но в случаеshort это меньший тип, чемintвсегда преобразуется в (подписано)int, независимо от того, был ли подписан или не подписан!

Суровая реальность, вызванная целочисленными повышениями, означает, что на C не может быть выполнено почти никакой операции с такими маленькими типами, какchar или жеshort, Операции всегда проводятся наint или более крупные типы.

Это может звучать как глупость, но, к счастью, компилятору разрешено оптимизировать код. Например, выражение, содержащее дваunsigned char операнды получат операндыint и операция проводится какint, Но компилятору разрешено оптимизировать выражение, чтобы оно фактически выполнялось как 8-битная операция, как и следовало ожидать. Однако здесь возникает проблема: компиляторне позволил оптимизировать неявное изменение подписи, вызванное целочисленным продвижением. Потому что компилятор не может сказать, намеренно ли программист полагается на неявное продвижение или он непреднамеренный.

Вот почему пример 1 в вопросе терпит неудачу. Оба беззнаковых операнда повышаются до типаintОперация проводится по типуintи результатx - y имеет типint, Это означает, что мы получаем-1 вместо255 чего можно было ожидать. Компилятор может генерировать машинный код, который выполняет код с 8-битными инструкциями вместоint, но это может не оптимизировать изменение подписи. Это означает, что мы получаем отрицательный результат, который, в свою очередь, приводит к странному числу, когдаprintf("%u вызывается. Пример 1 можно исправить, приведя результат операции обратно к типуunsigned char.

За исключением нескольких особых случаев, таких как++ а такжеsizeof операторы, целочисленные продвижения применяются почти ко всем операциям в C, независимо от того, используются ли унарные, бинарные (или троичные) операторы.

Обычные арифметические преобразования

Всякий раз, когда двоичная операция (операция с 2 операндами) выполняется в C, оба операнда оператора должны быть одного типа. Следовательно, в случае, если операнды имеют разные типы, C обеспечивает неявное преобразование одного операнда в тип другого операнда. Правила того, как это делается, названыобычные артихметические преобразования (иногда неофициально упоминается как «балансировка»). Они указаны в C11 6.3.18:

(Думайте об этом правиле как о длинном, вложенномif-else if Заявление и это может быть легче читать :))

6.3.1.8 Обычные арифметические преобразования

Многие операторы, которые ожидают операнды арифметического типа, вызывают преобразования и выдают типы результатов аналогичным образом. Цель состоит в том, чтобы определить общий реальный тип для операндов и результата. Для указанных операндов каждый операнд преобразуется без изменения типа домена в тип, соответствующий действительный тип которого является общим действительным типом. Если явно не указано иное, общий действительный тип также является соответствующим действительным типом результата, чья область типов является областью типов операндов, если они одинаковы, и сложной в противном случае. Эта модель называетсяобычные арифметические преобразования:

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

В противном случае целочисленные продвижения выполняются для обоих операндов. Затем к повышенным операндам применяются следующие правила:

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

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

Это причина, почемуa + b в примере 2 дает странный результат. Оба операнда являются целыми числами, и они по крайней мере рангаint, поэтому целочисленные акции не применяются. Операнды не одного типа -a являетсяunsigned int а такжеb являетсяsigned int, Поэтому операторb временно преобразуется в типunsigned int, Во время этого преобразования он теряет информацию о знаке и заканчивается как большое значение.

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

 Lundin17 нояб. 2017 г., 13:27
@jfs Для бинарных операторов, где применяются обычные арифметические преобразования, часть «следует» включена как часть тех правил, которые приведены выше. Для других операторов они повышаются, если этого требует конкретный оператор (глава 6.5). Возьмите например одинарный- operator, 6.5.3.3: «Результат унарного оператора является отрицательным для его (повышенного) операнда. Целочисленные преобразования выполняются над операндом, и результат имеет повышенный тип.». Другой пример (особый случай) - префикс++, который не упоминает целочисленное продвижение, и, следовательно, его операнд не продвигается.
 savram06 сент. 2017 г., 17:11
Очень основательно и информативно.
 Lundin25 февр. 2019 г., 08:42
@ Student Ах, теперь я понял, ожидание объяснения действительно не соответствует предложенному решению. Обновлено, спасибо.
 jfs17 нояб. 2017 г., 12:31
«Всякий раз, когда в выражении используется маленький целочисленный тип, он неявно преобразуется в int, которое всегда подписано». Не могли бы вы указать точное место в стандарте, которое говорит, что этодолжен случиться? Цитата C11 6.3.1.1 говориткак это случается (если это случается), но это не говорит, что этодолжен например, почемуx - y в вопросе ведет себя как(unsigned)(int)((int)x - (int)y) вместо(unsigned)(int)((Uchar)((Uchar)x - (Uchar)y)) goo.gl/nCvJy5. Где стандарт говорит, что еслиx то чар то+x являетсяint (или без знака)? В c ++ это §5.3.1.7 goo.gl/FkEakX
 Erik Eidt04 янв. 2019 г., 19:32
«Пример 1 можно исправить, приведя один или оба операнда к типу unsigned int». Предложенный бросок (и) не даст 255, как ожидалось. Правильное исправление - привести результат вычитания обратно к(unsigned char) что операнды начались, как в(unsigned char) (x-y): это даст ОП ожидаемые 255. Люди часто не ценят приведение к меньшему размеру, однако это правильный способ выполнения усечения (за которым последует неявное / автоматическое подписывание или нулевое расширение до ~ int размера).

Ваш ответ на вопрос