Целочисленные преобразования (сужение, расширение), неопределенное поведение

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


В примерах я буду иметь в виду:

(signed/unsigned) int bigger; (signed/unsigned) char smaller; 
  1. Усечение целых чисел. (Bigger-> меньше)

    • сначала усечь bigger на стороне MSB, чтобы соответствовать smaller размеру.
    • во-вторых, преобразовать усеченный результат в подпись / без знака в зависимости от меньшего типа.

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

  2. Расширение целых чисел (меньше -> больше)

    a) signed char -> signed int

    • preend меньше с MSB (1 или 0), чтобы соответствовать большему размеру
    • конвертировать в подписанные

    b) signed char -> unsigned int

    • preend меньше с MSB (1 или 0), чтобы соответствовать большему размеру.
    • конвертировать в unsigned

    c) unsigned char -> signed int

    • добавьте 0, чтобы соответствовать большему размеру
    • конвертировать в подписанные

    d) unsigned char -> unsigned int

    • добавьте 0, чтобы соответствовать большему размеру
    • конвертировать в unsigned

Где неопределенные / неуказанные поведения, которые я не упоминал, которые могут появиться?

Интегральное преобразование никогда не приводит к неопределенному поведению (это может привести к поведению, определяемому реализацией).

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

Преобразование в неподписанный тип всегда хорошо определено: значение принимается по модулю UINT_MAX + 1 (или любое другое максимальное значение, которое допускает тип цели).

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

Обратите внимание, что приведенные выше правила определены в терминах целочисленных значений, а не в терминах последовательностей бит.

Из стандартного документа C (стр. 50 черновик версии 201x я верю, а не точную цитату):

  • Ни одно из двух целых чисел не должно иметь одинаковый ранг

  • Ранг целого числа со знаком должен быть больше ранга любого знакового целого с меньшей точностью.

  • long long int больше длинного int, который больше, чем int, который больше, чем short int, который больше, чем знак char.

  • подписанные и неподписанные с одинаковой точностью имеют одинаковый ранг (ex: signed int имеет тот же ранг, что и unsigned int)

  • Ранг любого стандартного целочисленного типа должен быть больше ранга любого расширенного целочисленного типа той же ширины.

  • Ранг char равен unsigned char равно знаку char.

(Я оставляю bool, потому что вы исключили их из своего вопроса)

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

  • для всех целых типов T1 T2 и T3 T1 имеет больший ранг, чем T2, а T2 имеет больший ранг, чем T3, чем T1 имеет больший ранг, чем T3.

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

В простых выражениях:

Любой тип «меньше», чем int или unsigned int, получает значение int при преобразовании в другой тип большего ранга. Это задача компилятора, чтобы убедиться, что код C, скомпилированный для данной машины (архитектуры), соответствует ISO-C в этом отношении. char – это реализация, определенная (подписанная или неподписанная). Все другие типы (продвижение по службе или «понижение») определяются реализацией.

Что такое реализация? Это означает, что данный компилятор будет систематически вести себя одинаково на данной машине. Другими словами, все поведение, определяемое реализацией, зависит от BOTH на компиляторе AND и на целевой машине.

Чтобы сделать переносимый код:

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

Почему это безумие, определяемое реализацией, существует, если оно разрушает усилия программистов? Системное программирование в основном требует такого поведения, определенного для реализации.

Так более конкретно к вашему вопросу:

  • усечение, скорее всего, не будет противочным. Или потребуется гораздо больше усилий в обслуживании, отслеживании ошибок и т. Д., Чем просто поддерживать код, используя более высокие типы рангов.
  • Если ваша реализация запускает значения, превышающие используемые типы, ваш дизайн ошибочен (если вы не участвуете в системном программировании).
  • Как правило, переход от unsigned to signed сохраняет значения, но не наоборот. Поэтому, когда значение без знака выходит на носок против подписанного, продвигайте без знака к подписанному, а не наоборот.
  • Если вы используете как можно более мелкие целые типы, важно критически важно для вашего приложения, вы, вероятно, должны вернуться к архитектуре всей программы.
Давайте будем гением компьютера.