Когда char
или short
(включая версии со знаком и без знака) используется в качестве аргумента функции, где нет определенного типа c (как с аргументами ...
для printf(format,...)
) 1 , он автоматически повышается до int
(при условии, что он уже не такой широкий, как int
2 ).
Так что printf("%x\n", m_short);
имеет int
аргумент Какова ценность этого аргумента? В присваивании m_short = m_int;
вы попытались присвоить ему значение -5339876 (представлено байтами 0xffae851 c). Тем не менее, −5339876 не поместится в этот 16-разрядный код. В назначениях преобразование выполняется автоматически, и, когда преобразование целого числа в целочисленный тип со знаком не подходит, результат определяется реализацией. Похоже, что ваша реализация, как и многие другие, использует дополнение до двух и просто берет младшие биты целого числа. Таким образом, он помещает байты 0x851 c в m_short
, представляющие значение -31460.
Напомним, что это повышается до int
для использования в качестве аргумента printf
. В этом случае это соответствует int
, поэтому результат все еще равен -31460. В двоичном дополнении int
это представлено байтами 0xffff851 c.
Теперь мы знаем, что передается в printf
: int
с байтами 0xffff851 c, представляющими значение -31460. Однако вы печатаете его с %x
, который должен получить unsigned int
. При таком несоответствии поведение не определяется стандартом C. Тем не менее, это сравнительно небольшое несоответствие, и многие реализации C позволяют ему скользить. (G CC и Clang не предупреждают даже с -Wall
.)
Предположим, что ваша реализация C не рассматривает printf
как специальную известную функцию и просто генерирует код для вызова, как вы написал это, и что вы позже свяжете эту программу с библиотекой C. В этом случае компилятор должен передать аргумент в соответствии со спецификацией двоичного интерфейса приложения (ABI) для вашей платформы. (ABI определяет, помимо прочего, как аргументы передаются в функции.) Чтобы соответствовать ABI, компилятор C поместит адрес строки формата в одном месте, а биты int
в другом, и затем он вызовет printf
.
Подпрограмма printf
прочитает строку формата, см. %x
и ищет соответствующий аргумент, который должен быть unsigned int
. В каждой реализации C и ABI, о которых я знаю, int
и unsigned int
передаются в одном и том же месте . Это может быть регистр процессора или место в стеке. Допустим, это в регистре r13. Таким образом, компилятор спроектировал вашу вызывающую подпрограмму для помещения int
с байтами 0xffff851 c в r13, а подпрограмма printf
искала unsigned int
в r13 и нашла байты 0xffff851 c.
В результате printf
интерпретирует байты 0xffff851 c, как если бы они были unsigned int
, форматирует их с %x
и печатает «ffff851c».
По сути, вам это сойдет с рук потому что (a) short
повышен до int
, который имеет тот же размер, что и unsigned int
, которого ожидал printf
, и (b) большинство реализаций C не являются строгими в отношении несовпадения целочисленных типов такой же ширины с printf
. Если бы вы вместо этого попытались напечатать int
, используя %ld
, вы могли бы получить другие результаты, такие как «мусорные» биты в старших битах напечатанного значения. Или у вас может быть случай, когда переданный вами аргумент должен находиться в совершенно другом месте, чем ожидаемый printf
, поэтому ни один из битов не является правильным. В некоторых архитектурах неправильная передача аргументов может привести к повреждению стека и разрушению программы различными способами.
Сноски
1 Это автоматическое продвижение c происходит во многих и другие выражения.
2 Существуют некоторые технические детали относительно этих автоматических c целочисленных рекламных акций, которые не должны касаться нас в данный момент.