Как безопасно извлечь поле со знаком из uint32_t в число со знаком (int или uint32_t)

У меня есть проект, в котором я получаю вектор 32-битных инструкций ARM, и часть инструкций (значения смещения) необходимо читать как числа со знаком (дополнение до двух) вместо чисел без знака.

Я использовал вектор uint32_t, потому что все коды операций и регистры читаются как беззнаковые, а вся инструкция была 32-битной.

Например:

У меня есть эта 32-битная кодировка инструкций ARM:

uint32_t addr = 0b00110001010111111111111111110110

Последние 19 бит — это смещение ветки, которую мне нужно прочитать как целочисленное смещение ветки со знаком. Эта часть: 1111111111111110110


У меня есть эта функция, в которой параметром является вся 32-битная инструкция: я сдвигаю 13 позиций влево, а затем снова 13 позиций вправо, чтобы иметь только значение смещения и перемещать другую часть инструкции.

Я пробовал приводить эту функцию к разным переменным со знаком, используя разные способы приведения и используя другие функции С++, но она печатает число, поскольку оно было беззнаковым.

int getCat1BrOff(uint32_t inst)
{
    uint32_t temp = inst << 13;
    uint32_t brOff = temp >> 13;
    return (int)brOff;
}

Я получаю десятичное число 524278 вместо -10.

Последний вариант, который я считаю не самым лучшим, но он может сработать, — установить все двоичные значения в строку. Инвертируйте биты и добавьте 1, чтобы преобразовать их, а затем снова преобразуйте новое двоичное число в десятичное. Как я бы сделал это в статье, но это не очень хорошее решение.


person Moises Rodan    schedule 17.11.2019    source источник
comment
temp и brOff должны быть подписаны. Не могу проверить, соответствует ли это стандарту, но gcc -ansi -pedantic принимает это :)   -  person Jester    schedule 17.11.2019
comment
И чтобы подчеркнуть: u часть uint32_t означает без знака. Вот что это значит. Без знака означает: когда вы сдвигаете вправо, старший бит заполняется 0. Это фундаментальное свойство целочисленных типов без знака. Это явно не то, что вам нужно, поэтому вы, очевидно, не можете использовать для этого неподписанный тип.   -  person Sam Varshavchik    schedule 17.11.2019
comment
Мне нужно использовать беззнаковый тип, потому что на самом деле это инструкция с кодом операции, регистрами и другими важными частями, которые должны читаться как беззнаковые. Я перешел, чтобы избавиться (сделать 0) битами, которые я не конвертирую в подписанные, и получить только значение смещения инструкции. Я мог бы И это с 0b00000000000001111111111111111111 и тоже работает.   -  person Moises Rodan    schedule 17.11.2019
comment
Вы можете использовать беззнаковый тип везде, где вам это нужно. Но здесь вам нужно использовать подписанный тип по указанным причинам. Нет запрета на временное преобразование в знаковый тип для целей конкретной операции, а затем обратное преобразование.   -  person Sam Varshavchik    schedule 17.11.2019
comment
Я понял. Если я сдвину значение со знаком, оно заполнит биты 1 вместо 0. Позвольте мне проверить   -  person Moises Rodan    schedule 17.11.2019
comment
@SamVarshavchik Спасибо!! Я получил это благодаря вам. Мне нужен был кто-то, чтобы обновить свойства.   -  person Moises Rodan    schedule 17.11.2019
comment
Двоичные и другие операторы C не типизированы. Нет никакого различия в самом операторе для сложения, умножения, деления, сдвига и т.д. желаемый тип перед использованием (бинарного) оператора. Вы можете разыграть его в одну сторону, а затем обратно, если это необходимо.   -  person Erik Eidt    schedule 17.11.2019
comment
Пожалуйста, не включайте ответ в свой вопрос. Если у вас есть решение, вы можете опубликовать ответ.   -  person Keith Thompson    schedule 17.11.2019
comment
brOff << 13 потенциально является UB в переносимом C++, потому что вы можете переполнить целочисленный сдвиг влево со знаком. Не выполняйте приведение к int32_t до после смещения влево. И не публикуйте ответ в вопросе.   -  person Peter Cordes    schedule 18.11.2019


Ответы (2)


Это сводится к расширению знака, где бит знака является 19-м. Есть два способа.

  1. Используйте арифметические сдвиги.
  2. Обнаружение знакового бита и или с единицами в старших битах.

Нет переносимого способа сделать 1. на С++. Но это можно проверить во время компиляции. Пожалуйста, поправьте меня, если приведенный ниже код является UB, но я считаю, что это только определенная реализация, которую мы проверяем во время компиляции. Единственная сомнительная вещь - это преобразование беззнакового в подписанное, которое переполняется, и сдвиг вправо, но это должно быть определено реализацией.

int getCat1BrOff(uint32_t inst)
{
    if constexpr (int32_t(0xFFFFFFFFu) >> 1 == int32_t(0xFFFFFFFFu))
    {
        return int32_t(inst << uint32_t{13}) >> int32_t{13};
    }
    else
    {
        int32_t offset = inst & 0x0007FFFF;
        if (offset & 0x00040000)
        {
            offset |= 0xFFF80000;
        }
        return offset;
    }
}

или более универсальное решение

template <uint32_t N>
int32_t signExtend(uint32_t value)
{
    static_assert(N > 0 && N <= 32);
    constexpr uint32_t unusedBits = (uint32_t(32) - N);
    if constexpr (int32_t(0xFFFFFFFFu) >> 1 == int32_t(0xFFFFFFFFu))
    {
        return int32_t(value << unusedBits) >> int32_t(unusedBits);
    }
    else
    {
        constexpr uint32_t mask = uint32_t(0xFFFFFFFFu) >> unusedBits;
        value &= mask;
        if (value & (uint32_t(1) << (N-1)))
        {
            value |= ~mask;
        }
        return int32_t(value);
    }
}

https://godbolt.org/z/rb-rRB

person Sopel    schedule 17.11.2019

На практике вам просто нужно объявить temp подписанным:

int getCat1BrOff(uint32_t inst)
{
    int32_t temp = inst << 13;
    return temp >> 13;
}

К сожалению, это не переносимо:

Для отрицательного a значение a >> b определяется реализацией (в большинстве реализаций выполняется арифметический сдвиг вправо, так что результат остается отрицательным).

Но я еще не встречал компилятора, который не делает здесь очевидной вещи.

person TonyK    schedule 17.11.2019
comment
C и, более того, C++ — два моих домашних языка. Но иногда я просто хотел бы, чтобы стандарты не мешали и позволяли мне быть явными (например, указывать тип сдвига в сгенерированном ассемблере), что-то вроде переносимого ассемблера. Если какая-то странная эзотерическая арка имеет странное поведение и не поддерживает, например, как логические, так и арифметические сдвиги, компилятор может просто выдать ошибку для этой арки. На данный момент кодирование явного ассемблера, один фрагмент ARM и один X86 могут покрыть 97% рынка;) - person Erik Alapää; 23.11.2019