Семантика хранения вычитания двух неподписанных в подписанный - PullRequest
2 голосов
/ 24 марта 2020

Я вычитаю числа без знака и сохраняю их в тип со знаком, и это просто работает. Я не до конца понимаю, почему это просто работает. Возьмите следующий пример:

    #include <stdio.h>

    int main(void)
    {
        uint32_t a = 1;
        uint32_t b = 2;
        int32_t c = a - b;
        printf("%"PRId32"\n", c);
        return 0;
    }

Результат этого вычитания равен -1, и кажется, что это только -1, потому что мой компьютер является дополнением до двух. Я прав? Я смотрю на спецификацию C11.

Если мы выведем следующее утверждение:

        int32_t c = a - b;

Начнем с того, что в соответствии с приоритетом оператора (как указано в аннотации 85 на стр. 76), вычитание:

a - b

C11 6.5.6 / 6:

Результатом бинарного оператора является отличие, полученное в результате вычитания второго операнда из первого.

Это -1 и поэтому не подходит. Преобразование! C11 6.3.1.3/2:

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

Таким образом, фактическое значение a-b равно 4294967295. Далее, оператор присваивания: C11 6.5.16.1/2:

В простом присваивании (=) значение правого операнда преобразуется в тип выражения присваивания и заменяет сохраненное значение в объекте, обозначенном левым операндом.

Таким образом, значение без знака 4294967295 необходимо преобразовать в значение со знаком. Каковы правила? C11 6.3.1.3/3:

В противном случае новый тип подписывается и значение не может быть представлено в нем; либо результат определяется реализацией, либо генерируется определяемый реализацией сигнал.

Таким образом, это полностью определяется реализацией и, таким образом, заканчивается -1, поскольку такова моя реализация.

Это действительно так? Приведет ли код к другому значению, если я запусту его в системе с системой дополнения? Или я что-то пропускаю?

1 Ответ

3 голосов
/ 24 марта 2020

Результатом этого вычитания является -1, и кажется, что это только -1, потому что мой компьютер является дополнением до двух. Я прав?

Перед преобразованием lvalue при присвоении результат фактической операции a - b равен 4294967295 = 2^32 - 1 = UINT_MAX, поскольку вычитание выполняется на uint32_t Операнды и циклический переход четко определены в соответствии с правилом 6.3.1.3/2, которое вы цитируете. Вы получите 4294967295 независимо от того, какой формат подписи использует ваша система.

Это действительно так?

Да, вы правильно прочитали и процитировали стандарт.

Приведет ли код к другому значению, если я запусту его в системе с системой дополнения?

Да. Необработанный двоичный код это число 0xFFFFFFFF, и поэтому преобразование impl.defined в подписанное с большой вероятностью преобразует его в соответствующее представление формата подписи этого необработанного двоичного файла:

  • В системе дополнения 2, 0xFFFFFFFF дает -1.
  • В системе дополнения 1 это даст -0, что, возможно, представляет собой ловушку.
  • В системе величин со знаком это даст -2147483647.

Другие формы не допускаются (C17 6.2.6.2/2).

Или я что-то пропускаю?

Одна маленькая деталь. В отличие от целочисленных правил преобразования C, тип int32_t специально не содержит дерьма. гарантировано (7.20.1.1) всегда использовать 2 дополнения и никаких битов заполнения. Система подписи exoti c с 1 компл. или величина со знаком не может легко поддерживать int32_t - это на самом деле необязательный тип, обязательный только для реализаций дополнения 2. Поэтому ваш код не будет компилироваться, если дополнение 2 не поддерживается.

Также обратите внимание, что строго говоря, правильный спецификатор формата для int32_t равен "%"PRId32, а не %d.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...