Подтвердить что ты не робот

C = a + b и неявное преобразование

С моим компилятором c - 54464 (урезано 16 бит), а d - 10176. Но с gcc, c - 120000, а d - 600000.

Каково истинное поведение? Является ли поведение undefined? Или мой компилятор false?

unsigned short a = 60000;
unsigned short b = 60000;
unsigned long c = a + b;
unsigned long d = a * 10;

Есть ли возможность предупредить об этих случаях?

Wconversion предупреждает:

void foo(unsigned long a);
foo(a+b);

но не предупреждает:

unsigned long c = a + b
4b9b3361

Ответ 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

Ответ 2

a + b будет вычисляться как unsigned int (факт, что он назначен unsigned long, не имеет значения). В стандарте C указано, что эта сумма будет охватывать по модулю "один плюс самый большой неподписанный". В вашей системе это выглядит так: unsigned int - 16 бит, поэтому результат вычисляется по модулю 65536.

В другой системе это выглядит так: int и unsigned int больше и, следовательно, способны удерживать большие числа. То, что происходит сейчас, довольно тонкое (признайте @PascalCuoq): Beacuse все значения unsigned short представляются в int, a + b будут вычисляться как int. (Только если short и int имеют одинаковую ширину или каким-то другим образом некоторые значения unsigned short не могут быть представлены как int, сумма будет вычисляться как unsigned int.)

Хотя в стандарте C не указывается фиксированный размер для unsigned short или unsigned int, поведение вашей программы четко определено. Обратите внимание, что это значение не для подписанного типа.

В качестве последнего замечания вы можете использовать размерные типы uint16_t, uint32_t и т.д., которые, если поддерживается вашим компилятором, гарантированно имеют указанный размер.

Ответ 3

В C типы char, short (и их неподписанные couterparts) и float следует рассматривать как типы хранения, поскольку они предназначены для оптимизации хранилища, но не являются "родными", размер, который предпочитает процессор, и они никогда не используются для вычислений.

Например, если у вас есть два значения char и поместить их в выражение, они сначала преобразуются в int, тогда выполняется операция. Причина в том, что CPU работает лучше с int. То же самое происходит для float, который всегда неявно преобразуется в double для вычислений.

В вашем коде вычисление a+b представляет собой сумму двух целых без знака; в C нет способа вычислить сумму двух unsigned shorts... что вы можете сделать, это сохранить конечный результат в unsigned short, который благодаря свойствам modulo math будет таким же.