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

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

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

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

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

4 ответа

Решение

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

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

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

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) используется.

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

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

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

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