Какви преобразувания на типове се случват?

#include "stdio.h"

int main()
{
    int x = -13701;
    unsigned int y = 3;
    signed short z = x / y;

    printf("z = %d\n", z);

    return 0;
}

Очаквам отговорът да бъде -4567. Получавам "z = 17278". Защо повишението на тези числа води до 17278?

Изпълних това в Code Pad.


person Robert    schedule 23.09.2010    source източник


Отговори (4)


Преобразуванията на скрити типове са:

signed short z = (signed short) (((unsigned int) x) / y);

Когато смесвате подписани и неподписани типове, неподписаните печелят. x се преобразува в unsigned int, разделено на 3 и след това този резултат се преобразува надолу в (подписано) short. С 32-битови цели числа:

(unsigned) -13701         == (unsigned) 0xFFFFCA7B // Bit pattern
(unsigned) 0xFFFFCA7B     == (unsigned) 4294953595 // Re-interpret as unsigned
(unsigned) 4294953595 / 3 == (unsigned) 1431651198 // Divide by 3
(unsigned) 1431651198     == (unsigned) 0x5555437E // Bit pattern of that result
(short) 0x5555437E        == (short) 0x437E        // Strip high 16 bits
(short) 0x437E            == (short) 17278         // Re-interpret as short

Между другото, ключовата дума signed е ненужна. signed short е по-дълъг начин да се каже short. Единственият тип, който се нуждае от изрично signed, е char. char може да бъде подписано или неподписано в зависимост от платформата; всички други типове винаги са подписани по подразбиране.

person John Kugelman    schedule 23.09.2010
comment
Може да си струва да се отбележи, че в общия случай преобразуването от подписано в неподписано не се основава на повторна интерпретация. Всъщност преобразуването и повторното тълкуване са много, много, много различни неща и това, което имаме в този случай, всъщност е преобразуване, а не повторно тълкуване. - person AnT; 23.09.2010
comment
@AnT какво имаш предвид? Струва ми се, че знаковите ‹-› неподписани предавания са точно преинтерпретации, защото битовете в паметта не се променят, а само кое цяло число от база 10 представляват. От друга страна, int ‹-› късите прехвърляния могат действително да променят битове, така че те не са просто повторни интерпретации. - person Aditya Kashi; 07.01.2021
comment
@Aditya Kashi: Прехвърлянията между подписан и неподписан са чисто концептуални (не променяйте паметта) на съвременните хардуерни платформи, просто защото тези платформи използват допълнение с 2 представяне на стойности със знак. Оригиналните C и C++ стандарти не налагаха представяне с допълване на 2, но също така поддържаха представяне с допълнение с 1 и представяне с величина със знак. В тези алтернативни представяния със знак преобразуването вече не е концептуално. Той променя представянето на стойността, което означава, че преобразуването и повторното тълкуване дават различни резултати. - person AnT; 09.01.2021
comment
Последните версии на езиковите стандарти решиха да изоставят поддръжката на допълнение към 1 и величина със знак, така че сега можете сравнително безопасно да предположите, че преобразуването между знак и без знак не променя нищо. - person AnT; 09.01.2021

Кратък отговор: дивизията първо повишава x в unsigned. Само тогава резултатът се връща обратно към signed short.

Дълъг отговор: прочетете тази SO тема.

person Eli Bendersky    schedule 23.09.2010

Проблемите идват от unsigned int y. Наистина, x/y става без знак. Работи с:

#include "stdio.h"

int main()
{
    int x = -13701;
    signed int y = 3;
    signed short z = x / y;

    printf("z = %d\n", z);

    return 0;
}
person ThR37    schedule 23.09.2010

Всеки път, когато смесвате „големи“ стойности със знак и без знак в адитивни и мултипликативни аритметични операции, типът без знак „печели“ и оценката се извършва в домейна на типа без знак („голям“ означава int и по-голям). Ако първоначалната ви стойност със знак е била отрицателна, тя първо ще бъде преобразувана в положителна стойност без знак в съответствие с правилата за преобразувания от знак към неподписан. Във вашия случай -13701 ще се превърне в UINT_MAX + 1 - 13701 и резултатът ще се използва като дивидент.

Обърнете внимание, че резултатът от преобразуването от знак към неподписан на типична 32-битова int платформа ще доведе до стойност без знак 4294953595. След деление на 3 ще получите 1431651198. Тази стойност е твърде голяма, за да бъде въведена принудително в short обект на платформа с 16-битов short тип. Опитът да се направи това води до дефинирано от изпълнението поведение. Така че, ако свойствата на вашата платформа са същите като в моите предположения, тогава вашият код произвежда дефинирано от изпълнението поведение. Формално казано, "безсмислената" 17278 стойност, която получавате, не е нищо повече от конкретно проявление на това дефинирано от изпълнението поведение. Възможно е, ако сте компилирали кода си с разрешена проверка за препълване (ако вашият компилатор ги поддържа), той ще захване при присвояването.

person AnT    schedule 23.09.2010
comment
Всъщност това не е недефинирано поведение: стандартът казва, че или резултатът е дефиниран от имплементацията, или е повдигнат сигнал, дефиниран от реализация (преобразуването към по-тесен тип със знак е различно от препълване по време на изчисление). - person caf; 24.09.2010
comment
@caf: Много си прав. Благодаря за корекцията. Хм... Спомням си, че съм правил тази коригираща забележка доста пъти, но в този случай някак си я забравих :) - person AnT; 24.09.2010