Почему коротко преобразовать в int перед арифметическими операциями в C и С++?

Из ответов, полученных из этого вопроса, похоже, что С++ унаследовал это требование для преобразования short в int при выполнении арифметических операций из C. Могу ли я выбрать ваши мозги как , почему это было введено в C в первую очередь? Почему не просто выполнять эти операции как short?

Например (взято из предложения dyp в комментариях):

short s = 1, t = 2 ;
auto  x = s + t ;

x будет иметь тип int.

Ответ 1

Если мы посмотрим на Обоснование международного стандарта - языков программирования - C в разделе 6.3.1.8 Обычные арифметические преобразования, которые он говорит (выделение мое в дальнейшем):

Правила в Стандарте для этих преобразований являются небольшими модификациями по сравнению с K & R: эти изменения учитывают добавленные типы и правила сохранения значений. Явная лицензия была добавлена для выполнения вычислений в "более широком" типе, чем это абсолютно необходимо, поскольку это может иногда приводить к созданию меньшего и более быстрого кода, не говоря уже о правильном ответе чаще. Расчеты также могут быть выполнены в более узком виде по правилу "как будто", если получен тот же конечный результат. Явное приведение всегда можно использовать для получения значения в желаемом типе

Раздел 6.3.1.8 из проекта стандарта C99 охватывает Обычные арифметические преобразования, которые применяются к операндам арифметических выражений, например, раздел 6.5.6 Аддитивные операторы гласят:

Если оба операнда имеют арифметический тип, над ними выполняются обычные арифметические преобразования.

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

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

Обсуждение в разделе 6.3.1.1 Обоснования или Международного Стандарта - Языки программирования - C о целочисленных продвижениях на самом деле более интересно, я собираюсь выборочно цитировать b/c, это слишком долго, чтобы полностью цитировать:

Реализация делится на два основных лагеря, которые можно охарактеризовать как сохранение без знака и сохранение стоимости.

[...]

Подход с сохранением без знака требует преобразования двух меньших типов без знака в unsigned int. Это простое правило и дает тип, который не зависит от среды выполнения.

Подход, сохраняющий значения, требует преобразования этих типов в подписанное int, если этот тип может правильно представлять все значения исходного типа, и в противном случае для преобразования этих типов в unsigned int. Таким образом, если среда выполнения представляет short как нечто меньшее, чем int, unsigned short становится int; в противном случае он становится неподписанным Int.

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

Ответ 2

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

Ответ 3

Типы short и char считаются стандартным видом "типов хранения", то есть поддиапазонов, которые вы можете использовать для экономии места, но которые не принесут вам никакой скорости, потому что их размер "неестественен" для процессора.

На некоторых процессорах это не так, но хорошие компиляторы достаточно умны, чтобы заметить, что если вы, например, добавляете константу к неподписанному символу и сохраняете результат обратно в неподписанном символе, то нет необходимости проходить преобразование unsigned char → int. Например, с g++ код, сгенерированный для внутреннего цикла

void incbuf(unsigned char *buf, int size) {
    for (int i=0; i<size; i++) {
        buf[i] = buf[i] + 1;
    }
}

просто

.L3:
    addb    $1, (%rdi,%rax)
    addq    $1, %rax
    cmpl    %eax, %esi
    jg  .L3
.L1:

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

То же самое происходит, если вы выполняете вычисления между короткими целыми числами и сохраняете результат в коротких целых числах.

Ответ 4

Связанный вопрос, похоже, очень хорошо охватывает его: CPU просто нет. 32-разрядный процессор имеет свои собственные арифметические операции, настроенные для 32-разрядных регистров. Процессор предпочитает работать в своем любимом размере, и для таких операций копирование небольшого значения в регистр собственного размера дешево. (Для архитектуры x86 32-разрядные регистры названы так, как если бы они были расширенными версиями 16-разрядных регистров (от eax до ax, ebx до bx и т.д.), См. x86 целые инструкции).

Для некоторых чрезвычайно распространенных операций, в частности арифметики vector/float, могут быть специализированные инструкции, которые работают с другим типом или размером регистра. Для чего-то вроде короткого, заполнение (до) 16 бит нулей имеет очень небольшую стоимость исполнения и добавление специализированных инструкций, вероятно, не стоит времени или места на уме (если вы хотите получить действительно физическое о том, почему, я не уверен, что они будут занимать реальное пространство, но он становится более сложным).