Чому короткий зміст повинен бути перетворений на int перед арифметичними операціями в C та C ++?


74

З відповідей, які я отримав на це запитання , виходить, що С ++ успадкував цю вимогу до перетворення shortв intпри виконанні арифметичних операцій від С. Чи можу я вибрати мозок, чому це було введено в С? Чому б просто не робити ці операції як short?

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

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

xматиме тип int .


7
Просування @Jefffrey Integral є частиною звичайних арифметичних перетворень. short s=1, t=2; auto x = s+t;тоді xє int.
dyp

3
maxshort + maxshort> maxshort
технозавр

23
@technosaurus, який би не пояснив, чому intне підвищений long(maxint + maxint> maxint).
Взуття

10
Я не отримую голосів проти цього питання. Це гарне запитання з цікавою відповіддю. Чотири голоси проти і відсутність коментарів досить знеохочують.
Шафік Ягмур

1
@dyp: Правила, чому xсаме тип int, абсолютно різні в C та C ++ ... ;-)
Дедулікатор

Відповіді:


42

Якщо ми подивимося на обгрунтування міжнародного стандарту - Мови програмування - С у розділі 6.3.1.8 Звичайні арифметичні перетворення, то там сказано ( наголос на мене вперед ):

Правила стандарту для цих перетворень - це незначні модифікації правил у K&R: модифікації враховують додані типи та правила збереження вартості. Явна ліцензія була додана для виконання обчислень "ширшого" типу, ніж це було абсолютно необхідним, оскільки це може іноді створювати менший і швидший код, не кажучи вже про правильну відповідь частіше . Обчислення також можуть виконуватися в “вужчому” типі за правилом ніби до тих пір, поки буде отриманий однаковий кінцевий результат. Явне лиття завжди можна використовувати для отримання значення у бажаному типі

Розділ 6.3.1.8 із проекту стандарту C99 охоплює Звичайні арифметичні перетворення, які застосовуються до операндів арифметичних виразів, наприклад, розділ 6.5.6 Додаткові оператори говорять:

Якщо обидва операнди мають арифметичний тип, на них виконуються звичайні арифметичні перетворення .

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

Якщо int може представляти всі значення вихідного типу, значення перетворюється на int; в іншому випадку він перетворюється на беззнаковий int. Вони називаються цілочисельними акціями . 48) Усі інші типи не змінюються завдяки цілочисельним акціям.

Обговорення з розділу 6.3.1.1про Обгрунтування або міжнародного стандарту мов програмування-C на цілих заохочень насправді цікавіше, я буду вибірково цитатою б / с , що це занадто довго , щоб повністю процитувати:

Реалізація поділилася на два великі табори, які можна охарактеризувати як збереження без підпису та збереження цінності .

[...]

Без знака збереження підхід вимагає просування двох менших беззнакових типів беззнаковое міжнар. Це просте правило і дає тип, який не залежить від середовища виконання.

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

У деяких випадках це може мати досить несподівані результати, як демонструє непослідовність поведінки неявного перетворення між непідписаними та більшими підписаними типами , є багато інших прикладів. Хоча в більшості випадків це призводить до того, що операції працюють належним чином.


2
Так, іноді він стає меншим і швидшим, оскільки вам не потрібні додаткові інструкції для підписання / нульового розширення значень до int або маскування високих бітів. У x86 вам не потрібні додаткові префікси інструкцій для зміни розмірів аргументів
phuclv

Шкода, що обгрунтування не додало вторинне правило, що якщо результат адитивного, мультиплікативного або побітового оператора примусовий до типу без знака, менший за int, вираз буде поводитись так, ніби його операнди були так само примусові, а операція виконана над менший тип. Немає визначених випадків, які суперечили б такому правилу, але деякі компілятори можуть використовувати просування як привід зробити висновок, що твердження типу like x*=y;(з обома змінними unsigned short) обіцяє, що xне може перевищувати 2147483648 / рік.
supercat

якщо у мене є щось подібне int x = 1234і char *y = &x. Бінарне представлення 1234 is 00000000 00000000 00000100 11010010. Моя машина мало ендіанська, тому вона перевертає її і зберігає в пам'яті 11010010 00000100 00000000 00000000LSB на першому місці. Тепер основна частина. якщо я використовую printf("%d" , *p). printfбуде читати перший байт 11010010лише вихід, -46але 11010010це 210так, чому він друкується -46. Я дійсно розгублений, я думаю, якийсь промоушн від цілого числа щось робить, але я не знаю.
Сурадж Джейн

Ви цитуєте стандарт C99, але чи така поведінка не старша за це? Мені потрібно лягати спати, o / w Я б побачив, чи зможу я щось знайти в K&R.
PJTraill

Вікіпедія @PJTraill добре вказує на версію c89, хоча ви не можете отримати офіційний проект. У цій версії в Звичайні арифметичні перетворення вона описує дуже подібну процедуру. Тому я б сказав так. Зверніть увагу, що в цитаті вище сказано, що незначні модифікації цих у K&R, тому K&R повинні відрізнятися.
Шафік Ягмур

22

Це не особливість мови настільки, наскільки це обмеження архітектур фізичних процесорів, на яких працює код. intМашинка в C, як правило , розмір стандартного регістра процесора. Більше кремнію займає більше місця та більше енергії, тому в багатьох випадках арифметика може бути виконана лише на типах даних "природного розміру". Це не є загальним істинним, але більшість архітектур все ще мають це обмеження. Іншими словами, при додаванні двох 8-бітових чисел у процесорі насправді відбувається якийсь тип 32-бітової арифметики, за якою слідує або проста бітова маска, або інший відповідний тип перетворення.


4
Я не впевнений, що тут обов’язково є трохи маски. Процесор робить арифметику в рідному розмірі слова, а потім зберігає лише нижчі біти назад до пам'яті. (Крім того, хоча ви маєте рацію, що більшість архітектур виконують лише арифметику слів, єдиний помітний виняток, Intel, досить широко розповсюджений.)
Джеймс Канце,

@JamesKanze Ви маєте рацію. Я редагував за допомогою відповіді. І так, Intel - це вихід із оптимізованої арифметики, особливо з бібліотеками IPP.
Фонон,

11
Я не погоджуюся з "це не особливість мови"; це є характерною рисою мови. Він визначається так, оскільки ... але він визначається мовою, а не процесором.
Джонатан Леффлер

2
@JonathanLeffler Це, безумовно, особливість мови. З більшості мов, я думаю. Але відповідь Фонона пояснює, чому мови мають цю особливість. (Ймовірно, варто зазначити, що раніше машини мали лише слова, а не байти, півслови і т. Д. І коли була введена адресація байтів, це впливало лише на доступ до пам'яті, а не на регістри та операції. Тому, хоча PDP-11 мав як байтові, так і словні інструкції, коли цільовою адресою байтової інструкції був регістр, байт був розширений до слова.)
Джеймс Канзе,

2
Те, як центральний процесор виконує команди, повністю приховано від коду користувача. Ви взагалі не відповіли на запитання.
Софіт

18

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).

Те саме трапляється, якщо ви робите обчислення між короткими ints і зберігаєте результат у коротких ints.


8

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

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


2
Це не суто апаратне питання, під час розробки стандарту C99 був зроблений свідомий вибір, щоб цілочисельні просування працювали певним чином.
Шафік Ягмур

4
"Зауважте, що 32-розрядні регістри також називаються так, ніби вони є розширеними версіями 16-розрядних регістрів (eax до ax, ebx до bx тощо)" це стосується x86, але не є правильним для більшості інших архітектур. Реєстри MIPS мають однакові назви, незалежно від 32- або 64-розрядного режиму, і вони завжди працюють у
власному
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.