Ответ 1
Во-первых, вы должны знать, что в C стандартные типы не имеют конкретной точности (количество отображаемых значений) для стандартных целых типов. Для каждого типа требуется только минимальная точность. Это приводит к следующим типовым размерам бит, стандарт позволяет более сложные представления:
-
char
: 8 бит -
short
: 16 бит -
int
: 16 (!) бит -
long
: 32 бит -
long long
(начиная с C99): 64 бит
Примечание. Фактические пределы (которые подразумевают определенную точность) реализации приведены в limits.h
.
Во-вторых, тип, который выполняется операция, определяется типами операндов, а не типом левой части задания (поскольку присваивания также являются справедливыми выражениями). Для этого типы, приведенные выше, сортируются по рангу конверсии. Операнды меньшего ранга, чем int
, сначала преобразуются в int
. Для других операндов единица с меньшим рангом преобразуется в тип другого операнда. Это обычные арифметические преобразования.
В вашей реализации используется 16-разрядный unsigned int
с тем же размером, что и unsigned short
, поэтому a
и b
преобразуются в unsigned int
, операция выполняется с 16 бит. Для unsigned
операция выполняется по модулю 65536 (2 - по мощности 16) - это называется оберткой (это значение не, которое требуется для подписанных типов!). Затем результат преобразуется в unsigned long
и присваивается переменным.
Для gcc я предполагаю, что это компиляция для ПК или 32-битного процессора. для этого (unsigned) int
имеет обычно 32 бита, а (unsigned) long
имеет не менее 32 бит (обязательно). Таким образом, для операций нет обертки.
Примечание. Для ПК операнды преобразуются в int
, а не unsigned int
. Это потому, что int
уже может представлять все значения unsigned short
; unsigned int
не требуется. Это может привести к непредвиденному (фактически: определенному по реализации) поведению, если результат операции переполняет signed int
!
Если вам нужны типы определенного размера, см. stdint.h
(начиная с C99) для uint16_t
, uint32_t
. Это typedef
для типов с соответствующим размером для вашей реализации.
Вы также можете отбросить один из операндов (не целое выражение!) к типу результата:
unsigned long c = (unsigned long)a + b;
или, используя типы известных размеров:
#include <stdint.h>
...
uint16_t a = 60000, b = 60000;
uint32_t c = (uint32_t)a + b;
Обратите внимание, что из-за правил преобразования достаточно лить один операнд.
Обновить (благодаря @chux):
Показанный выше бросок работает без проблем. Однако, если a
имеет более высокий ранг преобразования, чем тип, это может усечь его значение для меньшего типа. Хотя это можно легко избежать, поскольку все типы известны во время компиляции (статическая типизация), альтернативой является умножение на 1 желаемого типа:
unsigned long c = ((unsigned long)1U * a) + b
Таким образом используется более высокий ранг типа, заданного в литой или a
(или b
). Умножение будет устранено любым разумным компилятором.
Другой подход, избегая даже знать имя целевого типа, можно сделать с расширением typeof()
gcc:
unsigned long c;
... many lines of code
c = ((typeof(c))1U * a) + b