Принудительный знак битового поля (до C ++ 14) при использовании типов с фиксированным размером - PullRequest
2 голосов
/ 27 апреля 2019

Перейдите к жирной части для существенного вопроса, остальное только фон.

По причинам, по которым я предпочитаю не вдаваться, я пишу генератор кода, который генерирует структуры C ++ в (очень) среде до C ++ 14. Генератор должен создавать битовые поля; ему также необходим как можно более жесткий контроль за поведением сгенерированных полей, насколько это возможно. Мне нужно контролировать как размер базовой единицы выделения, так и то, как обрабатываются подписанные значения. Я не буду вдаваться в то, почему я нахожусь в таком дурацком поручении, которое так явно идет вразрез с поведением, определяемым реализацией, но в этом есть зарплата, и люди отвергли все правильные способы сделать то, что нужно сделать. кто организует зарплату.

Так что я застрял, создавая такие вещи, как:

int32_t x : 11;

потому что мне нужно убедить компилятор, что это поле (и другие смежные поля с таким же базовым типом) находятся в 32-битном слове. Генерация int для базового типа не возможна, потому что int не имеет фиксированного размера, и все пойдет не так, как только кто-то выпустит компилятор, в котором int имеет ширину 64 бита, или мы вернемся к тому, где сейчас 16.

В пре-C ++ 14 int x: 11 может быть или не быть полем без знака, и вы добавляете явное signed или unsigned, чтобы получить то, что вам нужно. Я обеспокоен тем, что int32_t и друзья будут иметь одинаковую двусмысленность (почему бы и нет?), Но компиляторы заткнули рот signed int32_t.

Есть ли в стандарте C ++ какие-либо слова о том, накладывает ли типы intxx_t свою подпись на битовые поля? Если нет, есть ли гарантия, что что-то вроде

typedef signed int I32;
...
I32 x : 11;
...
assert(sizeof(I32)==4); //when this breaks, you won't have fun

будет переносить подписанный индикатор в битовое поле?

Пожалуйста, обратите внимание, что любое предложение, которое начинается с "просто сгенерировать функцию для ...", является указанием со стола. Эти сгенерированные заголовки будут включены в код, который выполняет такие действия, как s-> x = 17; и мне было хорошо объяснено, что я не должен предлагать изменить все это на s->set_x(17) даже еще раз. Даже при том, что я мог бы тривиально сгенерировать функцию set_x, чтобы точно и безопасно делать то, что мне нужно, без какого-либо поведения, определенного для реализации. Кроме того, я очень хорошо осведомлён о капризах битовых полей и слева направо и справа налево и наизнанку и обо всём остальном, с чем сталкиваются компиляторы, и нескольких других причинах, по которым это глупое поручение. И я не могу просто «пробовать вещи», потому что это должно работать на компиляторах, которых у меня нет, поэтому я карабкаюсь за гарантиями в стандарте.

Примечание: я не могу реализовать ни одно решение, которое не позволяет существующему коду просто приводить указатель на буфер байтов к указателю на сгенерированную структуру, а затем использовать их указатель для доступа к полям для чтения и записи , Существующий код полностью посвящен s-> x и должен работать без изменений. Это исключает любое решение, включающее конструктор в сгенерированном коде.

Ответы [ 2 ]

1 голос
/ 27 апреля 2019

Есть ли в стандарте C ++ какие-либо слова о том, накладывает ли типы intxx_t свою подпись на битовые поля?

номер


Краткое описание стандарта для целых чисел фиксированной ширины <cstdint>, [cstdint.syn] (ссылка на современный стандарт; соответствующие части резюме выглядят одинаково в C + +11 стандарт ) просто указывает, описательно (не с помощью ключевых слов signed / unsigned), что они должны иметь "целочисленный тип со знаком" или "тип целого без знака" .

например. для gcc , <cstdint> выставить фиксированные целочисленные значения ширины <stdint.h>, которые в свою очередь являются typedef для предопределенных макросов препроцессора (например, __INT32_TYPE__ для int32_t), последняя зависит от платформы.

Стандарт не предусматривает какого-либо обязательного использования ключевых слов signed или unsigned в этом кратком обзоре, и, таким образом, битовые поля целочисленных типов фиксированной ширины в C ++ 11 будут испытывать то же поведение, определяемое реализацией в отношении их подпись как присутствует при объявлении простого целочисленного битового поля. Напомним, что соответствующая часть [class.bit] / 3 до C ++ 14 была (до действия из-за CWG 739 ):

Определяется реализацией независимо от того, является ли битовое поле простого (без явного или без знака) char, short, int, long или long long подписанным или без знака. ...

Действительно, следующая ветка

показывает пример, где, например, __INT32_TYPE__ для конкретной платформы ответчика определяется без явного присутствия ключевого слова signed:

$ gcc -dM -E  - < /dev/null | grep __INT
...
#define __INT32_TYPE__ int
0 голосов
/ 27 апреля 2019

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

Эти две цели несовместимы. Битовые поля по своей природе имеют проблемы с переносимостью.

Если бы стандарт определял желаемое поведение, то «капризов битовых полей» не было бы, и люди не стали бы рекомендовать использовать битовые маски и сдвиги для переносимости.

Что вы, возможно, могли бы сделать, это предоставить класс, который предоставляет тот же интерфейс, что и struct с битовыми полями, но на самом деле он не использует битовые поля внутри. Затем вы можете заставить его конструктор и деструктор читать или записывать эти поля с помощью масок и сдвигов. Например, что-то вроде:

class BitfieldProxy
{
public:
    BitfieldProxy(uint32_t& u)
    : x((u >> 4) & 0x7FF),
      y(u & 0xF),
      mDest(u)
    {
    }

    ~BitfieldProxy()
    {
        assert((x & 0x7FF) == x);
        assert((y & 0xF) == y);
        dest = (x << 4) | y;
    }

    BitfieldProxy(const BitfieldProxy&) = delete;
    BitfieldProxy& operator=(const BitfieldProxy&) = delete;

    // Only the last 11 bits are valid.
    unsigned int x;

    // Only the last 4 bits are valid.
    unsigned int y;

private:
    uint32_t& mDest;
};
...